Compare commits

..

107 commits

Author SHA1 Message Date
268081b28b
Merge pull request 'fix: actually provide maven profile' (!50) from feature/esb-deploy-action into main
Reviewed-on: #50
Reviewed-by: Kraft_Ruben_-_J._Schmalz_GmbH <Ruben.Kraft@schmalz.de>
2026-06-17 07:50:35 +00:00
9783972537 fix: actually provide maven profile
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 59s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 41s
2026-06-17 09:41:57 +02:00
ee976b306e
Merge pull request 'feature/esb-deploy-action' (!49) from feature/esb-deploy-action into main
Reviewed-on: #49
2026-06-17 06:54:26 +00:00
a49611f288 fix: maven action did not set profile in verify mode
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 57s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 35s
2026-06-17 08:52:37 +02:00
9149415575 fix: action did not provide stage toggle 2026-06-17 08:01:38 +02:00
6a84d5d6f2
Merge pull request 'fix: readme mentioned wrong parameters' (!48) from feature/esb-deploy-action into main
Reviewed-on: #48
Reviewed-by: Kraft_Ruben_-_J._Schmalz_GmbH <Ruben.Kraft@schmalz.de>
2026-06-17 05:46:13 +00:00
0134da8ac7 fix: propagate maven profile in esb-deploy action
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 57s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 42s
2026-06-17 07:42:35 +02:00
115300a7e1 fix: readme mentioned wrong parameters
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m57s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 2m16s
2026-06-16 16:32:34 +02:00
c57466f628
Merge pull request 'fix: use correct name in tag-release workflow' (!47) from feature/esb-deploy-action into main
Reviewed-on: #47
Reviewed-by: Kraft_Ruben_-_J._Schmalz_GmbH <Ruben.Kraft@schmalz.de>
2026-06-16 14:27:06 +00:00
4e15383d23 fix: use correct name in tag-release workflow
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 38s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 51s
2026-06-16 16:25:43 +02:00
c2587887a0
Merge pull request 'feat: create deploy-esb action' (!46) from feature/esb-deploy-action into main
Reviewed-on: #46
Reviewed-by: Kraft_Ruben_-_J._Schmalz_GmbH <Ruben.Kraft@schmalz.de>
2026-06-16 14:23:52 +00:00
5b6f2cfd28 feat: create deploy-esb action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 53s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m14s
2026-06-16 16:06:32 +02:00
f6549e5a5b
Merge pull request 'fix: revert workspace selection as validate does not initialize backend' (!45) from fix/terraform-validate-backend-error into main
Reviewed-on: #45
Reviewed-by: Marcel Frey <Marcel.Frey@schmalz.de>
2026-06-15 07:29:40 +00:00
36343e0a79 fix: revert workspace selection as validate does not initialize backend
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 32s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 48s
2026-06-15 09:28:00 +02:00
463f657b4a
Merge pull request 'fix: create terraform workspaces automatically in shared actions' (!44) from fix/terraform-workspace-or-create into main
Reviewed-on: #44
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-06-15 06:46:11 +00:00
312d297a55 fix: create terraform workspaces automatically in shared actions
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m5s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 41s
2026-06-15 12:09:09 +05:30
add4dd1b95
Merge pull request 'feat: add publish actions with documentation' (!42) from feature/publish into main
Reviewed-on: #42
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-06-11 08:23:20 +00:00
801c7e2249 feat: add publish actions with documentation
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m5s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 47s
2026-06-11 07:34:01 +00:00
c6c23a8827
Merge pull request 'feat: add playwright actions with documentation' (#41) from feature/playwright-actions into main
Reviewed-on: #41
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-06-10 07:05:40 +00:00
3c3aa7a8ce feat: add playwright actions with documentation
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m39s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m43s
2026-06-08 15:06:22 +00:00
6a167350f0
Merge pull request 'feat: add vacuum-lint action with documentation' (#40) from feature/vacuum-lint into main
Reviewed-on: #40
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-06-08 06:52:28 +00:00
DMI
e3c0fdac23 feat: add vacuum-lint action with documentation
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 34s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 45s
2026-06-03 14:42:18 +00:00
35b0a321bc
Merge pull request 'feat: add rust-build action with documentation' (#39) from feature/rust-build into main
Reviewed-on: #39
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-06-02 05:21:33 +00:00
802aa7d6fe feat: add rust-build action with documentation
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 44s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 51s
2026-06-01 14:30:16 +00:00
909fb37930
Merge pull request 'fix: update cache control argument handling for S3 sync' (#38) from bugfix/CACHE_CONTROL_ARG into main
Reviewed-on: #38
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-06-01 12:44:17 +00:00
03b9e01225
Merge pull request 'feat: decouple hostname from action' (#37) from feature/ITDO-339 into main
Reviewed-on: #37
Reviewed-by: Kraft_Ruben_-_J._Schmalz_GmbH <Ruben.Kraft@schmalz.de>
2026-06-01 12:35:51 +00:00
08123e3f56 fix: update cache control argument handling for S3 sync
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 49s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 26s
2026-06-01 12:35:17 +00:00
e05e71fe12
Merge pull request 'feat: add download-artifact and upload-artifact actions with documentation' (#35) from feature/download-upload-artifact into main
Reviewed-on: #35
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-06-01 11:42:23 +00:00
f13ae2e528
Merge pull request 'feat: add i18n-sync action to fetch translations and create pull requests' (#34) from feature/i18n-sync into main
Reviewed-on: #34
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-06-01 11:40:45 +00:00
452039b6fd feat: decouple hostname from action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 47s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m16s
Refs: ITDO-339
2026-06-01 11:06:31 +02:00
7607756bf0
Merge pull request 'chore: use the workflow standards' (#36) from chore/pipelines into main
Reviewed-on: #36
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-06-01 07:19:45 +00:00
dcb17b88bf chore: use the workflow standards
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 34s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m2s
2026-05-22 09:57:52 +00:00
ed2dcca9bb feat: add download-artifact and upload-artifact actions with documentation
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 56s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m7s
2026-05-22 09:55:02 +00:00
afa2cfc241 feat: add i18n-sync action to fetch translations and create pull requests
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 40s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 39s
2026-05-22 09:27:55 +00:00
0473d4bc1d
Merge pull request 'fix: force public npm registry for pnpm self-installer bootstrap' (#33) from bugfix/pnpm-bootstrap-registry into main
Reviewed-on: #33
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-05-22 08:41:28 +00:00
6dc474f759 fix: force public npm registry for pnpm self-installer bootstrap
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 40s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 39s
pnpm/action-setup bootstraps itself via npm before pnpm is available.
If a repo has a custom registry in .npmrc (e.g. pointing to JFrog or
Nexus), the self-installer tries to fetch pnpm from that registry
without credentials and fails with exit code 1.

Setting NPM_CONFIG_REGISTRY overrides .npmrc for this step only,
ensuring pnpm is always fetched from the public registry. Private
registry auth is configured in subsequent steps once pnpm is ready.
2026-05-22 09:29:42 +02:00
97d17f46e8
Merge pull request 'feat: add per-file cache_rules and fix argument escaping' (#32) from feature/publish-static-contents-cache-rules into main
Reviewed-on: #32
Reviewed-by: Böhringer_Sebastian_-_J._Schmalz_GmbH <Sebastian.Boehringer@schmalz.de>
2026-05-22 05:09:47 +00:00
c690178858 feat: add per-file cache_rules and fix argument escaping
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 29s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m17s
- Add cache_rules input: JSON array of per-file cache overrides, each uploaded individually with its own Cache-Control and Content-Type headers and excluded from the bulk sync
- When versioning is true and cache_rules is empty, auto short-cache index.html (no-cache, max-age=300) if it exists in dist_dir
- Fix EXCLUDE_ARGS and CT_ARG to use bash arrays for correct handling of filenames and content-type values containing spaces
2026-05-22 05:06:42 +00:00
00d818b153
Merge pull request 'feat: add mock-files input to create empty files for Terraform validation' (#31) from feature/terraform-validate-mock-files into main
Reviewed-on: #31
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-05-21 16:07:26 +00:00
bd5e3add23 feat: add mock-files input to create empty files for Terraform validation
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m18s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 2m27s
2026-05-21 13:50:14 +00:00
038b488a6b
Merge pull request 'fix: strip leading slash from forgejo.repository in aikido full scan' (#29) from fix/aikido-full-scan-strip-leading-slash into main
Reviewed-on: #29
Reviewed-by: Marcel Frey <Marcel.Frey@schmalz.de>
2026-05-21 07:55:55 +00:00
e6012e11c0 fix: strip leading slash from forgejo.repository in aikido full scan
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 56s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m43s
2026-05-21 07:50:37 +00:00
9f70cd804d
Merge pull request 'fix: use forgejo.repository for aikido full scan schedule compatibility' (#28) from fix/aikido-full-scan-schedule-repository into main
Reviewed-on: #28
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-05-21 07:40:47 +00:00
44164494c8 fix: use forgejo.repository for aikido full scan schedule compatibility
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 38s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 2m4s
forgejo.event.repository.name is absent on schedule triggers.
forgejo.repository is always set to 'owner/repo' regardless of trigger type.
2026-05-21 07:37:05 +00:00
74aa5eb12f
Merge pull request 'fix: update versioning parameters in README and action.yml for clarity' (#27) from bugfix/publish-static-contents-versioning into main
Reviewed-on: #27
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-19 15:23:03 +00:00
4d2a9815d7 fix: update versioning parameters in README and action.yml for clarity
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 48s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m23s
Co-authored-by: Copilot <copilot@github.com>
2026-05-19 15:07:29 +00:00
4bb087153f
Merge pull request 'fix: install AWS CLI if not present before publishing assets' (#26) from bugfix/aws-sdk into main
Reviewed-on: #26
Reviewed-by: Maier David - J. Schmalz GmbH <david.maier@noreply.schmalz-git.git.onstackit.cloud>
2026-05-11 15:08:36 +00:00
d89b7842e2 fix: install AWS CLI if not present before publishing assets
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 32s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m20s
Co-authored-by: Copilot <copilot@github.com>
2026-05-11 13:52:20 +00:00
98fb3d79dd
Merge pull request 'fix: use actions with node20 support' (#25) from bugfix/node20 into main
Reviewed-on: #25
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-06 14:26:29 +00:00
c87077f8b0 feat: add support for nexus npm registry
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 41s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m35s
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 14:24:05 +00:00
4af880a1f0 fix: use actions with node20 support
node24 is not available in latest stackit runner containers

Co-authored-by: Copilot <copilot@github.com>
2026-05-06 14:23:32 +00:00
5102827397
Merge pull request 'feat: add caching for pnpm store and Terraform providers' (#23) from feature/add-cache into main
Reviewed-on: #23
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-06 08:53:04 +00:00
aa4aa9131a
Merge pull request 'feat: enable manual triggering for Aikido Security Full Scan workflow' (#24) from chore/workflow-dispatch into main
Reviewed-on: #24
2026-05-05 13:55:07 +00:00
afed7e800e
Merge pull request 'feat: add output export for Terraform apply action' (#20) from feature/terraform-apply-outputs into main
Reviewed-on: #20
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-05 13:22:42 +00:00
bad1852f79
Merge pull request 'feat: add maven-build action' (#21) from feature/maven-build into main
Reviewed-on: #21
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-05 13:22:00 +00:00
b666024c65
Merge pull request 'feat: add helm-deploy action' (#22) from feature/helm-deploy into main
Reviewed-on: #22
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-05 13:21:23 +00:00
45794cdf82 feat: enable manual triggering for Aikido Security Full Scan workflow
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 44s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 54s
2026-05-05 13:08:43 +00:00
a8be9bf8dc feat: add maven-build action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 44s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 53s
2026-05-05 13:07:32 +00:00
4030dee789 feat: add caching for pnpm store and Terraform providers
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 44s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 56s
2026-05-05 13:06:35 +00:00
0aa9f4274d feat: add helm-deploy action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m0s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m9s
2026-05-05 12:57:56 +00:00
dd41de5246
Merge pull request 'feat: add inject-content action' (#19) from feature/inject-content into main
Reviewed-on: #19
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-05 12:56:09 +00:00
a0e6adf3db feat: add output export for Terraform apply action
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m2s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 31s
2026-05-05 12:32:00 +00:00
9ab5db7b61 feat: add inject-content action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 40s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 52s
2026-05-05 12:23:18 +00:00
5df90fed35
Merge pull request 'feat: add cache action' (#18) from feature/cache into main
Reviewed-on: #18
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-05 12:12:11 +00:00
1e68ce68c7 feat: add cache action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 54s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m5s
Co-authored-by: Copilot <copilot@github.com>
2026-05-05 06:04:14 +00:00
65b5f623c1
Merge pull request 'fix: downgrade checkout' (#17) from bugfix/checkout into main
Reviewed-on: #17
2026-05-04 14:29:17 +00:00
3097bd89e2 fix: downgrade checkout
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m17s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m31s
...to avoid usage of node24 - not supported from stackit right now
2026-05-04 14:27:04 +00:00
0e2a2306c0
Merge pull request 'fix: downgrade checkout' (#16) from bugfix/checkout into main
Reviewed-on: #16
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-04 13:51:28 +00:00
f6a9668f6e fix: downgrade checkout
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m26s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m43s
...to avoid usage of node24 - not supported from stackit right now
2026-05-04 13:34:39 +00:00
fe4a3e3735
Merge pull request 'XXX' (#15) from bugfix/aikido into main
Reviewed-on: #15
2026-05-04 12:59:16 +00:00
a9ea87cafe fix: reference internal actions as full qualified shared actions
Some checks failed
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m9s
Aikido Security PR Check / Aikido Security Scan (pull_request) Failing after 1m12s
2026-05-04 12:56:45 +00:00
3eaba16b67 XXX
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m42s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 2m0s
2026-05-04 12:45:53 +00:00
70f2da4381
Merge pull request 'feat: add aws-access-key-id and aws-secret-access-key inputs to aws-configure action' (#14) from feature/aws-configure-keys into main
Reviewed-on: #14
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-04 11:16:48 +00:00
7bdca13059 feat: add aws-access-key-id and aws-secret-access-key inputs to aws-configure action
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m6s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 2m32s
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 09:04:49 +00:00
73591365cf
Merge pull request 'feat: add tag-release workflow for manual major release tagging' (#13) from feature/tag-release into main
Reviewed-on: #13
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-04 08:21:02 +00:00
55121b1397
Merge pull request 'fix: update checkout action to use shared-actions version' (#12) from chore/checkout into main
Reviewed-on: #12
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-04 08:12:56 +00:00
c4b0b59780
Merge pull request 'feat: add terraform-apply action' (#11) from feature/terraform-apply into main
Reviewed-on: #11
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-04 08:11:55 +00:00
df3e62e9c4
Merge pull request 'chore: remove unused extensions' (#10) from chore/devcontainer into main
Reviewed-on: #10
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-05-04 08:11:19 +00:00
14bf6ad0da feat: add tag-release workflow for manual major release tagging
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m40s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m57s
2026-05-04 08:06:16 +00:00
7a45b38c62 fix: update checkout action to use shared-actions version
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m4s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m58s
2026-05-04 08:04:13 +00:00
f3878d443b feat: add terraform-apply action
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m25s
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 07:52:38 +00:00
c8eaf38f38
Merge pull request 'feat: add Aikido security workflows for full scan and PR check' (#7) from feature/aikido-actions into main
Reviewed-on: #7
2026-05-04 07:15:18 +00:00
5f542e7a12 chore: remove unused extensions
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m17s
2026-05-04 07:13:53 +00:00
2a5f1e2fe7 feat: add Aikido security workflows for full scan and PR check
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m20s
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 1m48s
2026-05-04 07:10:56 +00:00
34c06ff025
Merge pull request 'fix: move aikido internal actions into the shared actions to make it work' (#9) from bugfix/aikido-actions into main
Reviewed-on: #9
2026-05-04 07:01:49 +00:00
b009c23bb7 fix: move aikido internal actions into the shared actions to make it work
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 56s
2026-05-04 06:59:47 +00:00
092f0c75a8
Merge pull request 'feature/shared-actions' (#8) from feature/shared-actions into main
Reviewed-on: #8
2026-04-30 12:35:47 +00:00
d76b222340 fix: correct aikido references
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m1s
2026-04-30 14:30:34 +02:00
a51fbccdc1 feat: add terraform-validate action 2026-04-30 14:30:34 +02:00
a320b8a6f0
Merge pull request 'refactor: update AWS configuration action and README' (#6) from refactor/aws-configure into main
Reviewed-on: #6
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-04-30 12:03:03 +00:00
6f95a91a6c
Merge pull request 'chore: add devcontainer' (#5) from chore/devcontainer into main
Reviewed-on: #5
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-04-30 12:02:53 +00:00
ec92d4c539 feat: add publish-static-contents action 2026-04-30 14:02:09 +02:00
c9ed9789bb feat: add pnpm-build action 2026-04-30 14:02:09 +02:00
fdb8ffd777 refactor: update AWS configuration action and README
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m53s
2026-04-30 13:27:27 +02:00
dee0fc4bbb
Merge pull request 'feat: add Aikido full and PR scan actions' (#4) from feature/aikido into main
Reviewed-on: #4
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-04-30 11:24:03 +00:00
585c258dc1 chore: add devcontainer
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m8s
2026-04-30 13:23:40 +02:00
fc82729de8 fix: correct paths
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 1m7s
2026-04-30 13:17:44 +02:00
feaeeedd7a feat: add Aikido full and PR scan actions
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 13:17:44 +02:00
af89d0421c
Merge pull request 'feat: add checkout action' (#3) from feature/checkout into main
Reviewed-on: #3
Reviewed-by: Markus.Opahle@schmalz.de <Markus.Opahle@schmalz.de>
2026-04-30 08:17:31 +00:00
6bf775448b fix: update runner environment to stackit-ubuntu-22
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 2m24s
2026-04-30 10:13:34 +02:00
0cd6236a11 feat: add checkout action
Some checks failed
validate-shared-actions / validate-shared-actions (pull_request) Failing after 2s
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 10:09:54 +02:00
b461f99922
Merge pull request 'feature/aws-configure' (#2) from feature/aws-configure into main
Reviewed-on: #2
Reviewed-by: Michael.Seele@schmalz.de <Michael.Seele@schmalz.de>
2026-04-27 09:26:10 +00:00
6fb1549aea fix: use forgejo runner to validate actions
All checks were successful
validate-shared-actions / validate-shared-actions (pull_request) Successful in 37s
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 10:54:02 +02:00
5d6b2ce81b ci: add codeowners
Some checks failed
validate-shared-actions / validate-shared-actions (pull_request) Has been cancelled
2026-04-24 16:05:57 +02:00
7f2350f1d5 chore: add readme 2026-04-24 16:05:57 +02:00
1aaa12bcea feat: add aws configure action 2026-04-24 16:05:22 +02:00
f2522af427 ci: add validation workflow 2026-04-24 16:05:22 +02:00
74 changed files with 2303 additions and 2548 deletions

5
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM schmalz.jfrog.io/default-docker/db-devcontainer-base:1
# ===== ===== ===== ===== ===== =====
# Project specific setup
# ===== ===== ===== ===== ===== =====

View file

@ -0,0 +1,21 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"build": {
"dockerfile": "Dockerfile"
},
"mounts": [
"source=${localEnv:HOME}/.devcontainer/.config/,target=/home/ubuntu/.config/,type=bind,consistency=cached",
"source=${localEnv:HOME}/.devcontainer/.zshrc,target=/home/ubuntu/.zshrc_user,type=bind,consistency=cached",
"source=${localEnv:HOME}/.devcontainer/.commandhistory/,target=/commandhistory/,type=bind,consistency=cached",
"source=${localEnv:HOME}/.devcontainer/.aws/,target=/home/ubuntu/.aws/,type=bind,consistency=cached"
// Project specific mounts
],
"customizations": {
"vscode": {
"extensions": [
"amazonwebservices.aws-toolkit-vscode"
]
}
}
}

View file

@ -0,0 +1,19 @@
name: Aikido Security Full Scan
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
aikido-full-scan:
name: Aikido Security Full Scan
runs-on: stackit-ubuntu-22
steps:
- name: Checkout repository
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1
- name: Run Aikido full scan
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-full-scan@aikido-full-scan-v1
with:
apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }}

View file

@ -0,0 +1,21 @@
name: Aikido Security PR Check
on:
pull_request:
concurrency:
group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
cancel-in-progress: true
jobs:
aikido-security:
name: Aikido Security Scan
runs-on: stackit-ubuntu-22
steps:
- name: Checkout repository
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1
- name: Security scan
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-pr-scan@aikido-pr-scan-v1
with:
apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }}

View file

@ -0,0 +1,20 @@
name: validate-shared-actions
on:
pull_request:
permissions:
contents: read
jobs:
validate-shared-actions:
runs-on: stackit-ubuntu-22
steps:
- name: Checkout
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1
- name: Validate shared action metadata
uses: docker://data.forgejo.org/forgejo/runner:12
with:
entrypoint: /bin/sh
args: -ec "find . -mindepth 2 -maxdepth 2 -name action.yml -exec forgejo-runner validate --action --path {} \\;"

View file

@ -0,0 +1,89 @@
name: tag-release
# Manually create or move a major release tag for a shared action.
# Tag format: <action-name>-v<major> (e.g. checkout-v1, pnpm-build-v2)
#
# If the tag already exists it is force-moved to the selected ref.
on:
workflow_dispatch:
inputs:
action:
description: Action to release
required: true
type: choice
options:
- aikido-full-scan
- aikido-pr-scan
- aws-configure
- cache
- checkout
- download-artifact
- esb-deploy
- helm-deploy
- i18n-sync
- inject-content
- maven-build
- pnpm-build
- playwright-merge
- playwright-run
- publish-npm-package
- publish-rust-crate
- publish-static-contents
- rust-build
- terraform-apply
- terraform-validate
- upload-artifact
- vacuum-lint
major-version:
description: 'Major version number (e.g. 1)'
required: true
type: string
ref:
description: 'Branch, tag, or commit SHA to tag (defaults to the default branch)'
required: false
type: string
default: ''
permissions:
contents: write
jobs:
tag-release:
runs-on: stackit-ubuntu-22
steps:
- name: Checkout
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Validate major version
env:
MAJOR: ${{ inputs.major-version }}
run: |
if ! echo "$MAJOR" | grep -qE '^[0-9]+$'; then
echo "::error::major-version must be a positive integer, got: $MAJOR"
exit 1
fi
- name: Create or move major tag
env:
ACTION: ${{ inputs.action }}
MAJOR: ${{ inputs.major-version }}
run: |
set -euo pipefail
TAG="${ACTION}-v${MAJOR}"
COMMIT=$(git rev-parse HEAD)
echo "Tag : $TAG"
echo "Commit : $COMMIT"
git config user.name "forgejo-actions[bot]"
git config user.email "forgejo-actions[bot]@noreply.forgejo.org"
git tag -f "$TAG" "$COMMIT"
git push origin -f "refs/tags/$TAG"
echo "::notice::Tag $TAG now points to $COMMIT"

View file

@ -1,23 +0,0 @@
name: validate-shared-actions
on:
pull_request:
types: [opened, reopened, synchronize]
permissions:
contents: read
jobs:
validate-shared-actions:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate shared action metadata
uses: mpalmer/action-validator@v0.9.0
with:
version: 0.9.0
patterns: |
:(glob)**/action.yml
:(glob)**/action.yaml

File diff suppressed because it is too large Load diff

1
CODEOWNERS Normal file
View file

@ -0,0 +1 @@
.* @schmalz/Developers

View file

@ -1,38 +1,45 @@
# shared-actions
Shared composite actions for Forgejo CI/CD pipelines.
Shared actions for Forgejo CI/CD pipelines.
## Actions
| Action | Description |
|--------|-------------|
| [aikido-full-scan](.github/actions/aikido-full-scan) | Run a full Aikido security scan (for nightly/scheduled runs) |
| [aikido-pr-scan](.github/actions/aikido-pr-scan) | Run Aikido security scan on a PR in gating mode (fails on new vulnerabilities) |
| [aws-configure](.github/actions/aws-configure) | Authenticate with AWS via OIDC |
| [aws-lambda-update](.github/actions/aws-lambda-update) | Update Lambda function alias to a new version, optionally wait for provisioned concurrency |
| [aws-s3-sync](.github/actions/aws-s3-sync) | Sync build artifacts to S3, clean up old versioned assets, optionally invalidate CloudFront |
| [cloudfront-invalidate](.github/actions/cloudfront-invalidate) | Invalidate one or more CloudFront distributions |
| [docker-build-push](.github/actions/docker-build-push) | Build Docker image and push to JFrog with semver tags (major, minor, patch) |
| [helm-deploy](.github/actions/helm-deploy) | Deploy a service to Kubernetes via Helm over SSH |
| [maven-build](.github/actions/maven-build) | Run Maven build — verify-only (PRs) or package+jib push (deploy) |
| [playwright-e2e](.github/actions/playwright-e2e) | Run Playwright E2E tests with optional sharding, upload results to S3 |
| [pnpm-build](.github/actions/pnpm-build) | Set up pnpm, authenticate JFrog npm registry, install deps, run scripts |
| [publish-npm-package](.github/actions/publish-npm-package) | Build and publish npm package to JFrog Artifactory |
| [publish-rust-crate](.github/actions/publish-rust-crate) | Build, test, and publish Rust crate to JFrog Cargo registry |
| [rust-build](.github/actions/rust-build) | Run Rust CI — fmt, clippy, tests, optional cross-compilation |
| [secrets-inject](.github/actions/secrets-inject) | Append a secrets file to a Java .properties file |
| [terraform-apply](.github/actions/terraform-apply) | Full Terraform init + workspace + apply + output capture |
| [terraform-module-publish](.github/actions/terraform-module-publish) | Zip a Terraform module and publish to JFrog Artifactory |
| [terraform-validate](.github/actions/terraform-validate) | Validate Terraform code without backend (for PR checks) |
| [aikido-full-scan](aikido-full-scan) | Aikido full scan |
| [aikido-pr-scan](aikido-pr-scan) | Aikido PR scan |
| [aws-configure](aws-configure) | Authenticate with AWS via OIDC |
| [cache](cache) | Cache files between workflow runs |
| [checkout](checkout) | Action for checking out a repository |
| [download-artifact](download-artifact) | Download Forgejo Actions artifacts by name or pattern |
| [helm-deploy](helm-deploy) | Deploy a service to Kubernetes via Helm over SSH |
| [i18n-sync](i18n-sync) | Fetch translations from i18n.schmalz.com and open a pull request |
| [inject-content](inject-content) | Inject content into a file by appending or overwriting |
| [maven-build](maven-build) | Action for building and validating Maven projects |
| [pnpm-build](pnpm-build) | Action for building and validating with PNPM |
| [playwright-merge](playwright-merge) | Merge Playwright shard blob reports and publish consolidated reports |
| [playwright-run](playwright-run) | Run Playwright tests for one shard and upload its blob report |
| [publish-npm-package](publish-npm-package) | Publish a PNPM package to JFrog Artifactory |
| [publish-rust-crate](publish-rust-crate) | Publish a Rust crate to JFrog Artifactory |
| [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution |
| [rust-build](rust-build) | Set up Rust toolchain, run checks, and build via the project's build.sh |
| [terraform-apply](terraform-apply) | Apply Terraform configuration files using the official Terraform CLI |
| [terraform-validate](terraform-validate) | Validate Terraform configuration files using the official Terraform CLI |
| [upload-artifact](upload-artifact) | Upload files as a Forgejo Actions artifact |
| [vacuum-lint](vacuum-lint) | Validate and lint OpenAPI specifications using Vacuum |
## Security
Where third-party Forgejo/GitHub Actions are used internally, they are pinned to exact commit hashes rather than mutable tags to prevent supply chain attacks.
## Usage
Reference actions from your project's workflow:
```yaml
- uses: schmalz/shared-actions/.github/actions/<action-name>@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/<action-name>@<action-name>-v1
with:
# see each action's README for inputs
```
Each action has its own README with inputs, usage examples, and notes.
Each action has its own README with inputs, usage examples, and notes.

View file

@ -1,44 +1,21 @@
# aikido-full-scan
Run a full Aikido security scan — intended for nightly or scheduled runs.
Composite wrapper around the Aikido full-release Docker scan. Automatically resolves repository and branch info from the Forgejo context — only the API key needs to be supplied by the caller.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `aikido-api-key` | Yes | | Aikido CI API key (`AIK_CI_xxx`) |
| `branch-name` | Yes | | Branch to scan (`dev` for applications, `main` for libraries) |
| `apikey` | Yes | — | Aikido CI API key |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aikido-full-scan@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-full-scan@aikido-full-scan-v1
with:
aikido-api-key: ${{ secrets.AIKIDO_API_KEY }}
branch-name: dev
```
### Nightly schedule example
```yaml
on:
schedule:
- cron: '0 2 * * *'
jobs:
aikido-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: schmalz/shared-actions/.github/actions/aikido-full-scan@v1
with:
aikido-api-key: ${{ secrets.AIKIDO_API_KEY }}
branch-name: dev
apikey: ${{ secrets.AIKIDO_API_KEY }}
```
## Notes
- Uses the `aikidosecurity/local-scanner` Docker image to scan inside the CI runner.
- Performs a full scan (all scan types: code, dependencies, IaC, secrets) and uploads results to the Aikido dashboard.
- Repository name is auto-detected from Forgejo environment variables.
- Does not run in gating mode — the scan reports findings but does not fail the workflow unless the scanner itself errors.
- Delegates to `actions/internal-aikido-full-scan` with organization, repository name, and branch name resolved automatically from the Forgejo context.

View file

@ -1,29 +1,26 @@
name: aikido-full-scan
description: Run a full Aikido security scan (for nightly/scheduled runs)
name: Aikido Security Full Scan
description: >
Composite wrapper around the Aikido full-release Docker scan.
Automatically resolves repository and branch info from the forgejo context.
Only the API key needs to be supplied by the caller.
inputs:
aikido-api-key:
description: 'Aikido CI API key (AIK_CI_xxx)'
required: true
branch-name:
description: 'Branch to scan (e.g., dev for applications, main for libraries)'
apikey:
description: Aikido CI API key
required: true
runs:
using: composite
steps:
- name: Aikido full scan
- name: Normalize repository name
id: repo
shell: bash
env:
AIKIDO_API_KEY: ${{ inputs.aikido-api-key }}
INPUT_BRANCH_NAME: ${{ inputs.branch-name }}
run: |
REPO_NAME="${GITHUB_REPOSITORY##*/}"
repo="${{ forgejo.repository }}"
echo "name=${repo#/}" >> $GITHUB_OUTPUT
docker run --rm \
-v "$GITHUB_WORKSPACE:/repo" \
-e AIKIDO_API_KEY \
aikidosecurity/local-scanner:latest \
scan /repo \
--repositoryname "$REPO_NAME" \
--branchname "$INPUT_BRANCH_NAME"
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-full-scan@internal-aikido-full-scan-v1
with:
apikey: ${{ inputs.apikey }}
repository-name: ${{ steps.repo.outputs.name }}
branch-name: ${{ forgejo.ref_name }}

View file

@ -1,26 +1,23 @@
# aikido-pr-scan
Run Aikido security scan on a PR in gating mode — fails if new vulnerabilities are introduced.
Composite wrapper around the Aikido PR Docker scan. Automatically resolves repository, branch, and commit info from the Forgejo context — only the API key needs to be supplied by the caller.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `aikido-api-key` | Yes | | Aikido CI API key (`AIK_CI_xxx`) |
| `fail-on` | No | `high` | Minimum severity to fail on (`low`, `medium`, `high`, `critical`) |
| `apikey` | Yes | | Aikido CI API key |
| `fail-on` | No | `high` | Minimum severity to fail on: `low`, `medium`, `high`, `critical` |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aikido-pr-scan@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-pr-scan@aikido-pr-scan-v1
with:
aikido-api-key: ${{ secrets.AIKIDO_API_KEY }}
apikey: ${{ secrets.AIKIDO_API_KEY }}
fail-on: high
```
## Notes
- Uses the `aikidosecurity/local-scanner` Docker image to scan inside the CI runner.
- Runs in PR gating mode: only detects **new** vulnerabilities introduced by the PR (diff-based).
- Repository name and branch are auto-detected from Forgejo environment variables.
- The API key is passed via environment variable, never interpolated in shell commands.
- Base and head commit IDs are derived from `GITHUB_BASE_SHA` and `GITHUB_SHA`.
- Delegates to `actions/internal-aikido-pr-scan` with organization, repository name, branch name, and base/head commit SHAs resolved automatically from the Forgejo context.

View file

@ -1,37 +1,27 @@
name: aikido-pr-scan
description: Run Aikido security scan on a PR in gating mode (fails on new vulnerabilities)
name: Aikido Security PR Scan
description: >
Composite wrapper around the Aikido PR Docker scan.
Automatically resolves repository, branch, and commit info from the forgejo context.
Only the API key needs to be supplied by the caller.
inputs:
aikido-api-key:
description: 'Aikido CI API key (AIK_CI_xxx)'
apikey:
description: Aikido CI API key
required: true
fail-on:
description: 'Minimum severity to fail on (low, medium, high, critical)'
description: 'Minimum severity to fail on: low, medium, high, critical'
default: high
required: false
default: 'high'
runs:
using: composite
steps:
- name: Aikido PR scan
shell: bash
env:
AIKIDO_API_KEY: ${{ inputs.aikido-api-key }}
INPUT_FAIL_ON: ${{ inputs.fail-on }}
run: |
REPO_NAME="${GITHUB_REPOSITORY##*/}"
BRANCH_NAME="${GITHUB_HEAD_REF}"
BASE_COMMIT="${GITHUB_BASE_SHA:-$(git rev-parse HEAD~1)}"
HEAD_COMMIT="${GITHUB_SHA}"
docker run --rm \
-v "$GITHUB_WORKSPACE:/repo" \
-e AIKIDO_API_KEY \
aikidosecurity/local-scanner:latest \
scan /repo \
--repositoryname "$REPO_NAME" \
--branchname "$BRANCH_NAME" \
--gating-mode pr \
--fail-on "$INPUT_FAIL_ON" \
--base-commit-id "$BASE_COMMIT" \
--head-commit-id "$HEAD_COMMIT"
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-pr-scan@internal-aikido-pr-scan-v1
with:
apikey: ${{ inputs.apikey }}
organization: ${{ forgejo.repository_owner }}
repository-name: ${{ forgejo.event.repository.name }}
branch-name: ${{ forgejo.head_ref }}
base-commit-id: ${{ forgejo.event.pull_request.base.sha }}
head-commit-id: ${{ forgejo.event.pull_request.head.sha }}
fail-on: ${{ inputs.fail-on }}

View file

@ -7,13 +7,14 @@ Authenticate with AWS via OIDC and export credentials to the environment.
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `role-arn` | Yes | | Full IAM role ARN |
| `aws-profile` | No | `default` | Profile name written to `~/.aws/config` |
| `region` | No | `eu-central-1` | AWS region |
| `aws-access-key-id` | No | | AWS access key to use. Only required for some authentication types. |
| `aws-secret-access-key` | No | | AWS secret key to use. Only required for some authentication types. |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aws-configure@aws-configure-v1
with:
role-arn: arn:aws:iam::123456789012:role/my-role
```

View file

@ -1,45 +1,30 @@
name: aws-configure
description: Authenticate with AWS via OIDC
description: Authenticate with AWS via OIDC and export credentials to the environment.
inputs:
role-arn:
description: Full IAM role ARN
description: Full IAM role ARN to assume via OIDC
required: true
aws-profile:
description: Profile name written to ~/.aws/config
required: false
default: default
region:
description: AWS region
required: false
default: eu-central-1
aws-access-key-id:
description: AWS access key to use. Only required for some authentication types.
required: false
aws-secret-access-key:
description: AWS secret key to use. Only required for some authentication types.
required: false
runs:
using: composite
steps:
- run: |
OIDC_TOKEN=$(curl -sf \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r .value)
CREDS=$(aws sts assume-role-with-web-identity \
--role-arn "$INPUT_ROLE_ARN" \
--role-session-name forgejo-ci \
--web-identity-token "$OIDC_TOKEN" \
--region "$INPUT_REGION" \
--query 'Credentials' --output json)
mkdir -p ~/.aws
echo "AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .AccessKeyId)" >> $FORGEJO_ENV
echo "AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .SecretAccessKey)" >> $FORGEJO_ENV
echo "AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .SessionToken)" >> $FORGEJO_ENV
echo "AWS_DEFAULT_REGION=$INPUT_REGION" >> $FORGEJO_ENV
if [ "$INPUT_AWS_PROFILE" != "default" ]; then
aws configure set aws_access_key_id "$(echo $CREDS | jq -r .AccessKeyId)" --profile "$INPUT_AWS_PROFILE"
aws configure set aws_secret_access_key "$(echo $CREDS | jq -r .SecretAccessKey)" --profile "$INPUT_AWS_PROFILE"
aws configure set aws_session_token "$(echo $CREDS | jq -r .SessionToken)" --profile "$INPUT_AWS_PROFILE"
aws configure set region "$INPUT_REGION" --profile "$INPUT_AWS_PROFILE"
fi
shell: bash
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# aws-actions/configure-aws-credentials v6.1.0 — https://code.forgejo.org/aws-actions/configure-aws-credentials/commits/tag/v6
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37
with:
role-to-assume: ${{ inputs.role-arn }}
aws-region: ${{ inputs.region }}
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}

View file

@ -1,50 +0,0 @@
# aws-lambda-update
Update Lambda function alias to a new version, optionally wait for provisioned concurrency.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `function-name` | Yes | | Lambda function name |
| `function-version` | Yes | | Lambda version number |
| `alias-name` | Yes | | Alias name |
| `aws-role-arn` | Yes | | IAM role via OIDC |
| `wait-provisioned-concurrency` | No | `false` | Poll until provisioned concurrency is READY |
| `aws-profile` | No | `default` | AWS CLI profile name |
| `region` | No | `eu-central-1` | AWS region |
| `lambda-alias-updates-json` | No | `""` | JSON array of `{function_name, version, alias_name}` objects for batch updates |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-lambda-update@v1
with:
function-name: my-function
function-version: "42"
alias-name: live
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
wait-provisioned-concurrency: "true"
```
### Batch update
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-lambda-update@v1
with:
function-name: unused
function-version: "0"
alias-name: unused
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
lambda-alias-updates-json: |
[
{"function_name": "fn-a", "version": "3", "alias_name": "live"},
{"function_name": "fn-b", "version": "7", "alias_name": "live"}
]
```
## Notes
- When `lambda-alias-updates-json` is set, the single-alias inputs (`function-name`, `function-version`, `alias-name`) are ignored.
- Provisioned concurrency polling checks every 5 seconds and fails the step if status becomes `FAILED`.
- Uses `aws-configure` internally for OIDC authentication.

View file

@ -1,95 +0,0 @@
name: aws-lambda-update
description: Update Lambda function alias to a new version, optionally wait for provisioned concurrency
inputs:
function-name:
description: Lambda function name
required: true
function-version:
description: Lambda version number
required: true
alias-name:
description: Alias name
required: true
wait-provisioned-concurrency:
description: Poll until provisioned concurrency is READY
required: false
default: "false"
aws-role-arn:
description: IAM role via OIDC
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: default
region:
description: AWS region
required: false
default: eu-central-1
lambda-alias-updates-json:
description: JSON array of {function_name, version, alias_name} objects; when set, applies all entries instead of single alias
required: false
default: ""
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
region: ${{ inputs.region }}
- run: |
AWS_PROFILE="${{ inputs.aws-profile }}"
WAIT_PC="${{ inputs.wait-provisioned-concurrency }}"
update_alias() {
local fn="$1" ver="$2" alias="$3"
echo "Updating alias '$alias' on '$fn' to version $ver"
aws lambda update-alias \
--function-name "$fn" \
--name "$alias" \
--function-version "$ver" \
--profile "$AWS_PROFILE"
}
wait_provisioned_concurrency() {
local fn="$1" alias="$2"
echo "Waiting for provisioned concurrency on '$fn' alias '$alias'..."
while true; do
STATUS=$(aws lambda get-provisioned-concurrency-config \
--function-name "$fn" \
--qualifier "$alias" \
--profile "$AWS_PROFILE" \
--query 'Status' --output text 2>/dev/null || echo "NOT_FOUND")
echo " Status: $STATUS"
if [ "$STATUS" = "READY" ]; then
break
elif [ "$STATUS" = "FAILED" ]; then
echo "ERROR: Provisioned concurrency failed for '$fn' alias '$alias'"
exit 1
fi
sleep 5
done
}
UPDATES_JSON='${{ inputs.lambda-alias-updates-json }}'
if [ -n "$UPDATES_JSON" ]; then
echo "$UPDATES_JSON" | jq -c '.[]' | while read -r entry; do
FN=$(echo "$entry" | jq -r '.function_name')
VER=$(echo "$entry" | jq -r '.version')
ALIAS=$(echo "$entry" | jq -r '.alias_name')
update_alias "$FN" "$VER" "$ALIAS"
if [ "$WAIT_PC" = "true" ]; then
wait_provisioned_concurrency "$FN" "$ALIAS"
fi
done
else
update_alias "${{ inputs.function-name }}" "${{ inputs.function-version }}" "${{ inputs.alias-name }}"
if [ "$WAIT_PC" = "true" ]; then
wait_provisioned_concurrency "${{ inputs.function-name }}" "${{ inputs.alias-name }}"
fi
fi
shell: bash

View file

@ -1,31 +0,0 @@
# aws-s3-sync
Sync build artifacts to S3, clean up old versioned assets, and optionally invalidate CloudFront.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `source-dir` | Yes | | Local path to sync |
| `s3-bucket` | Yes | | Target S3 bucket name |
| `aws-role-arn` | Yes | | IAM role ARN for OIDC authentication |
| `cache-control` | No | `public, max-age=31536000, immutable` | HTTP `Cache-Control` header |
| `aws-profile` | No | `default` | AWS CLI profile name |
| `cloudfront-distribution-ids-file` | No | `""` | Path to file with space-separated CloudFront distribution IDs |
| `retention-days` | No | `7` | Delete `_static/<timestamp>` prefixes older than this many days (`0` to skip) |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-s3-sync@v1
with:
source-dir: dist/
s3-bucket: my-app-bucket
aws-role-arn: arn:aws:iam::123456789012:role/my-role
```
## Notes
- Requires `enable-openid-connect: true` on the job.
- Old assets under `_static/` are cleaned up based on `retention-days` by parsing timestamp-named prefixes.
- CloudFront invalidation uses `/*` path and is triggered only when `cloudfront-distribution-ids-file` points to an existing file.

View file

@ -1,71 +0,0 @@
name: aws-s3-sync
description: Sync build artifacts to S3, clean up old versioned assets, optionally invalidate CloudFront
inputs:
source-dir:
description: Local path to sync
required: true
s3-bucket:
description: Target S3 bucket name
required: true
cache-control:
description: HTTP Cache-Control header
required: false
default: "public, max-age=31536000, immutable"
aws-role-arn:
description: IAM role via OIDC
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: default
cloudfront-distribution-ids-file:
description: Path to file with space-separated CF distribution IDs. If set, creates invalidations.
required: false
default: ""
retention-days:
description: Delete versioned _static/<timestamp> prefixes older than this many days (0 = skip)
required: false
default: "7"
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- run: |
SOURCE_DIR="${{ inputs.source-dir }}"
S3_BUCKET="${{ inputs.s3-bucket }}"
CACHE_CONTROL="${{ inputs.cache-control }}"
AWS_PROFILE="${{ inputs.aws-profile }}"
RETENTION_DAYS="${{ inputs.retention-days }}"
CF_IDS_FILE="${{ inputs.cloudfront-distribution-ids-file }}"
# Sync
aws s3 sync "$SOURCE_DIR" "s3://$S3_BUCKET" \
--cache-control "$CACHE_CONTROL" \
--profile "$AWS_PROFILE"
# Cleanup old _static/ prefixes if retention-days > 0
if [ "$RETENTION_DAYS" -gt 0 ]; then
CUTOFF=$(date -d "-${RETENTION_DAYS} days" +%s)
aws s3 ls "s3://$S3_BUCKET/_static/" --profile "$AWS_PROFILE" | while read -r line; do
PREFIX=$(echo "$line" | awk '{print $2}')
# Extract timestamp from prefix name, compare to cutoff, delete if old
TIMESTAMP=$(echo "$PREFIX" | tr -d '/')
if [ $(date -d "$TIMESTAMP" +%s 2>/dev/null || echo 0) -lt "$CUTOFF" ]; then
aws s3 rm "s3://$S3_BUCKET/_static/$PREFIX" --recursive --profile "$AWS_PROFILE"
fi
done
fi
# CloudFront invalidation
if [ -n "$CF_IDS_FILE" ] && [ -f "$CF_IDS_FILE" ]; then
for DIST_ID in $(cat "$CF_IDS_FILE"); do
aws cloudfront create-invalidation --distribution-id "$DIST_ID" --paths "/*" --profile "$AWS_PROFILE"
done
fi
shell: bash

51
cache/README.md vendored Normal file
View file

@ -0,0 +1,51 @@
# cache
Composite wrapper around actions/cache pinned to a specific commit SHA to prevent supply chain attacks via tag or branch hijacking.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `path` | Yes | — | List of files, directories, and wildcard patterns to cache and restore |
| `key` | Yes | — | An explicit key for saving and restoring the cache |
| `restore-keys` | No | `''` | Ordered multiline string of prefix-matched keys used for restoring stale cache |
| `upload-chunk-size` | No | `''` | Chunk size in bytes used to split large files during upload |
| `enableCrossOsArchive` | No | `false` | Allow caches saved on one OS to be restored on another |
| `fail-on-cache-miss` | No | `false` | Fail the workflow if no cache entry is found |
| `lookup-only` | No | `false` | Check if a cache entry exists without downloading it |
## Outputs
| Output | Description |
|--------|-------------|
| `cache-hit` | `true` if an exact match was found for the primary key |
## Usage
```yaml
- name: Cache pnpm store
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.local/share/pnpm/store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
```
```yaml
- name: Cache node_modules
id: node-modules-cache
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
run: npm ci
```
## Notes
- Pinned to `actions/cache` commit SHA `0057852b` (v4.3.0) to prevent supply chain attacks via tag or branch hijacking.
- Upstream action: [code.forgejo.org/actions/cache](https://code.forgejo.org/actions/cache).

54
cache/action.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: Schmalz Cache
description: >
Composite wrapper around actions/cache pinned to a specific commit SHA
to prevent supply chain attacks via tag or branch hijacking.
inputs:
path:
description: A list of files, directories, and wildcard patterns to cache and restore.
required: true
key:
description: An explicit key for saving and restoring the cache.
required: true
restore-keys:
description: An ordered multiline string listing prefix-matched keys used for restoring stale cache if no cache hit occurred for key.
required: false
default: ''
upload-chunk-size:
description: The chunk size used to split up large files during upload, in bytes.
required: false
default: ''
enableCrossOsArchive:
description: When enabled, allows Windows runners to save or restore caches that can be used on other platforms.
required: false
default: 'false'
fail-on-cache-miss:
description: Fail the workflow if cache entry is not found.
required: false
default: 'false'
lookup-only:
description: Check if a cache entry exists for the given input(s) without downloading the cache.
required: false
default: 'false'
outputs:
cache-hit:
description: A boolean value to indicate an exact match was found for the primary key.
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/cache v4.3.0 — https://code.forgejo.org/actions/cache/commits/tag/v4.3.0
- name: Cache
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
restore-keys: ${{ inputs.restore-keys }}
upload-chunk-size: ${{ inputs.upload-chunk-size }}
enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }}
fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }}
lookup-only: ${{ inputs.lookup-only }}

24
checkout/README.md Normal file
View file

@ -0,0 +1,24 @@
# checkout
Composite wrapper around actions/checkout pinned to a specific commit SHA to prevent supply chain attacks via tag or branch hijacking.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `ref` | No | `''` | Branch, tag, or SHA to checkout |
| `repository` | No | `${{ github.repository }}` | Repository name with owner |
| `token` | No | `${{ github.token }}` | Personal access token to fetch the repository |
| `path` | No | `''` | Relative path under `$GITHUB_WORKSPACE` to place the repository |
| `fetch-depth` | No | `1` | Number of commits to fetch. `0` fetches all history |
| `submodules` | No | `false` | Whether to checkout submodules (`true`, `false`, or `recursive`) |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1
```
## Notes
- Pinned to `actions/checkout` commit SHA `de0fac2e` (v6.0.2) to prevent supply chain attacks via tag or branch hijacking.

45
checkout/action.yml Normal file
View file

@ -0,0 +1,45 @@
name: Schmalz Checkout
description: >
Composite wrapper around actions/checkout pinned to a specific commit SHA
to prevent supply chain attacks via tag or branch hijacking.
inputs:
ref:
description: The branch, tag, or SHA to checkout. Defaults to the ref that triggered the workflow.
required: false
default: ''
repository:
description: Repository name with owner (e.g. actions/checkout). Defaults to the current repository.
required: false
default: ${{ github.repository }}
token:
description: Personal access token used to fetch the repository.
required: false
default: ${{ github.token }}
path:
description: Relative path under $GITHUB_WORKSPACE to place the repository.
required: false
default: ''
fetch-depth:
description: Number of commits to fetch. 0 fetches all history.
required: false
default: '1'
submodules:
description: Whether to checkout submodules. true, false, or recursive.
required: false
default: 'false'
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/checkout v4.3.1 — https://code.forgejo.org/actions/checkout/commits/tag/v4.3.1
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ inputs.ref }}
repository: ${{ inputs.repository }}
token: ${{ inputs.token }}
path: ${{ inputs.path }}
fetch-depth: ${{ inputs.fetch-depth }}
submodules: ${{ inputs.submodules }}

View file

@ -1,27 +0,0 @@
# cloudfront-invalidate
Invalidate one or more CloudFront distributions.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `distribution-ids` | Yes | | Space-separated CloudFront distribution IDs, or path to a file containing them |
| `aws-role-arn` | Yes | | IAM role via OIDC |
| `paths` | No | `/*` | Invalidation paths |
| `aws-profile` | No | `default` | AWS CLI profile name |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/cloudfront-invalidate@v1
with:
distribution-ids: E1234567890ABC E0987654321XYZ
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
```
## Notes
- `distribution-ids` can be literal IDs or a path to a file containing them (one per line or space-separated).
- Each distribution is invalidated separately in a loop.
- Uses `aws-configure` internally for OIDC authentication.

View file

@ -1,46 +0,0 @@
name: cloudfront-invalidate
description: Invalidate one or more CloudFront distributions
inputs:
distribution-ids:
description: Space-separated CloudFront distribution IDs, or path to file containing them
required: true
paths:
description: Invalidation paths
required: false
default: "/*"
aws-role-arn:
description: IAM role via OIDC
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: default
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- run: |
DISTRIBUTION_IDS="${{ inputs.distribution-ids }}"
PATHS="${{ inputs.paths }}"
AWS_PROFILE="${{ inputs.aws-profile }}"
# Check if distribution-ids is a file path or literal IDs
if [ -f "$DISTRIBUTION_IDS" ]; then
IDS=$(cat "$DISTRIBUTION_IDS")
else
IDS="$DISTRIBUTION_IDS"
fi
for DIST_ID in $IDS; do
aws cloudfront create-invalidation \
--distribution-id "$DIST_ID" \
--paths "$PATHS" \
--profile "$AWS_PROFILE"
done
shell: bash

View file

@ -1,30 +0,0 @@
# docker-build-push
Build Docker image and push to JFrog with semver tags.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `image-name` | Yes | | Image name (e.g., `default-docker/db-devcontainer-base`) |
| `version-tag` | Yes | | Full semver tag (e.g., `1.2.3`) |
| `jfrog-token` | Yes | | JFrog registry password/token |
| `registry` | No | `schmalz.jfrog.io` | Docker registry |
| `dockerfile` | No | `Dockerfile` | Path to Dockerfile |
| `context` | No | `.` | Docker build context |
| `jfrog-user` | No | `jfrog-cicd-user` | JFrog registry user |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/docker-build-push@v1
with:
image-name: default-docker/my-service
version-tag: 1.2.3
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Pushes three tags per build: full (`1.2.3`), minor (`1.2`), and major (`1`).
- Consumers using the major or minor tag always get the latest patch release.

View file

@ -1,46 +0,0 @@
name: docker-build-push
description: Build Docker image and push to JFrog with semver tags (major, minor, patch)
inputs:
image-name:
description: 'Image name (e.g., default-docker/db-devcontainer-base)'
required: true
registry:
description: 'Docker registry'
required: false
default: 'schmalz.jfrog.io'
dockerfile:
description: 'Path to Dockerfile'
required: false
default: 'Dockerfile'
context:
description: 'Docker build context'
required: false
default: '.'
version-tag:
description: 'Full semver tag (e.g., 1.2.3)'
required: true
jfrog-token:
description: 'JFrog registry password/token'
required: true
jfrog-user:
description: 'JFrog registry user'
required: false
default: 'jfrog-cicd-user'
runs:
using: composite
steps:
- name: Build and push Docker image
shell: bash
run: |
echo "${{ inputs.jfrog-token }}" | docker login "${{ inputs.registry }}" -u "${{ inputs.jfrog-user }}" --password-stdin
FULL="${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.version-tag }}"
MINOR="${{ inputs.registry }}/${{ inputs.image-name }}:$(echo "${{ inputs.version-tag }}" | cut -d. -f1,2)"
MAJOR="${{ inputs.registry }}/${{ inputs.image-name }}:$(echo "${{ inputs.version-tag }}" | cut -d. -f1)"
docker build -f "${{ inputs.dockerfile }}" -t "$FULL" -t "$MINOR" -t "$MAJOR" "${{ inputs.context }}"
docker push "$FULL"
docker push "$MINOR"
docker push "$MAJOR"

View file

@ -0,0 +1,46 @@
# download-artifact
Download Forgejo Actions artifacts by name or pattern. Thin wrapper around `forgejo/download-artifact` pinned to a specific commit SHA to prevent supply chain attacks.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `name` | No | `""` | Exact artifact name or glob pattern (e.g. `blob-report-*`). If omitted, all artifacts for the run are downloaded. |
| `path` | No | `.` | Local destination directory |
| `merge-multiple` | No | `false` | When true, merge all matched artifacts into a single directory |
## Usage
Download a single artifact by name:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
name: my-artifact
path: dist/
```
Download all artifacts matching a pattern and merge into one directory:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
name: blob-report-*
path: all-blob-reports/
merge-multiple: "true"
```
Download all artifacts for the run:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
path: artifacts/
```
## Notes
- Wraps `forgejo/download-artifact` v4 (node20), compatible with Ubuntu 22 runners.
- The underlying action is pinned to a commit SHA rather than a mutable tag to prevent supply chain attacks.
- When `merge-multiple` is false (default), each matched artifact is extracted into its own subdirectory under `path`.

View file

@ -0,0 +1,30 @@
name: Schmalz Download Artifact
description: >
Download Forgejo Actions artifacts by name or pattern.
Thin wrapper around forgejo/download-artifact, pinned to a specific SHA.
inputs:
name:
description: Exact artifact name or glob pattern (e.g. 'blob-report-*'). If omitted, all artifacts for the run are downloaded.
required: false
default: ""
path:
description: Local destination directory
required: false
default: "."
merge-multiple:
description: When true, merge all matched artifacts into a single directory
required: false
default: "false"
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# forgejo/download-artifact v4 — https://code.forgejo.org/forgejo/download-artifact/commits/tag/v4
- name: Download artifact
uses: https://code.forgejo.org/forgejo/download-artifact@d8d0a99033603453ad2255e58720b460a0555e1e
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
merge-multiple: ${{ inputs.merge-multiple }}

32
esb-deploy/README.md Normal file
View file

@ -0,0 +1,32 @@
# esb-deploy
Deploy a service to an ESB docker host.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `docker-host` | Yes | - | esbdb3.schmalzgroup.net, esbdb4.schmalzgroup.net, esbdb2-stage.schmalzgroup.net|
| `java-version` | Yes | 25 | Same as default of the maven-build action |
| `maven-profile` | No | `test` | Maven profile to activate during deploy |
| `maven-settings` | **Yes** | — | Secret containing the `settings.xml` content used for repository authentication |
| `service` | Yes | — | Name of the service to deploy |
| `stage` | No | true | If true this is a stage deployment |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/esb-deploy@esb-deploy-v1
with:
service: my-service
docker-host: esbdocker2-stage.schmalzgroup.net
java-version: 8
maven-profile: test
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
stage: true
```
## Notes
- The compose files are extracted from variables. They can be provided on the organization or repository level.
- The action uses the maven-build action to build the service. The pom.xml has to be in the root directory

64
esb-deploy/action.yml Normal file
View file

@ -0,0 +1,64 @@
name: Deploy ESB
description: Deploy a service to an ESB docker host.
inputs:
docker-host:
description: Docker host to deploy to
required: true
maven-profile:
required: false
default: 'test'
description: 'Maven profile to use for the build'
maven-settings:
description: Secret containing the settings.xml content used for repository authentication
required: true
java-version:
description: Java version to use for the build
required: true
service:
description: Name of the service to deploy
required: true
stage:
description: Whether to deploy to stage environment (true) or production environment (false)
required: false
default: 'true'
runs:
using: composite
steps:
- name: Create compose files
shell: bash
env:
BASE_COMPOSE: ${{ vars.DOCKER_COMPOSE }}
SU_COMPOSE: ${{ vars.DOCKER_COMPOSE_SU }}
run: |
printf '%s\n' "$BASE_COMPOSE" > compose.yml
printf '%s\n' "$SU_COMPOSE" > compose-su.yml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
phase: verify
maven-settings: ${{ inputs.maven-settings }}
verify-goals: clean package
java-version: ${{ inputs.java-version }}
maven-profile: ${{ inputs.maven-profile}}
- name: Compose stage
if: ${{ inputs.stage == 'true' }}
shell: bash
env:
SERVICE: ${{ inputs.service }}
run: |
echo "Deploying $SERVICE to stage environment"
export DOCKER_HOST="tcp://${{ inputs.docker-host }}:2375"
docker compose -f compose.yml -f compose-su.yml up -d --build --no-deps "$SERVICE"
- name: Compose prod
if: ${{ inputs.stage != 'true' }}
shell: bash
env:
SERVICE: ${{ inputs.service }}
run: |
echo "Deploying $SERVICE to production environment"
export DOCKER_HOST="tcp://${{ inputs.docker-host }}:2375"
docker compose -f compose.yml up -d --build --no-deps "$SERVICE"

View file

@ -6,28 +6,29 @@ Deploy a service to Kubernetes via Helm over SSH.
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `service-name` | Yes | | Helm release name |
| `helm-host` | Yes | | SSH target (e.g., `dsp1-stage.schmalzgroup.net`) |
| `image-tag` | Yes | | Docker image tag to deploy |
| `ssh-key` | Yes | | Private SSH key content |
| `overrides-file` | No | `kubernetes/overrides-su.yaml` | Local path to Helm values override file |
| `service-name` | Yes | | Helm release name |
| `helm-cluster` | Yes | — | Cluster to deploye to (one of `internal_stage` or `internal_prod`) |
| `image-tag` | Yes | | Docker image tag to deploy |
| `ssh-key` | Yes | | Private SSH key content |
| `overrides-file` | No | `kubernetes/overrides-pu.yaml` | Local path to Helm values override file |
| `namespace` | No | `dsp` | Kubernetes namespace |
| `helm-repo` | No | `nexus-helm-repository` | Helm chart repository name |
| `helm-chart` | No | `DSP-Blueprint` | Chart name in the repo |
| `helm-chart` | No | `DSP-Blueprint` | Chart name in the repository |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/helm-deploy@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/helm-deploy@helm-deploy-v1
with:
service-name: my-service
helm-host: dsp1-stage.schmalzgroup.net
image-tag: ${{ github.sha }}
ssh-key: ${{ secrets.SSH_KEY }}
ssh-key: ${{ secrets.HELM_SSH_KEY }}
```
## Notes
- The override file is `scp`-ed to the remote host, then `helm upgrade --install` is run via SSH.
- Uses `--atomic` flag, so a failed deploy is automatically rolled back.
- `StrictHostKeyChecking` is disabled.
- The SSH key is written to a temporary file with `600` permissions and removed after the job, even on failure.
- The overrides file is copied to the remote host via `scp` before the Helm upgrade.
- `helm upgrade --install` is run with `--atomic` so a failed release is automatically rolled back.
- `StrictHostKeyChecking=no` is used; ensure the host is trusted within your network or add host verification as needed.

View file

@ -5,13 +5,13 @@ inputs:
service-name:
description: Helm release name
required: true
helm-host:
description: SSH target (e.g., dsp1-stage.schmalzgroup.net)
helm-cluster:
description: Name of the target Kubernetes cluster to deploy to
required: true
overrides-file:
description: Local path to Helm values override file
required: false
default: kubernetes/overrides-su.yaml
default: kubernetes/overrides-pu.yaml
image-tag:
description: Docker image tag to deploy
required: true
@ -34,22 +34,72 @@ inputs:
runs:
using: composite
steps:
- shell: bash
- name: Setup SSH key
shell: bash
env:
SSH_KEY: ${{ inputs.ssh-key }}
run: |
set -euo pipefail
SSH_KEY_FILE=$(mktemp)
trap "rm -f '$SSH_KEY_FILE'" EXIT
echo "${{ inputs.ssh-key }}" > "$SSH_KEY_FILE"
printf '%s\n' "$SSH_KEY" > "$SSH_KEY_FILE"
chmod 600 "$SSH_KEY_FILE"
echo "SSH_KEY_FILE=$SSH_KEY_FILE" >> "$GITHUB_ENV"
SSH_OPTS="-i $SSH_KEY_FILE -o StrictHostKeyChecking=no"
REMOTE="root@${{ inputs.helm-host }}"
SERVICE="${{ inputs.service-name }}"
- name: Map cluster name to target host
id: map-host
shell: bash
env:
HELM_CLUSTER: ${{ inputs.helm-cluster }}
run: |
case "$HELM_CLUSTER" in
internal_stage) echo "host=dsp1-stage.schmalzgroup.net" ;;
internal_prod) echo "host=dsp1.schmalzgroup.net" ;;
*) echo "Invalid cluster '$HELM_CLUSTER'. Must be 'internal_stage' or 'internal_prod'." && exit 1 ;;
esac >> "$GITHUB_OUTPUT"
scp $SSH_OPTS "${{ inputs.overrides-file }}" "$REMOTE:/tmp/${SERVICE}-overrides.yaml"
- name: Copy overrides file
shell: bash
env:
HELM_HOST: ${{ steps.map-host.outputs.host }}
SERVICE_NAME: ${{ inputs.service-name }}
OVERRIDES_FILE: ${{ inputs.overrides-file }}
run: |
set -euo pipefail
scp -i "$SSH_KEY_FILE" \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-o ConnectTimeout=10 \
"$OVERRIDES_FILE" \
"root@${HELM_HOST}:/tmp/${SERVICE_NAME}-overrides.yaml"
ssh $SSH_OPTS "$REMOTE" \
- name: Helm deploy
shell: bash
env:
HELM_HOST: ${{ steps.map-host.outputs.host }}
SERVICE_NAME: ${{ inputs.service-name }}
NAMESPACE: ${{ inputs.namespace }}
HELM_REPO: ${{ inputs.helm-repo }}
HELM_CHART: ${{ inputs.helm-chart }}
IMAGE_TAG: ${{ inputs.image-tag }}
run: |
set -euo pipefail
ssh -i "$SSH_KEY_FILE" \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-o ConnectTimeout=10 \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=5 \
"root@${HELM_HOST}" \
"helm repo update && \
helm upgrade --install --create-namespace -n ${{ inputs.namespace }} $SERVICE \
${{ inputs.helm-repo }}/${{ inputs.helm-chart }} -f /tmp/${SERVICE}-overrides.yaml \
--set image.tag=${{ inputs.image-tag }} --atomic --debug"
helm upgrade --install --create-namespace \
-n '${NAMESPACE}' \
'${SERVICE_NAME}' \
'${HELM_REPO}/${HELM_CHART}' \
-f '/tmp/${SERVICE_NAME}-overrides.yaml' \
--set image.tag='${IMAGE_TAG}' \
--atomic --debug"
- name: Cleanup SSH key
if: always()
shell: bash
run: rm -f "$SSH_KEY_FILE"

46
i18n-sync/README.md Normal file
View file

@ -0,0 +1,46 @@
# i18n-sync
Fetches the latest translations from i18n.schmalz.com, commits them to a `chore/i18n` branch, and opens a pull request against the destination branch.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `i18n-application` | Yes | — | Application key in i18n.schmalz.com (e.g. `calculator`) |
| `locales-folder` | No | `locales` | Path to the locales directory relative to the repository root |
| `format-folder` | No | `""` | Directory containing the `package.json` whose format script should be run after downloading translations. Leave empty to skip formatting. |
| `format-script` | No | `format` | pnpm script name to run for formatting (e.g. `format`, `format:fix`) |
| `jfrog-token` | No | `""` | JFrog npm auth token (required when `format-folder` is set) |
| `forgejo-token` | Yes | — | Forgejo token with `contents:write` and `pull-requests:write` access |
| `destination-branch` | No | `dev` | Target branch for the pull request |
## Usage
Minimal — no formatting:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/i18n-sync@i18n-sync-v1
with:
i18n-application: my-app
forgejo-token: ${{ secrets.FORGEJO_I18N_UPDATE_TOKEN }}
```
With formatting after download:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/i18n-sync@i18n-sync-v1
with:
i18n-application: my-app
locales-folder: frontend/locales
format-folder: frontend
jfrog-token: ${{ secrets.JFROG_TOKEN }}
forgejo-token: ${{ secrets.FORGEJO_I18N_UPDATE_TOKEN }}
```
## Notes
- The action fails fast if the `chore/i18n` branch already exists, indicating a previous update is still pending review.
- If no translation changes are detected after downloading, the action exits cleanly without creating a branch or PR.
- Deleted languages (files removed from i18n.schmalz.com) are also staged for removal via `git add --all`.
- `jq` is installed automatically if not present on the runner.
- The `forgejo-token` must belong to a user or bot with `contents:write` and `pull-requests:write` permissions on the repository.

131
i18n-sync/action.yml Normal file
View file

@ -0,0 +1,131 @@
name: i18n Sync
description: >
Fetches the latest translations from i18n.schmalz.com, commits them to a
chore/i18n branch, and opens a pull request against the destination branch.
inputs:
i18n-application:
description: Application key in i18n.schmalz.com (e.g. "calculator")
required: true
locales-folder:
description: Path to the locales directory relative to the repository root
required: false
default: locales
format-folder:
description: >
Directory containing the package.json whose format script should be run
after downloading translations. Leave empty to skip formatting.
required: false
default: ""
format-script:
description: pnpm script name to run for formatting (e.g. "format", "format:fix")
required: false
default: format
jfrog-token:
description: JFrog npm auth token (required when format-folder is set)
required: false
default: ""
forgejo-token:
description: Forgejo token with contents:write and pull-requests:write access
required: true
destination-branch:
description: Target branch for the pull request
required: false
default: dev
runs:
using: composite
steps:
- name: Install jq if missing
shell: bash
run: |
set -euo pipefail
command -v jq >/dev/null 2>&1 || sudo apt-get install -y --no-install-recommends jq
- name: Configure git
shell: bash
env:
FORGEJO_TOKEN: ${{ inputs.forgejo-token }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
ORIGIN_URL=$(git remote get-url origin)
CLEAN_URL=$(echo "$ORIGIN_URL" | sed 's|https://[^@]*@|https://|')
git remote set-url origin "$(echo "$CLEAN_URL" | sed "s|https://|https://github-actions[bot]:${FORGEJO_TOKEN}@|")"
- name: Check if i18n branch already exists
shell: bash
run: |
set -euo pipefail
if git ls-remote --exit-code --heads origin "refs/heads/chore/i18n" >/dev/null 2>&1; then
echo "Branch chore/i18n already exists. A previous i18n update is still pending."
exit 1
fi
- name: Download translations
shell: bash
env:
I18N_APPLICATION: ${{ inputs.i18n-application }}
LOCALES_FOLDER: ${{ inputs.locales-folder }}
run: |
set -euo pipefail
git checkout -b chore/i18n
echo "Fetching available languages for application: $I18N_APPLICATION"
languages_json=$(curl --fail-with-body -sSL "https://i18n.schmalz.com/api/languages?projectKey=$I18N_APPLICATION")
echo "$languages_json" | jq -r '.[]' | while read -r lang; do
echo "Downloading language: $lang"
curl --fail-with-body -sSL -o "${LOCALES_FOLDER}/${lang}.json" \
"https://i18n.schmalz.com/api/${I18N_APPLICATION}/${lang}.json"
done
- name: Format translations
if: ${{ inputs.format-folder != '' }}
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.format-folder }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ${{ inputs.format-script }}
check-dedupe: "false"
- name: Commit and push translations
id: commit
shell: bash
env:
LOCALES_FOLDER: ${{ inputs.locales-folder }}
run: |
set -euo pipefail
git add --all "${LOCALES_FOLDER}/"
if git diff-index --cached --quiet HEAD; then
echo "No translation changes detected. Nothing to do."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit -m "chore: update translations via i18n"
git push origin chore/i18n
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create pull request
if: ${{ steps.commit.outputs.has_changes == 'true' }}
shell: bash
env:
TOKEN: ${{ inputs.forgejo-token }}
DESTINATION_BRANCH: ${{ inputs.destination-branch }}
run: |
set -euo pipefail
curl --fail-with-body -s -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/pulls" \
-d "$(jq -n \
--arg title "chore: update translations via i18n" \
--arg head "chore/i18n" \
--arg base "$DESTINATION_BRANCH" \
--arg body "Automated translation update from i18n.schmalz.com" \
'{title: $title, head: $head, base: $base, body: $body, delete_branch_after_merge: true}'
)"

40
inject-content/README.md Normal file
View file

@ -0,0 +1,40 @@
# inject-content
Inject content into a file by appending or overwriting. Useful for writing secrets, configuration snippets, or any multi-line content into an existing file at runtime.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `content` | Yes | | Content to write into the target file |
| `target-file` | Yes | | Path to the target file |
| `mode` | No | `append` | Write mode: `append` adds to the end of the file; `overwrite` replaces its contents |
## Usage
Append secrets to a Java `.properties` file:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/inject-content@inject-content-v1
with:
content: ${{ secrets.APP_PROPERTIES }}
target-file: src/main/resources/application.properties
```
Overwrite a `.env` file:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/inject-content@inject-content-v1
with:
content: ${{ secrets.DOTENV_CONTENT }}
target-file: .env
mode: overwrite
```
## Notes
- Content is passed via an environment variable to avoid shell injection vulnerabilities.
- In `append` mode a blank line is inserted between existing content and the new content. If the target file is empty or does not exist, no leading blank line is added.
- In `overwrite` mode the file is replaced entirely; if the file does not exist it is created.
- Both modes ensure the written content ends with a trailing newline.
- Parent directories of `target-file` are created automatically if they do not exist.

67
inject-content/action.yml Normal file
View file

@ -0,0 +1,67 @@
name: inject-content
description: Inject content into a file by appending or overwriting
inputs:
content:
description: Content to write into the target file
required: true
target-file:
description: Path to the target file
required: true
mode:
description: "Write mode: 'append' adds to the end of the file; 'overwrite' replaces its contents"
required: false
default: append
outputs:
target-file:
description: Path of the file that was written
value: ${{ steps.inject.outputs.target-file }}
runs:
using: composite
steps:
- name: Validate target-file input
shell: bash
env:
TARGET_FILE: ${{ inputs.target-file }}
run: |
if [[ -z "$TARGET_FILE" ]]; then
echo "::error::target-file must not be empty"
exit 1
fi
- name: Validate mode input
shell: bash
env:
MODE: ${{ inputs.mode }}
run: |
if [[ "$MODE" != "append" && "$MODE" != "overwrite" ]]; then
echo "::error::Invalid mode '$MODE'. Must be 'append' or 'overwrite'."
exit 1
fi
- name: Inject content into file
id: inject
shell: bash
env:
CONTENT: ${{ inputs.content }}
TARGET_FILE: ${{ inputs.target-file }}
MODE: ${{ inputs.mode }}
run: |
set -euo pipefail
mkdir -p "$(dirname "$TARGET_FILE")"
if [[ "$MODE" == "overwrite" ]]; then
printf '%s\n' "$CONTENT" > "$TARGET_FILE"
else
if [[ -s "$TARGET_FILE" ]]; then
printf '\n%s\n' "$CONTENT" >> "$TARGET_FILE"
else
printf '%s\n' "$CONTENT" >> "$TARGET_FILE"
fi
fi
echo "Content injected into $TARGET_FILE (mode: $MODE)"
echo "target-file=$TARGET_FILE" >> "$GITHUB_OUTPUT"

View file

@ -0,0 +1,4 @@
FROM aikidosecurity/local-scanner:latest
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View file

@ -0,0 +1,27 @@
name: Aikido Security Release Scan
description: Run an Aikido local full release scan (scheduled / post-merge)
inputs:
apikey:
description: Aikido CI API key
required: true
repository-name:
description: Full repository name (owner/repo)
required: true
branch-name:
description: Branch to scan against
default: main
required: false
runs:
using: docker
image: Dockerfile
args:
- --apikey
- ${{ inputs.apikey }}
- --repositoryname
- ${{ inputs.repository-name }}
- --branchname
- ${{ inputs.branch-name }}
- --force-create-repository-for-branch
- --include-dev-deps

View file

@ -0,0 +1,2 @@
#!/bin/sh
exec aikido-local-scanner scan "${GITHUB_WORKSPACE:-.}" "$@"

View file

@ -0,0 +1,4 @@
FROM aikidosecurity/local-scanner:latest
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View file

@ -0,0 +1,46 @@
name: Aikido Security PR Scan
description: Run an Aikido local PR diff scan (detects newly introduced issues)
inputs:
apikey:
description: Aikido CI API key
required: true
organization:
description: Organization or owner name
required: true
repository-name:
description: Repository name
required: true
base-commit-id:
description: Base commit SHA
required: true
head-commit-id:
description: Head commit SHA
required: true
branch-name:
description: Branch name
required: true
fail-on:
description: 'Minimum severity to fail on: low, medium, high, critical'
default: high
required: false
runs:
using: docker
image: Dockerfile
args:
- --apikey
- ${{ inputs.apikey }}
- --repositoryname
- ${{ inputs.organization }}/${{ inputs.repository-name }}
- --branchname
- ${{ inputs.branch-name }}
- --gating-mode
- pr
- --fail-on
- ${{ inputs.fail-on }}
- --base-commit-id
- ${{ inputs.base-commit-id }}
- --head-commit-id
- ${{ inputs.head-commit-id }}
- --include-dev-deps

View file

@ -0,0 +1,2 @@
#!/bin/sh
exec aikido-local-scanner scan "${GITHUB_WORKSPACE:-.}" "$@"

View file

@ -1,32 +1,58 @@
# maven-build
Run a Maven build -- verify-only for PRs or package+jib push for deploys.
Action for building and validating Maven projects.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `maven-settings` | Yes | | Secret containing `settings.xml` content |
| `java-version` | No | `8` | Java version |
| `maven-image` | No | `maven:3.8.5-openjdk-8` | Maven Docker image |
| `phase` | No | `verify` | Build phase: `verify` or `package-and-push` |
| `verify-goals` | No | `spotless:check checkstyle:check compile` | Goals for the verify phase |
| `maven-profile` | No | `test` | Maven profile used during `package-and-push` |
| `service-dir` | No | `.` | Working directory containing the POM |
| `skip-tests` | No | `false` | Pass `-DskipTests` |
| `image-tag` | No | `${{ github.sha }}-${{ github.run_id }}` | Docker image tag for jib |
| `extra-args` | No | | Additional Maven CLI arguments |
| `java-version` | No | `25` | Java version to set up for the build |
| `maven-version` | No | `3.9.15` | Maven version to set up for the build |
| `distribution` | No | `temurin` | JDK distribution to use |
| `phase` | No | `verify` | Build phase to execute: `verify` runs code-quality checks; `deploy` builds and pushes a Docker image |
| `verify-goals` | No | `spotless:check checkstyle:check test` | Space-separated Maven goals to run during the verify phase |
| `maven-profile` | No | `test` | Maven profile to activate during deploy |
| `service-dir` | No | `.` | Working directory for the Maven build |
| `maven-settings` | **Yes** | — | Secret containing the `settings.xml` content used for repository authentication |
| `extra-args` | No | `""` | Additional Maven arguments appended to the build command |
## Outputs
| Output | Description |
|--------|-------------|
| `image-tag` | The Docker image tag used during the deploy phase |
## Usage
### Verify (code quality + tests)
```yaml
- uses: schmalz/shared-actions/.github/actions/maven-build@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
phase: package-and-push
```
### Deploy (build and push Docker image)
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
phase: deploy
maven-profile: prod
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
```
### Multi-module project
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
service-dir: my-service
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
```
## Notes
- Maven repository cache is restored/saved automatically using `pom.xml` hash.
- The `settings.xml` is written to a temp file and cleaned up after the build.
- The `maven-settings` input is written to a temporary file (`/tmp/maven-settings.xml`) and removed after the build, even on failure.
- During the `deploy` phase, the image tag is generated as `<FORGEJO_SHA>-<unix-timestamp>` and exposed via the `image-tag` output.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,74 +1,125 @@
name: maven-build
description: Run Maven build — verify-only (PRs) or package+jib push (deploy)
description: Action for building and validating Maven projects
inputs:
java-version:
required: false
default: '25'
description: 'Java version to set up for the build'
maven-version:
required: false
default: '3.9.15'
description: 'Maven version to set up for the build'
distribution:
required: false
default: 'temurin'
description: 'JDK distribution to use'
phase:
required: false
default: 'verify'
description: 'Build phase to execute: "verify" runs code-quality checks; "deploy" builds and pushes a Docker image'
verify-goals:
required: false
default: 'spotless:check checkstyle:check compile'
default: 'spotless:check checkstyle:check test'
description: 'Space-separated Maven goals to run during the verify phase'
maven-profile:
required: false
default: 'test'
description: 'Maven profile to activate during deploy'
service-dir:
required: false
default: '.'
skip-tests:
required: false
default: 'false'
image-tag:
required: false
default: '${{ github.sha }}-${{ github.run_id }}'
description: 'Working directory for the Maven build'
maven-settings:
required: true
description: Secret containing settings.xml content
description: 'Secret containing the settings.xml content used for repository authentication'
extra-args:
required: false
default: ''
description: 'Additional Maven arguments appended to the build command'
outputs:
image-tag:
description: 'The Docker image tag used during the deploy phase'
value: ${{ steps.deploy.outputs.image-tag }}
runs:
using: composite
steps:
- name: Restore Maven cache
uses: https://data.forgejo.org/actions/cache/restore@v4
- name: Validate phase
shell: bash
env:
BUILD_PHASE: ${{ inputs.phase }}
run: |
case "$BUILD_PHASE" in
verify|deploy) ;;
*) echo "Invalid phase '$BUILD_PHASE'. Must be 'verify' or 'deploy'." && exit 1 ;;
esac
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/setup-java v4.8.0 — https://github.com/actions/setup-java/tree/v4.8.0
- name: Setup Java
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9
with:
java-version: ${{ inputs.java-version }}
distribution: ${{ inputs.distribution }}
- name: Setup Maven
shell: bash
env:
MAVEN_VERSION: ${{ inputs.maven-version }}
run: |
curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
| tar -xzf - -C /opt
echo "/opt/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH"
echo "Maven ${MAVEN_VERSION} installed successfully"
- name: Cache Maven local repository
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}
key: ${{ runner.os }}-maven-${{ inputs.java-version }}-${{ hashFiles(format('{0}/**/pom.xml', inputs.service-dir)) }}
restore-keys: ${{ runner.os }}-maven-${{ inputs.java-version }}-
- name: Maven build
- name: Write Maven settings
shell: bash
env:
MAVEN_SETTINGS: ${{ inputs.maven-settings }}
run: printf '%s\n' "$MAVEN_SETTINGS" > /tmp/maven-settings.xml
- name: Verify
if: ${{ inputs.phase == 'verify' }}
shell: bash
working-directory: ${{ inputs.service-dir }}
env:
VERIFY_GOALS: ${{ inputs.verify-goals }}
EXTRA_ARGS: ${{ inputs.extra-args }}
MAVEN_PROFILE: ${{ inputs.maven-profile }}
run: |
SETTINGS_FILE=$(mktemp)
echo "${{ inputs.maven-settings }}" > "$SETTINGS_FILE"
trap 'rm -f "$SETTINGS_FILE"' EXIT
mvn --batch-mode $VERIFY_GOALS \
-s /tmp/maven-settings.xml \
-P "$MAVEN_PROFILE" \
$EXTRA_ARGS
SKIP_TESTS_FLAG=""
if [ "${{ inputs.skip-tests }}" = "true" ]; then
SKIP_TESTS_FLAG="-DskipTests"
fi
- name: Deploy
id: deploy
if: ${{ inputs.phase == 'deploy' }}
shell: bash
working-directory: ${{ inputs.service-dir }}
env:
MAVEN_PROFILE: ${{ inputs.maven-profile }}
EXTRA_ARGS: ${{ inputs.extra-args }}
run: |
IMAGE_TAG="${FORGEJO_SHA}-$(date +%s)"
mvn --batch-mode clean package jib:build \
-DsendCredentialsOverHttp=true \
"-Djib.to.tags=$IMAGE_TAG" \
-P "$MAVEN_PROFILE" \
-s /tmp/maven-settings.xml \
$EXTRA_ARGS
echo "image-tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
if [ "${{ inputs.phase }}" = "verify" ]; then
mvn ${{ inputs.verify-goals }} \
-s "$SETTINGS_FILE" \
$SKIP_TESTS_FLAG \
${{ inputs.extra-args }}
elif [ "${{ inputs.phase }}" = "package-and-push" ]; then
mvn clean package jib:build \
-DsendCredentialsOverHttp=true \
-Djib.to.tags=${{ inputs.image-tag }} \
-P ${{ inputs.maven-profile }} \
-s "$SETTINGS_FILE" \
$SKIP_TESTS_FLAG \
${{ inputs.extra-args }}
else
echo "Unknown phase: ${{ inputs.phase }}"
exit 1
fi
- name: Save Maven cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}
- name: Remove Maven settings
if: always()
shell: bash
run: rm -f /tmp/maven-settings.xml

View file

@ -1,38 +0,0 @@
# playwright-e2e
Run Playwright E2E tests with optional sharding, upload results to S3.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog npm auth token |
| `s3-reports-bucket` | Yes | | S3 bucket for report upload |
| `s3-reports-prefix` | Yes | | S3 path prefix |
| `aws-role-arn` | Yes | | IAM role ARN for OIDC authentication |
| `working-directory` | No | `e2e` | Directory containing Playwright config |
| `playwright-version` | No | `v1.58.2` | Playwright version tag for browser cache key |
| `pnpm-version` | No | `10.11` | pnpm version |
| `shard-index` | No | `1` | Current shard (1-based) |
| `shard-total` | No | `1` | Total shards. 1 = no sharding |
| `aws-profile` | No | `stage` | AWS CLI profile name |
| `extra-deps` | No | `""` | Space-separated apt packages to install |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/playwright-e2e@v1
with:
jfrog-token: ${{ secrets.JFROG_TOKEN }}
s3-reports-bucket: my-reports-bucket
s3-reports-prefix: pr-${{ github.event.number }}
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
shard-index: "1"
shard-total: "4"
```
## Notes
- Playwright browsers are cached between runs using the `playwright-version` input as cache key.
- Reports are uploaded to `s3://<bucket>/<prefix>/shard-<index>/`.
- Uses JFrog Artifactory as the npm registry for dependency installation.

View file

@ -1,109 +0,0 @@
name: playwright-e2e
description: Run Playwright E2E tests with optional sharding, upload results to S3.
inputs:
working-directory:
description: Directory containing Playwright config
required: false
default: e2e
playwright-version:
description: Playwright version tag (browser cache key only — actual version comes from pnpm-lock.yaml)
required: false
default: v1.58.2
jfrog-token:
description: JFrog npm auth token
required: true
pnpm-version:
description: pnpm version
required: false
default: "10.11"
node-version:
description: Node.js version
required: false
default: "22"
shard-index:
description: Current shard (1-based)
required: false
default: "1"
shard-total:
description: Total shards. 1 = no sharding.
required: false
default: "1"
s3-reports-bucket:
description: S3 bucket for report upload
required: true
s3-reports-prefix:
description: S3 path prefix
required: true
aws-role-arn:
description: IAM role ARN for OIDC authentication
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: stage
extra-deps:
description: Space-separated apt packages to install
required: false
default: ""
runs:
using: composite
steps:
- name: Configure AWS
uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- name: Restore Playwright browser cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ inputs.playwright-version }}
- name: Setup Node.js
uses: https://code.forgejo.org/actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Run Playwright tests and upload results
shell: bash
env:
EXTRA_DEPS: ${{ inputs.extra-deps }}
PNPM_VERSION: ${{ inputs.pnpm-version }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
WORKING_DIR: ${{ inputs.working-directory }}
SHARD_INDEX: ${{ inputs.shard-index }}
SHARD_TOTAL: ${{ inputs.shard-total }}
BUCKET: ${{ inputs.s3-reports-bucket }}
PREFIX: ${{ inputs.s3-reports-prefix }}
AWS_PROFILE: ${{ inputs.aws-profile }}
run: |
if [ -n "${EXTRA_DEPS}" ]; then
sudo apt-get update -qq
sudo apt-get install -y ${EXTRA_DEPS}
fi
npm i -g "pnpm@${PNPM_VERSION}"
pnpm set registry https://schmalz.jfrog.io/artifactory/api/npm/default-npm/
pnpm set "//schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken=${JFROG_TOKEN}"
pnpm --prefix="${WORKING_DIR}" install --frozen-lockfile
pnpm --prefix="${WORKING_DIR}" exec playwright install --with-deps
SHARD_ARG=""
if [ "${SHARD_TOTAL}" != "1" ]; then
SHARD_ARG="--shard=${SHARD_INDEX}/${SHARD_TOTAL}"
fi
pnpm --prefix="${WORKING_DIR}" exec playwright test ${SHARD_ARG}
aws s3 sync "${WORKING_DIR}/playwright-report/" \
"s3://${BUCKET}/${PREFIX}/shard-${SHARD_INDEX}/" \
--profile "${AWS_PROFILE}"
- name: Save Playwright browser cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ inputs.playwright-version }}

View file

@ -0,0 +1,39 @@
# playwright-merge
Download all Playwright blob reports from shard jobs, merge them into a single HTML + JUnit report, and optionally upload the HTML report to S3.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `package.json` and `playwright.config.ts` |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | No | `""` | JFrog npm auth token |
| `role-arn` | Yes | — | IAM role ARN for AWS authentication |
| `aws-access-key-id` | Yes | — | AWS access key ID |
| `aws-secret-access-key` | Yes | — | AWS secret access key |
| `project-name` | Yes | — | Project name used as the folder in the report bucket (e.g. `hs-pricelist`) |
| `publish-test-reports` | No | `true` | Whether to upload the report to S3 and print the URL at `https://test-reports.schmalz.com` |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/playwright-merge@playwright-merge-v1
with:
working-directory: frontend
node-version: 22
jfrog-token: ${{ secrets.JFROG_TOKEN }}
role-arn: arn:aws:iam::953815822701:role/deployment-role
aws-access-key-id: ${{ secrets.DB_STAGE_DEPLOYMENT_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.DB_STAGE_DEPLOYMENT_SECRET_ACCESS_KEY }}
project-name: hs-pricelist
```
## Notes
- Intended as a follow-up job after all `playwright-run` shard jobs complete.
- Downloads shard artifacts into `all-blob-reports/` and merges them with `playwright merge-reports`.
- Generates HTML report in `playwright-report/` and JUnit XML in `test-results/junit.xml`.
- Uploads report files to `s3://com.schmalz.db.stage.test-reports/reports/<project-name>/<timestamp>/` when `publish-test-reports` is `true`.
- Prints the final report URL on `https://test-reports.schmalz.com`.

104
playwright-merge/action.yml Normal file
View file

@ -0,0 +1,104 @@
name: Playwright Merge
description: >
Download all blob reports from shard jobs, merge them into a single HTML + JUnit
report, upload the HTML report to S3, and upload the JUnit report as an artifact.
Call this in a follow-up job after all playwright-run shard jobs complete.
inputs:
working-directory:
description: Directory containing package.json and playwright.config.ts
required: false
default: "."
node-version:
description: Node.js version
required: false
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: false
default: ""
role-arn:
description: IAM role ARN for AWS authentication
required: true
aws-access-key-id:
description: AWS access key ID
required: true
aws-secret-access-key:
description: AWS secret access key
required: true
project-name:
description: Project name used as the folder in the report bucket (e.g. hs-pricelist)
required: true
publish-test-reports:
description: Whether to upload the report to S3 and print the URL at https://test-reports.schmalz.com. Set to false to skip upload entirely.
required: false
default: "true"
runs:
using: composite
steps:
- name: Install AWS CLI
shell: bash
run: |
if ! command -v aws &>/dev/null; then
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install
rm -rf /tmp/awscliv2.zip /tmp/aws
fi
- name: Configure AWS
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aws-configure@aws-configure-v1
with:
role-arn: ${{ inputs.role-arn }}
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
- name: Install dependencies
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.working-directory }}
node-version: ${{ inputs.node-version }}
pnpm-version: ${{ inputs.pnpm-version }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ""
frozen-lockfile: "true"
check-dedupe: "false"
- name: Download blob reports
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
path: ${{ inputs.working-directory }}/all-blob-reports/
merge-multiple: "true"
- name: Merge blob reports
shell: bash
env:
CI: "true"
WORKING_DIR: ${{ inputs.working-directory }}
PLAYWRIGHT_JUNIT_OUTPUT_NAME: test-results/junit.xml
run: |
cd "${WORKING_DIR}"
pnpm exec playwright merge-reports \
--reporter=html,junit \
all-blob-reports/
- name: Upload report to S3
if: ${{ inputs.publish-test-reports == 'true' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
PROJECT_NAME: ${{ inputs.project-name }}
run: |
TIMESTAMP=$(date +%s)
S3_BUCKET=com.schmalz.db.stage.test-reports
S3_PATH="s3://${S3_BUCKET}/reports/${PROJECT_NAME}/${TIMESTAMP}"
aws s3 sync "${WORKING_DIR}/playwright-report/" "${S3_PATH}/"
if [ -f "${WORKING_DIR}/test-results/junit.xml" ]; then
aws s3 cp "${WORKING_DIR}/test-results/junit.xml" "${S3_PATH}/junit.xml"
fi
echo "Report URL: https://test-reports.schmalz.com/reports/${PROJECT_NAME}/${TIMESTAMP}/index.html"

33
playwright-run/README.md Normal file
View file

@ -0,0 +1,33 @@
# playwright-run
Run Playwright E2E tests for one shard and upload the blob report as an artifact.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `package.json` and `playwright.config.ts` |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | No | `""` | JFrog npm auth token |
| `shard-index` | No | `1` | Current shard index (1-based). Set to `1` when not sharding. |
| `shard-total` | No | `1` | Total number of shards. Set to `1` to disable sharding. |
| `artifact-retention-days` | No | `3` | Number of days to retain the blob report artifact |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/playwright-run@playwright-run-v1
with:
working-directory: frontend
node-version: 22
jfrog-token: ${{ secrets.JFROG_TOKEN }}
shard-index: ${{ matrix.shard-index }}
shard-total: 5
```
## Notes
- Intended for matrix shard jobs.
- Uploads one artifact per shard named `blob-report-<shard-index>`.
- Use `playwright-merge` in a follow-up job to merge shard reports.

87
playwright-run/action.yml Normal file
View file

@ -0,0 +1,87 @@
name: Playwright Run
description: >
Run Playwright E2E tests for one shard and upload the blob report as an artifact.
Call this from a matrix job. Use the playwright-merge action in a follow-up job
to produce the final HTML + JUnit report.
inputs:
working-directory:
description: Directory containing package.json and playwright.config.ts
required: false
default: "."
node-version:
description: Node.js version
required: false
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: false
default: ""
shard-index:
description: Current shard index (1-based). Set to 1 when not sharding.
required: false
default: "1"
shard-total:
description: Total number of shards. Set to 1 to disable sharding.
required: false
default: "1"
artifact-retention-days:
description: Number of days to retain the blob report artifact
required: false
default: "3"
runs:
using: composite
steps:
- name: Install dependencies
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.working-directory }}
node-version: ${{ inputs.node-version }}
pnpm-version: ${{ inputs.pnpm-version }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ""
frozen-lockfile: "true"
check-dedupe: "false"
- name: Cache Playwright browsers
id: playwright-cache
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-playwright-
- name: Install Playwright browsers
if: ${{ steps.playwright-cache.outputs.cache-hit != 'true' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
run: pnpm --prefix="${WORKING_DIR}" exec playwright install --with-deps
- name: Run Playwright tests
shell: bash
env:
CI: "true"
WORKING_DIR: ${{ inputs.working-directory }}
SHARD_INDEX: ${{ inputs.shard-index }}
SHARD_TOTAL: ${{ inputs.shard-total }}
run: |
SHARD_ARG=""
if [ "${SHARD_TOTAL}" != "1" ]; then
SHARD_ARG="--shard=${SHARD_INDEX}/${SHARD_TOTAL}"
fi
pnpm --prefix="${WORKING_DIR}" exec playwright test ${SHARD_ARG} --reporter=blob,dot
- name: Upload blob report
if: ${{ !cancelled() }}
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/upload-artifact@upload-artifact-v1
with:
name: blob-report-${{ inputs.shard-index }}
path: ${{ inputs.working-directory }}/blob-report/
retention-days: ${{ inputs.artifact-retention-days }}
if-no-files-found: ignore

View file

@ -1,28 +1,30 @@
# pnpm-build
Set up pnpm, authenticate with JFrog npm registry, install dependencies, and run build scripts.
Action for building and validating with PNPM.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog npm auth token |
| `working-directory` | No | `.` | Directory containing `package.json` |
| `pnpm-version` | No | `10.14` | pnpm version |
| `node-version` | No | `22` | Node.js version |
| `run-scripts` | No | `ci,typecheck,build` | Comma-separated list of `pnpm run` scripts |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | No | `""` | JFrog npm auth token for the Artifactory registry |
| `run-scripts` | No | `ci,typecheck,build` | Comma-separated list of `pnpm run` scripts to execute |
| `frozen-lockfile` | No | `true` | Pass `--frozen-lockfile` to `pnpm install` |
| `check-dedupe` | No | `true` | Run `pnpm dedupe --check` |
| `check-dedupe` | No | `true` | Run `pnpm dedupe --check` before install |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/pnpm-build@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: frontend
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- pnpm store cache is restored/saved automatically using `pnpm-lock.yaml` hash.
- Registry is set to `schmalz.jfrog.io`.
- Configures the Artifactory npm registry authentication only if `jfrog-token` is provided.
- Runs `pnpm dedupe --check` before install when `check-dedupe` is `true`.
- Executes each script in `run-scripts` in order via `pnpm run`.

View file

@ -1,18 +1,28 @@
name: pnpm-build
description: Set up pnpm, authenticate JFrog npm registry, install deps, run scripts.
name: PNPM Build
description: >
Build and validate frontend using PNPM.
inputs:
working-directory:
description: Directory containing package.json
required: false
default: "."
node-version:
description: Node.js version
required: false
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.14"
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: true
required: false
default: ""
nexus-token:
description: Nexus npm auth token
required: false
default: ""
run-scripts:
description: Comma-separated list of pnpm run scripts
required: false
@ -25,56 +35,80 @@ inputs:
description: Run pnpm dedupe --check
required: false
default: "true"
node-version:
description: Node.js version
required: false
default: "22"
runs:
using: composite
steps:
- name: Restore pnpm cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ~/.local/share/pnpm/store/v3
key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/setup-node v4.4.0 — https://code.forgejo.org/actions/setup-node/commits/tag/v4.4.0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: ${{ inputs.node-version }}
- name: Setup Node.js
uses: https://code.forgejo.org/actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# pnpm/action-setup v4.3.0 — https://code.forgejo.org/pnpm/action-setup/commits/tag/v4.3.0
- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
env:
# Override any registry configured in .npmrc (e.g. JFrog or Nexus).
# pnpm/action-setup bootstraps itself via npm before pnpm is available,
# so it must reach the public npm registry. Auth for private registries
# is configured in a later step, after pnpm is installed.
NPM_CONFIG_REGISTRY: https://registry.npmjs.org
with:
version: ${{ inputs.pnpm-version }}
- name: Setup and build
shell: bash
env:
PNPM_VERSION: ${{ inputs.pnpm-version }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
WORKING_DIR: ${{ inputs.working-directory }}
RUN_SCRIPTS: ${{ inputs.run-scripts }}
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
CHECK_DEDUPE: ${{ inputs.check-dedupe }}
run: |
npm i -g "pnpm@${PNPM_VERSION}"
pnpm set registry https://schmalz.jfrog.io/artifactory/api/npm/default-npm/
pnpm set "//schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken=${JFROG_TOKEN}"
- name: Get pnpm store directory
id: pnpm-store
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
if [ "${CHECK_DEDUPE}" = "true" ]; then
pnpm --prefix="${WORKING_DIR}" dedupe --check
fi
- name: Cache pnpm store
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-${{ inputs.pnpm-version }}-${{ hashFiles(format('{0}/pnpm-lock.yaml', inputs.working-directory)) }}
restore-keys: ${{ runner.os }}-pnpm-${{ inputs.pnpm-version }}-
INSTALL_ARGS=""
if [ "${FROZEN_LOCKFILE}" = "true" ]; then
INSTALL_ARGS="--frozen-lockfile"
fi
pnpm --prefix="${WORKING_DIR}" install ${INSTALL_ARGS}
- name: Configure JFrog registry authentication
if: ${{ inputs.jfrog-token != '' }}
shell: bash
env:
JFROG_TOKEN: ${{ inputs.jfrog-token }}
run: |
pnpm set registry https://schmalz.jfrog.io/artifactory/api/npm/default-npm/
pnpm set //schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken "$JFROG_TOKEN"
IFS=',' read -ra SCRIPTS <<< "${RUN_SCRIPTS}"
for script in "${SCRIPTS[@]}"; do
pnpm --prefix="${WORKING_DIR}" run "${script}"
done
- name: Configure Nexus registry authentication
if: ${{ inputs.nexus-token != '' }}
shell: bash
env:
NEXUS_TOKEN: ${{ inputs.nexus-token }}
run: |
pnpm set registry https://nexus.schmalzgroup.com/repository/npm-all/
pnpm set //nexus.schmalzgroup.com/repository/npm-all/:_authToken "$NEXUS_TOKEN"
- name: Save pnpm cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ~/.local/share/pnpm/store/v3
key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Build
shell: bash
env:
PNPM_VERSION: ${{ inputs.pnpm-version }}
WORKING_DIR: ${{ inputs.working-directory }}
RUN_SCRIPTS: ${{ inputs.run-scripts }}
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
CHECK_DEDUPE: ${{ inputs.check-dedupe }}
run: |
if [ "${CHECK_DEDUPE}" = "true" ]; then
pnpm --prefix="${WORKING_DIR}" dedupe --check
fi
INSTALL_ARGS=""
if [ "${FROZEN_LOCKFILE}" = "true" ]; then
INSTALL_ARGS="--frozen-lockfile"
fi
pnpm --prefix="${WORKING_DIR}" install $INSTALL_ARGS
IFS=',' read -ra SCRIPTS <<< "${RUN_SCRIPTS}"
for script in "${SCRIPTS[@]}"; do
pnpm --prefix="${WORKING_DIR}" run "${script}"
done

View file

@ -1,26 +1,28 @@
# publish-npm-package
Build and publish npm package to JFrog Artifactory.
Publish a PNPM package to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog npm auth token |
| `working-directory` | No | `.` | Directory containing package.json |
| `pnpm-version` | No | `10.14` | pnpm version |
| `registry-url` | No | `https://schmalz.jfrog.io/artifactory/api/npm/default-npm/` | JFrog npm registry URL |
| `build-script` | No | `build` | pnpm build script name |
| `working-directory` | No | `.` | Directory containing `package.json` |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | Yes | — | JFrog npm auth token |
| `registry-url` | No | `https://schmalz.jfrog.io/artifactory/api/npm/default-npm/` | npm registry URL |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/publish-npm-package@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-npm-package@publish-npm-package-v1
with:
working-directory: .
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Runs `pnpm install --frozen-lockfile`, then the build script, then `pnpm publish --no-git-checks`.
- The version published is whatever is in `package.json` -- bump it before calling this action.
- Publishes with `pnpm publish`.
- Configures the registry auth token from `registry-url` and `jfrog-token`.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,51 +1,64 @@
name: publish-npm-package
description: Build and publish npm package to JFrog Artifactory.
description: Publish a PNPM package to JFrog Artifactory.
inputs:
working-directory:
description: Directory containing package.json
required: false
default: "."
pnpm-version:
description: pnpm version
required: false
default: "10.14"
node-version:
description: Node.js version
required: false
default: "22"
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: true
registry-url:
description: JFrog npm registry URL
description: npm registry URL
required: false
default: "https://schmalz.jfrog.io/artifactory/api/npm/default-npm/"
build-script:
description: pnpm build script name
required: false
default: "build"
runs:
using: composite
steps:
- name: Setup Node.js
uses: https://code.forgejo.org/actions/setup-node@v4
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/setup-node v4.4.0 — https://code.forgejo.org/actions/setup-node/commits/tag/v4.4.0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: ${{ inputs.node-version }}
- name: Build and publish
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# pnpm/action-setup v4.3.0 — https://code.forgejo.org/pnpm/action-setup/commits/tag/v4.3.0
- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
env:
# pnpm/action-setup bootstraps itself via npm before pnpm is available,
# so it must reach the public npm registry.
NPM_CONFIG_REGISTRY: https://registry.npmjs.org
with:
version: ${{ inputs.pnpm-version }}
- name: Configure JFrog registry authentication
shell: bash
env:
PNPM_VERSION: ${{ inputs.pnpm-version }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
REGISTRY_URL: ${{ inputs.registry-url }}
WORKING_DIR: ${{ inputs.working-directory }}
BUILD_SCRIPT: ${{ inputs.build-script }}
run: |
npm i -g "pnpm@${PNPM_VERSION}"
set -euo pipefail
pnpm set registry "${REGISTRY_URL}"
pnpm set "//schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken=${JFROG_TOKEN}"
pnpm --prefix="${WORKING_DIR}" install --frozen-lockfile
pnpm --prefix="${WORKING_DIR}" run "${BUILD_SCRIPT}"
pnpm --prefix="${WORKING_DIR}" publish --no-git-checks
AUTHORITY="${REGISTRY_URL#https://}"
AUTHORITY="${AUTHORITY#http://}"
AUTHORITY="${AUTHORITY%/}"
pnpm set "//${AUTHORITY}/:_authToken" "${JFROG_TOKEN}"
- name: Publish
shell: bash
working-directory: ${{ inputs.working-directory }}
run: pnpm publish

View file

@ -1,26 +1,29 @@
# publish-rust-crate
Build, test, and publish Rust crate to JFrog Cargo registry.
Publish a Rust crate to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog access token for Cargo registry auth |
| `working-directory` | No | `.` | Directory containing Cargo.toml |
| `rust-version` | No | `1.87` | Rust toolchain version |
| `cargo-registry-name` | No | `jfrog` | Cargo registry name |
| `run-tests` | No | `true` | Run `cargo test` before publishing |
| `working-directory` | No | `.` | Directory containing `Cargo.toml` |
| `rust-version` | No | `1.95.0` | Rust toolchain version |
| `jfrog-token` | Yes | — | JFrog token for the Artifactory Cargo registry |
| `registry-name` | No | `artifactory` | Cargo registry name |
| `registry-index` | No | `sparse+https://schmalz.jfrog.io/artifactory/api/cargo/schmalz-cargo-local/index/` | Cargo registry index URL |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/publish-rust-crate@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-rust-crate@publish-rust-crate-v1
with:
working-directory: .
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- The registry token is set via `CARGO_REGISTRIES_<NAME>_TOKEN` environment variable (registry name is uppercased).
- The crate version published is whatever is in `Cargo.toml` -- bump it before calling this action.
- Configures Cargo registry settings in `${CARGO_HOME}/config.toml` and `${CARGO_HOME}/credentials.toml`.
- Falls back to `$HOME/.cargo` when `CARGO_HOME` is not set.
- Publishes with `cargo publish --registry <registry-name>`.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,5 +1,5 @@
name: publish-rust-crate
description: Build, test, and publish Rust crate to JFrog Cargo registry.
description: Publish a Rust crate to JFrog Artifactory.
inputs:
working-directory:
@ -9,34 +9,56 @@ inputs:
rust-version:
description: Rust toolchain version
required: false
default: "1.87"
cargo-registry-name:
default: "1.95.0"
jfrog-token:
description: JFrog token for the Artifactory Cargo registry
required: true
registry-name:
description: Cargo registry name
required: false
default: "jfrog"
jfrog-token:
description: JFrog access token for Cargo registry auth
required: true
run-tests:
description: Run cargo test before publishing
default: artifactory
registry-index:
description: Cargo registry index URL
required: false
default: "true"
default: "sparse+https://schmalz.jfrog.io/artifactory/api/cargo/schmalz-cargo-local/index/"
runs:
using: composite
steps:
- name: Test and publish
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# dtolnay/rust-toolchain v1 (2026-03-27) — https://github.com/dtolnay/rust-toolchain/commit/3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: ${{ inputs.rust-version }}
- name: Configure Cargo registry (JFrog Artifactory)
shell: bash
env:
RUST_VERSION: ${{ inputs.rust-version }}
REGISTRY_NAME: ${{ inputs.cargo-registry-name }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
WORKING_DIR: ${{ inputs.working-directory }}
RUN_TESTS: ${{ inputs.run-tests }}
REGISTRY_NAME: ${{ inputs.registry-name }}
REGISTRY_INDEX: ${{ inputs.registry-index }}
run: |
rustup default "${RUST_VERSION}"
UPPER_NAME="$(echo "${REGISTRY_NAME}" | tr '[:lower:]' '[:upper:]')"
export "CARGO_REGISTRIES_${UPPER_NAME}_TOKEN=Bearer ${JFROG_TOKEN}"
cd "${WORKING_DIR}"
if [ "${RUN_TESTS}" = "true" ]; then cargo test; fi
cargo publish --registry "${REGISTRY_NAME}"
set -euo pipefail
CARGO_HOME_DIR="${CARGO_HOME:-$HOME/.cargo}"
mkdir -p "${CARGO_HOME_DIR}"
cat >> "${CARGO_HOME_DIR}/config.toml" <<EOF
[registries.${REGISTRY_NAME}]
index = "${REGISTRY_INDEX}"
[registry]
global-credential-providers = ["cargo:token"]
EOF
cat >> "${CARGO_HOME_DIR}/credentials.toml" <<EOF
[registries.${REGISTRY_NAME}]
token = "Bearer ${JFROG_TOKEN}"
EOF
- name: Publish
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
REGISTRY_NAME: ${{ inputs.registry-name }}
run: cargo publish --registry "${REGISTRY_NAME}"

View file

@ -0,0 +1,69 @@
# publish-static-contents
Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionally manages versioned static builds by cleaning up old versions.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `dist_dir` | No | `frontend/dist` | Path to the frontend dist directory |
| `s3_bucket_name` | Yes | — | Name of the S3 bucket to sync assets to |
| `cloudfront_distribution_ids` | No | `''` | Space-separated list of CloudFront distribution IDs to invalidate |
| `versioning` | No | `false` | When `true`, enables versioned builds. All assets get `Cache-Control: public, max-age=31536000, immutable`. Old versions older than 7 days are deleted, keeping at least the 2 newest |
| `versioning_prefix` | No | `''` | S3 prefix under which versioned builds are stored (e.g. `_static``_static/1234567890/`). Requires `versioning: true` |
| `cache_rules` | No | `[]` | JSON array of per-file cache overrides. Each matched file is uploaded individually with the given headers and excluded from the bulk sync. When `versioning` is `true` and `cache_rules` is empty and `index.html` exists, defaults to short-caching `index.html` |
## Usage
Plain sync:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with:
s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
```
With versioned static assets:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with:
s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
versioning: true
```
With versioned static assets under a prefix:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with:
s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
versioning: true
versioning_prefix: _static
```
With custom per-file cache rules (e.g. for a PWA):
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with:
s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
versioning: true
cache_rules: |
[
{"file": "index.html", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "text/html"},
{"file": "sw.js", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "application/javascript"},
{"file": "manifest.webmanifest", "cache_control": "no-cache, max-age=300", "content_type": "application/manifest+json"}
]
```
## Notes
- When `versioning` is `true` and `cache_rules` is empty, `index.html` is automatically short-cached (`no-cache, max-age=300`) if it exists in the dist directory.
- `cache_rules` works independently of `versioning` and can be used for plain syncs too.
- Old versioned builds are pruned: any version folder older than 7 days is deleted, with at least the 2 newest versions always retained.
- CloudFront invalidation is skipped when `cloudfront_distribution_ids` is empty.

View file

@ -0,0 +1,141 @@
name: Publish to CloudFront
description: Syncs frontend assets to S3 and invalidates the CloudFront distribution
inputs:
dist_dir:
description: Path to the frontend dist directory
required: false
default: frontend/dist
s3_bucket_name:
description: Name of the S3 bucket to sync assets to
required: true
cloudfront_distribution_ids:
description: Space-separated list of CloudFront distribution IDs to invalidate
required: false
default: ''
versioning:
description: >
When set to true, enables versioned builds: assets are synced with
"public, max-age=31536000, immutable" and old versions older than 7 days
are deleted, keeping at least the 2 newest.
required: false
default: 'false'
versioning_prefix:
description: >
S3 prefix under which versioned builds are stored
(e.g. "_static" → _static/1234567890/). When omitted, versions are
stored at the bucket root. Requires versioning to be true.
required: false
default: ''
cache_rules:
description: >
JSON array of per-file cache overrides. Each matched file is uploaded
individually with the given headers and excluded from the bulk sync.
content_type is optional. Applied independently of versioning.
When versioning is enabled and cache_rules is empty, defaults to
short-caching index.html — the standard SPA behaviour.
Example:
cache_rules: |
[
{"file": "index.html", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "text/html"},
{"file": "sw.js", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "application/javascript"},
{"file": "manifest.webmanifest", "cache_control": "no-cache, max-age=300", "content_type": "application/manifest+json"}
]
required: false
default: '[]'
runs:
using: composite
steps:
- name: Install AWS CLI
shell: bash
run: |
if ! command -v aws &> /dev/null; then
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install
rm -rf /tmp/awscliv2.zip /tmp/aws
fi
- name: Publish frontend assets
shell: bash
env:
INPUT_DIST_DIR: ${{ inputs.dist_dir }}
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
INPUT_VERSIONING: ${{ inputs.versioning }}
INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }}
INPUT_CACHE_RULES: ${{ inputs.cache_rules }}
run: |
# When versioning is active and no cache_rules provided, short-cache index.html by default
EFFECTIVE_CACHE_RULES="${INPUT_CACHE_RULES}"
if [[ "${INPUT_VERSIONING}" == "true" && "${EFFECTIVE_CACHE_RULES}" == "[]" && -f "${INPUT_DIST_DIR}/index.html" ]]; then
EFFECTIVE_CACHE_RULES='[{"file": "index.html", "cache_control": "no-cache, max-age=300", "content_type": "text/html"}]'
fi
# Upload each file from cache_rules with its own headers,
# and collect --exclude args so the bulk sync skips them
EXCLUDE_ARGS=()
if [[ "${EFFECTIVE_CACHE_RULES}" != "[]" ]]; then
while IFS= read -r rule; do
file=$(echo "$rule" | jq -r '.file')
cache_control=$(echo "$rule" | jq -r '.cache_control')
content_type=$(echo "$rule" | jq -r '.content_type // empty')
EXCLUDE_ARGS+=("--exclude" "$file")
if [[ -f "${INPUT_DIST_DIR}/${file}" ]]; then
CT_ARG=()
if [[ -n "$content_type" ]]; then
CT_ARG=("--content-type" "$content_type")
fi
aws s3 cp "${INPUT_DIST_DIR}/${file}" "s3://${INPUT_S3_BUCKET_NAME}/${file}" \
--cache-control "$cache_control" \
--metadata-directive REPLACE \
"${CT_ARG[@]}"
fi
done < <(echo "$EFFECTIVE_CACHE_RULES" | jq -c '.[]')
fi
# Bulk sync remaining files; versioned builds get immutable cache headers
CACHE_CONTROL_ARG=()
if [[ "${INPUT_VERSIONING}" == "true" ]]; then
CACHE_CONTROL_ARG=("--cache-control" "public, max-age=31536000, immutable")
fi
aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" "${CACHE_CONTROL_ARG[@]}" "${EXCLUDE_ARGS[@]}"
- name: Clean up old versioned static builds
if: ${{ inputs.versioning == 'true' }}
shell: bash
env:
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }}
run: |
S3_PATH="s3://$INPUT_S3_BUCKET_NAME"
if [[ -n "${INPUT_VERSIONING_PREFIX}" ]]; then
S3_PATH="s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONING_PREFIX"
fi
aws s3 ls "${S3_PATH}/" \
| grep -oP '(?<=PRE )[0-9]+' \
| sort --stable --reverse \
| tail -n +3 \
| while read version; do
now=$(($(date +%s%N)/1000000))
diff=$(($now-$version))
# delete if older than 7 days
if [ $diff -gt 604800000 ]; then
echo "Deleting $version"
aws s3 rm --recursive "${S3_PATH}/$version"
fi
done
- name: Invalidate CloudFront
if: ${{ inputs.cloudfront_distribution_ids != '' }}
shell: bash
env:
INPUT_CLOUDFRONT_DISTRIBUTION_IDS: ${{ inputs.cloudfront_distribution_ids }}
run: |
echo "$INPUT_CLOUDFRONT_DISTRIBUTION_IDS" \
| tr ' ' '\n' \
| xargs -I% aws cloudfront create-invalidation --distribution-id % --paths '/*'

View file

@ -1,42 +1,42 @@
# rust-build
Run Rust CI -- fmt, clippy, tests, optional cross-compilation.
Set up Rust toolchain, configure Cargo registry, cache dependencies, run optional checks, and build via the project's `build.sh` script.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `toolchain` | No | `stable` | Rust toolchain |
| `features` | No | `""` | Cargo features to enable |
| `cross-target` | No | `""` | Cross-compilation target (installs `cross` when set) |
| `run-fmt` | No | `true` | Run `cargo fmt --check` |
| `run-clippy` | No | `true` | Run clippy with `-D warnings` |
| `run-tests` | No | `true` | Run `cargo test` |
| `run-deny` | No | `false` | Run `cargo deny check` |
| `run-semver-checks` | No | `false` | Run `cargo semver-checks` |
| `sccache-enabled` | No | `true` | Enable sccache (requires `sccache_ci_setup.sh` in working directory) |
| `working-directory` | No | `.` | Working directory |
| `working-directory` | No | `.` | Directory containing `Cargo.toml` and `build.sh` |
| `rust-version` | No | `1.95.0` | Rust toolchain version |
| `cross-target` | No | `x86_64-unknown-linux-musl` | Cross-compilation target triple |
| `build-mode` | No | `release` | Build mode — `release` or `debug` |
| `run-checks` | No | `""` | Comma-separated checks to run before building — `fmt`, `clippy`, `test` |
| `jfrog-token` | No | `""` | JFrog token for the Artifactory Cargo registry |
## Usage
### PR check (checks + debug build)
```yaml
- uses: schmalz/shared-actions/.github/actions/rust-build@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/rust-build@rust-build-v1
with:
features: serde,tokio
run-deny: "true"
working-directory: backend-rs
build-mode: debug
run-checks: fmt,clippy,test
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
### Cross-compilation
### Release build
```yaml
- uses: schmalz/shared-actions/.github/actions/rust-build@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/rust-build@rust-build-v1
with:
cross-target: aarch64-unknown-linux-gnu
run-fmt: "false"
working-directory: backend-rs
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Cargo registry and `target/` directory are cached based on `Cargo.lock` hash.
- When `cross-target` is set, `cross` is installed and used instead of `cargo` for clippy, test, and build steps. `cargo fmt` always uses `cargo` directly.
- sccache integration requires a `sccache_ci_setup.sh` script in the working directory.
- Requires a `build.sh` in `working-directory` that accepts `--target <triple>` and optionally `--release`. The script is responsible for running `cargo build` and copying binaries to `target/deploy/`.
- Configures the Artifactory Cargo registry only if `jfrog-token` is provided.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,101 +1,112 @@
name: rust-build
description: Run Rust CI — fmt, clippy, tests, optional cross-compilation.
description: >
Set up Rust toolchain, configure Cargo registry, cache dependencies,
run optional checks, and build via the project's build.sh script.
inputs:
toolchain:
required: false
default: "stable"
features:
required: false
default: ""
cross-target:
required: false
default: ""
run-fmt:
required: false
default: "true"
run-clippy:
required: false
default: "true"
run-tests:
required: false
default: "true"
run-deny:
required: false
default: "false"
run-semver-checks:
required: false
default: "false"
sccache-enabled:
required: false
default: "true"
working-directory:
description: Directory containing Cargo.toml and build.sh
required: false
default: "."
rust-version:
description: Rust toolchain version (passed to dtolnay/rust-toolchain)
required: false
default: "1.95.0"
cross-target:
description: Cross-compilation target triple
required: false
default: x86_64-unknown-linux-musl
build-mode:
description: Build mode — 'release' or 'debug'
required: false
default: release
run-checks:
description: Comma-separated checks to run before building — 'fmt', 'clippy', 'test'
required: false
default: ""
jfrog-token:
description: JFrog token for the Artifactory Cargo registry
required: false
default: ""
runs:
using: composite
steps:
- name: Restore cargo cache
uses: https://data.forgejo.org/actions/cache/restore@v4
- name: Install musl tools
shell: bash
run: |
if ! command -v musl-gcc &>/dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq musl-tools
fi
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# dtolnay/rust-toolchain v1 (2026-03-27) — https://github.com/dtolnay/rust-toolchain/commit/3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
- name: Setup Rust toolchain
id: rust-toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: ${{ inputs.rust-version }}
targets: ${{ inputs.cross-target }}
components: rustfmt,clippy
- name: Configure Cargo registry (JFrog Artifactory)
if: ${{ inputs.jfrog-token != '' }}
shell: bash
env:
JFROG_TOKEN: ${{ inputs.jfrog-token }}
run: |
mkdir -p "${CARGO_HOME}"
cat >> "${CARGO_HOME}/config.toml" <<'EOF'
[registries.artifactory]
index = "sparse+https://schmalz.jfrog.io/artifactory/api/cargo/schmalz-cargo-local/index/"
[registry]
global-credential-providers = ["cargo:token"]
EOF
cat >> "${CARGO_HOME}/credentials.toml" <<EOF
[registries.artifactory]
token = "Bearer ${JFROG_TOKEN}"
EOF
- name: Cache cargo registry
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: |
~/.cargo/registry
target/
key: cargo-${{ hashFiles('**/Cargo.lock') }}
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ inputs.cross-target }}-${{ hashFiles(format('{0}/Cargo.lock', inputs.working-directory)) }}
restore-keys: |
${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ inputs.cross-target }}-
${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-
- name: Rust CI
- name: Run checks
if: ${{ inputs.run-checks != '' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
CROSS_TARGET: ${{ inputs.cross-target }}
RUN_CHECKS: ${{ inputs.run-checks }}
run: |
IFS=',' read -ra CHECKS <<< "${RUN_CHECKS}"
for check in "${CHECKS[@]}"; do
case "${check}" in
fmt) cargo fmt --manifest-path="${WORKING_DIR}/Cargo.toml" --check ;;
clippy) cargo clippy --manifest-path="${WORKING_DIR}/Cargo.toml" --target="${CROSS_TARGET}" -- -D warnings ;;
test) cargo test --manifest-path="${WORKING_DIR}/Cargo.toml" ;;
*) echo "Unknown check: ${check}"; exit 1 ;;
esac
done
- name: Build
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
CROSS_TARGET: ${{ inputs.cross-target }}
BUILD_MODE: ${{ inputs.build-mode }}
run: |
set -euo pipefail
rustup default ${{ inputs.toolchain }}
if [[ "${{ inputs.sccache-enabled }}" == "true" && -f sccache_ci_setup.sh ]]; then
source sccache_ci_setup.sh
BUILD_ARGS="--target ${CROSS_TARGET}"
if [ "${BUILD_MODE}" = "release" ]; then
BUILD_ARGS="${BUILD_ARGS} --release"
fi
CMD=cargo
if [[ -n "${{ inputs.cross-target }}" ]]; then
cargo install cross
CMD=cross
fi
FEATURES_ARG=""
if [[ -n "${{ inputs.features }}" ]]; then
FEATURES_ARG="--features ${{ inputs.features }}"
fi
TARGET_ARG=""
if [[ -n "${{ inputs.cross-target }}" ]]; then
TARGET_ARG="--target ${{ inputs.cross-target }}"
fi
if [[ "${{ inputs.run-fmt }}" == "true" ]]; then
cargo fmt --check
fi
if [[ "${{ inputs.run-clippy }}" == "true" ]]; then
$CMD clippy $FEATURES_ARG $TARGET_ARG -- -D warnings
fi
if [[ "${{ inputs.run-tests }}" == "true" ]]; then
$CMD test $FEATURES_ARG $TARGET_ARG
fi
if [[ "${{ inputs.run-deny }}" == "true" ]]; then
cargo deny check
fi
if [[ "${{ inputs.run-semver-checks }}" == "true" ]]; then
cargo semver-checks
fi
- name: Save cargo cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: |
~/.cargo/registry
target/
key: cargo-${{ hashFiles('**/Cargo.lock') }}
./build.sh ${BUILD_ARGS}

View file

@ -1,23 +0,0 @@
# secrets-inject
Append secret properties content to a Java `.properties` file.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `secret-content` | Yes | | Properties content to append |
| `target-file` | Yes | | Path to the `.properties` file |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/secrets-inject@v1
with:
secret-content: ${{ secrets.APP_SECRETS }}
target-file: src/main/resources/application.properties
```
## Notes
- A blank line is inserted before the appended content to avoid merging with the last existing line.

View file

@ -1,18 +0,0 @@
name: secrets-inject
description: Append a secrets file to a Java .properties file
inputs:
secret-content:
description: Secret value (properties content to append)
required: true
target-file:
description: Path to the .properties file
required: true
runs:
using: composite
steps:
- run: |
echo "" >> ${{ inputs.target-file }}
echo "${{ inputs.secret-content }}" >> ${{ inputs.target-file }}
shell: bash

View file

@ -1,38 +1,37 @@
# terraform-apply
Full Terraform init, workspace select, plan/apply, and output capture.
Apply Terraform configuration files using the official Terraform CLI.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `var-file` | Yes | | Path to `.tfvars` file |
| `workspace` | Yes | | Terraform workspace (`stage` or `prod`) |
| `aws-role-arn` | Yes | | IAM role ARN for OIDC authentication |
| `jfrog-token` | Yes | | JFrog access token (sets `TF_TOKEN_schmalz_jfrog_io`) |
| `terraform-dir` | No | `terraform` | Directory containing Terraform configuration |
| `terraform-version` | No | `1.11` | Terraform version to install |
| `aws-profile` | No | `default` | AWS CLI profile name |
| `output-names` | No | `""` | Comma-separated Terraform output names to capture as raw values |
| `output-json-names` | No | `""` | Comma-separated output names to capture as JSON |
| `plan-only` | No | `false` | Run `plan -out` instead of `apply` |
| `plan-file` | No | `""` | Pre-existing plan file to apply |
| `terraform-dir` | No | `terraform` | Directory containing `.tf` files |
| `terraform-version` | No | `~1.15` | Terraform version to use |
| `var-file` | No | `""` | Path to `.tfvars` file, relative to `terraform-dir` |
| `workspace` | No | `""` | Terraform workspace to select |
| `jfrog-token` | No | `""` | JFrog Artifactory token for the Terraform provider registry (`TF_TOKEN_schmalz_jfrog_io`) |
## Outputs
Non-sensitive Terraform outputs are automatically exported after apply. They are accessible on the calling step via `steps.<id>.outputs.<terraform-output-name>`. Complex types (lists, maps) are JSON-encoded. Outputs marked as `sensitive = true` in Terraform are excluded.
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/terraform-apply@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/terraform-apply@terraform-apply-v1
id: tf-apply
with:
var-file: envs/stage.tfvars
workspace: stage
aws-role-arn: arn:aws:iam::123456789012:role/my-role
var-file: stage.tfvars
jfrog-token: ${{ secrets.JFROG_TOKEN }}
output-names: api_url,db_host
- run: echo ${{ steps.tf-apply.outputs.s3_bucket_name }}
```
## Notes
- Requires `enable-openid-connect: true` on the job.
- Captured outputs are written to `$FORGEJO_OUTPUT` and to files under `<terraform-dir>/.outputs/`.
- Provider cache is restored/saved automatically.
- Use `plan-only: true` for a plan-then-apply workflow across jobs.
- Runs `terraform init`, selects the workspace (if provided), and applies with `-auto-approve`.
- Sets `TF_TOKEN_schmalz_jfrog_io` on both `init` and `apply` steps if `jfrog-token` is provided.
- If `var-file` is provided, it is passed as `-var-file` to the apply command.
- Non-sensitive Terraform outputs are written to `$GITHUB_OUTPUT` after apply — no separate `terraform output` step needed. Sensitive outputs are excluded to prevent secret leakage.

View file

@ -1,118 +1,90 @@
name: terraform-apply
description: Full Terraform init + workspace + apply + output capture
name: Terraform Apply
description: >
Init and apply Terraform configuration files using the official Terraform CLI.
inputs:
terraform-dir:
description: Directory containing Terraform configuration
description: Directory containing .tf files
required: false
default: terraform
terraform-version:
description: Terraform version to install
description: Terraform version to use
required: false
default: "1.11"
default: "~1.15"
var-file:
description: Path to .tfvars file
required: true
description: Path to .tfvars file, relative to terraform-dir
required: false
default: ""
workspace:
description: Terraform workspace (stage or prod)
required: true
aws-role-arn:
description: IAM role ARN for OIDC authentication
required: true
aws-profile:
description: AWS profile name
description: Terraform workspace to use
required: false
default: default
default: ""
jfrog-token:
description: JFrog access token (sets TF_TOKEN_schmalz_jfrog_io)
required: true
output-names:
description: Comma-separated list of terraform output names to capture as raw
required: false
default: ""
plan-only:
description: Run plan -out instead of apply
required: false
default: "false"
plan-file:
description: Pre-existing plan file to apply
required: false
default: ""
output-json-names:
description: Comma-separated output names to capture as JSON
description: JFrog Artifactory token used for Terraform provider registry (sets TF_TOKEN_schmalz_jfrog_io)
required: false
default: ""
runs:
using: composite
steps:
- name: Configure AWS credentials
uses: schmalz/shared-actions/.github/actions/aws-configure@v1
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# hashicorp/setup-terraform v4.0.0 — https://github.com/hashicorp/setup-terraform/commits/v4.0.0/
- name: Setup Terraform
uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
terraform_version: ${{ inputs.terraform-version }}
- name: Restore Terraform provider cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ${{ inputs.terraform-dir }}/.terraform/providers
key: terraform-providers-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
- name: Install Terraform
- name: Set Terraform plugin cache directory
shell: bash
run: |
TF_VERSION="${{ inputs.terraform-version }}"
curl -fsSL "https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" -o /tmp/terraform.zip
sudo unzip -o /tmp/terraform.zip -d /usr/local/bin
rm /tmp/terraform.zip
terraform version
mkdir -p ~/.terraform.d/plugin-cache
echo "TF_PLUGIN_CACHE_DIR=$HOME/.terraform.d/plugin-cache" >> "$GITHUB_ENV"
- name: Terraform init, plan/apply, and capture outputs
- name: Cache Terraform providers
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.terraform.d/plugin-cache
key: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
restore-keys: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-
- name: Terraform Init
shell: bash
env:
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
TF_DIR: ${{ inputs.terraform-dir }}
run: terraform -chdir="$TF_DIR" init -no-color
- name: Terraform Select Workspace
if: ${{ inputs.workspace != '' }}
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
TF_WORKSPACE_NAME: ${{ inputs.workspace }}
run: |
terraform -chdir="$TF_DIR" workspace select -or-create "$TF_WORKSPACE_NAME"
- name: Terraform Apply
shell: bash
env:
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
TF_WORKSPACE: ${{ inputs.workspace }}
AWS_PROFILE: ${{ inputs.aws-profile }}
TF_DIR: ${{ inputs.terraform-dir }}
VAR_FILE: ${{ inputs.var-file }}
PLAN_ONLY: ${{ inputs.plan-only }}
PLAN_FILE: ${{ inputs.plan-file }}
OUTPUT_NAMES: ${{ inputs.output-names }}
OUTPUT_JSON_NAMES: ${{ inputs.output-json-names }}
run: |
terraform -chdir="$TF_DIR" init -no-color
if [ "$PLAN_ONLY" = "true" ]; then
terraform -chdir="$TF_DIR" plan -var-file="$VAR_FILE" -out=terraform.plan -no-color
elif [ -n "$PLAN_FILE" ]; then
terraform -chdir="$TF_DIR" apply -auto-approve -no-color "$PLAN_FILE"
else
terraform -chdir="$TF_DIR" apply -var-file="$VAR_FILE" -auto-approve -no-color
ARGS="-auto-approve -no-color"
if [ -n "$VAR_FILE" ]; then
ARGS="$ARGS -var-file=$VAR_FILE"
fi
terraform -chdir="$TF_DIR" apply $ARGS
mkdir -p "$TF_DIR/.outputs"
if [ -n "$OUTPUT_NAMES" ]; then
IFS=',' read -ra NAMES <<< "$OUTPUT_NAMES"
for name in "${NAMES[@]}"; do
name=$(echo "$name" | xargs)
val=$(terraform -chdir="$TF_DIR" output --raw "$name")
echo "$val" > "$TF_DIR/.outputs/$name"
echo "$name=$val" >> "$FORGEJO_OUTPUT"
done
fi
if [ -n "$OUTPUT_JSON_NAMES" ]; then
IFS=',' read -ra JNAMES <<< "$OUTPUT_JSON_NAMES"
for name in "${JNAMES[@]}"; do
name=$(echo "$name" | xargs)
terraform -chdir="$TF_DIR" output --json "$name" > "$TF_DIR/.outputs/$name.json"
echo "$name=$(terraform -chdir="$TF_DIR" output --json "$name")" >> "$FORGEJO_OUTPUT"
done
fi
- name: Save Terraform provider cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ${{ inputs.terraform-dir }}/.terraform/providers
key: terraform-providers-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
- name: Export Terraform Outputs
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
run: |
terraform -chdir="$TF_DIR" output -json | jq -r '
to_entries[]
| select(.value.sensitive != true)
| .key as $k
| (.value.value | if type == "string" then . else tojson end) as $v
| "\($k)<<__TF_OUT__\n\($v)\n__TF_OUT__"
' >> "$GITHUB_OUTPUT"

View file

@ -1,29 +0,0 @@
# terraform-module-publish
Zip a Terraform module and publish to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `module-dir` | Yes | | Path to the module directory to zip |
| `module-name` | Yes | | Module name (used in zip filename and upload path) |
| `version` | Yes | | Version string (from git tag) |
| `jfrog-token` | Yes | | JFrog access token |
| `jfrog-url` | No | `https://schmalz.jfrog.io/artifactory/terraform/schmalz` | JFrog Artifactory base URL |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/terraform-module-publish@v1
with:
module-dir: modules/vpc
module-name: vpc
version: ${{ github.ref_name }}
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- The module directory contents are zipped and uploaded to `<jfrog-url>/<module-name>/<version>.zip`.
- Uses `curl` with bearer token authentication.

View file

@ -1,32 +0,0 @@
name: terraform-module-publish
description: Zip a Terraform module and publish to JFrog Artifactory
inputs:
module-dir:
description: Path to the module directory to zip
required: true
module-name:
description: Module name (used in zip filename and upload path)
required: true
version:
description: Version string (from git tag)
required: true
jfrog-url:
description: JFrog Artifactory base URL
required: false
default: https://schmalz.jfrog.io/artifactory/terraform/schmalz
jfrog-token:
description: JFrog access token
required: true
runs:
using: composite
steps:
- name: Zip and publish module
shell: bash
run: |
cd "${{ inputs.module-dir }}"
zip -r "/tmp/${{ inputs.module-name }}-${{ inputs.version }}.zip" .
curl -sf -H "Authorization: Bearer ${{ inputs.jfrog-token }}" \
-T "/tmp/${{ inputs.module-name }}-${{ inputs.version }}.zip" \
"${{ inputs.jfrog-url }}/${{ inputs.module-name }}/${{ inputs.version }}.zip"

View file

@ -1,28 +1,42 @@
# terraform-validate
Validate Terraform code without a backend (for PR checks).
Validate Terraform configuration files using the official Terraform CLI.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | Sets `TF_TOKEN_schmalz_jfrog_io` for provider auth |
| `terraform-dir` | No | `terraform` | Directory containing `.tf` files |
| `terraform-version` | No | `1.11` | Terraform version to use |
| `aws-role-arn` | No | `""` | If provided, runs `aws-configure` before validate |
| `aws-profile` | No | `stage` | AWS profile when `aws-role-arn` is given |
| `terraform-version` | No | `~1.15` | Terraform version to use |
| `workspace` | No | `""` | Terraform workspace to use |
| `jfrog-token` | No | `""` | JFrog Artifactory token for the Terraform provider registry (`TF_TOKEN_schmalz_jfrog_io`) |
| `mock-files` | No | `""` | Newline-separated list of file paths (relative to repo root) to create as empty files before validation. Useful when Terraform uses `file()` references that do not exist in CI. |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/terraform-validate@v1
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/terraform-validate@terraform-validate-v1
with:
workspace: stage
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
With mock files for `file()` dependencies:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/terraform-validate@terraform-validate-v1
with:
workspace: stage
jfrog-token: ${{ secrets.JFROG_TOKEN }}
mock-files: |
config/app.json
secrets/tls.crt
secrets/tls.key
```
## Notes
- Runs `terraform init -backend=false`, `terraform fmt -check -recursive`, and `terraform validate`.
- Sets `TF_WORKSPACE=stage` during init.
- Provider cache is restored/saved automatically.
- Optionally configures AWS credentials if `aws-role-arn` is provided (requires `enable-openid-connect: true`).
- Sets `TF_WORKSPACE` during validate if `workspace` is provided.
- Sets `TF_TOKEN_schmalz_jfrog_io` on both `init` and `validate` steps if `jfrog-token` is provided.
- When `mock-files` is set, empty files are created at the given paths (including any missing parent directories) before `terraform init` runs. This allows validation of configurations that reference external files via `file()`.

View file

@ -1,5 +1,6 @@
name: terraform-validate
description: Validate Terraform code without backend (for PR checks)
name: Terraform Validate
description: >
Validate Terraform configuration files using the official Terraform CLI.
inputs:
terraform-dir:
@ -9,51 +10,75 @@ inputs:
terraform-version:
description: Terraform version to use
required: false
default: "1.11"
jfrog-token:
description: Sets TF_TOKEN_schmalz_jfrog_io
required: true
aws-role-arn:
description: If provided, runs aws-configure before validate
default: "~1.15"
workspace:
description: Terraform workspace to use
required: false
default: ""
aws-profile:
description: Profile if aws-role-arn given
jfrog-token:
description: JFrog Artifactory token used for Terraform provider registry (sets TF_TOKEN_schmalz_jfrog_io)
required: false
default: stage
default: ""
mock-files:
description: |-
Newline-separated list of file paths to create as empty files before validation.
Useful when Terraform configurations reference external files via file() that do not exist in CI.
Paths are relative to the repository root.
required: false
default: ""
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
if: ${{ inputs.aws-role-arn != '' }}
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# hashicorp/setup-terraform v4.0.0 — https://github.com/hashicorp/setup-terraform/commits/v4.0.0/
- name: Setup Terraform
uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
terraform_version: ${{ inputs.terraform-version }}
- uses: https://data.forgejo.org/actions/cache/restore@v4
with:
key: terraform-${{ hashFiles('**/.terraform.lock.hcl') }}
path: ${{ inputs.terraform-dir }}/.terraform/providers
- name: Install Terraform
- name: Set Terraform plugin cache directory
shell: bash
run: |
TF_VERSION="${{ inputs.terraform-version }}"
curl -fsSL "https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" -o /tmp/terraform.zip
sudo unzip -o /tmp/terraform.zip -d /usr/local/bin
rm /tmp/terraform.zip
terraform version
mkdir -p ~/.terraform.d/plugin-cache
echo "TF_PLUGIN_CACHE_DIR=$HOME/.terraform.d/plugin-cache" >> "$GITHUB_ENV"
- run: |
export TF_TOKEN_schmalz_jfrog_io="${INPUT_JFROG_TOKEN}"
export TF_WORKSPACE=stage
terraform -chdir="${INPUT_TERRAFORM_DIR}" init -backend=false -no-color
terraform -chdir="${INPUT_TERRAFORM_DIR}" fmt -check -recursive
terraform -chdir="${INPUT_TERRAFORM_DIR}" validate
shell: bash
- uses: https://data.forgejo.org/actions/cache/save@v4
- name: Cache Terraform providers
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
key: terraform-${{ hashFiles('**/.terraform.lock.hcl') }}
path: ${{ inputs.terraform-dir }}/.terraform/providers
path: ~/.terraform.d/plugin-cache
key: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
restore-keys: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-
- name: Create mock files
if: ${{ inputs.mock-files != '' }}
shell: bash
env:
MOCK_FILES: ${{ inputs.mock-files }}
run: |
while IFS= read -r mock_file; do
[ -z "$mock_file" ] && continue
mkdir -p "$(dirname "$mock_file")"
touch "$mock_file"
done <<< "$MOCK_FILES"
- name: Terraform Init
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
run: terraform -chdir=${{ env.TF_DIR }} init -backend=false -no-color
- name: Terraform Format Check
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
run: terraform -chdir=${{ env.TF_DIR }} fmt -check -recursive
- name: Terraform Validate
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
TF_WORKSPACE: ${{ inputs.workspace }}
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
run: terraform -chdir=${{ env.TF_DIR }} validate

37
upload-artifact/README.md Normal file
View file

@ -0,0 +1,37 @@
# upload-artifact
Upload files as a Forgejo Actions artifact. Thin wrapper around `forgejo/upload-artifact` pinned to a specific commit SHA to prevent supply chain attacks.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `name` | Yes | — | Artifact name |
| `path` | Yes | — | File or directory path to upload |
| `retention-days` | No | `30` | Number of days to retain the artifact |
| `if-no-files-found` | No | `warn` | Behaviour when no files are found — `warn`, `error`, or `ignore` |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/upload-artifact@upload-artifact-v1
with:
name: my-artifact
path: dist/
```
Upload and ignore if no files exist:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/upload-artifact@upload-artifact-v1
with:
name: blob-report-${{ matrix.shard-index }}
path: frontend/blob-report/
retention-days: 3
if-no-files-found: ignore
```
## Notes
- Wraps `forgejo/upload-artifact` v4 (node20), compatible with Ubuntu 22 runners.
- The underlying action is pinned to a commit SHA rather than a mutable tag to prevent supply chain attacks.

View file

@ -0,0 +1,33 @@
name: Schmalz Upload Artifact
description: >
Upload files as a Forgejo Actions artifact.
Thin wrapper around forgejo/upload-artifact, pinned to a specific SHA.
inputs:
name:
description: Artifact name
required: true
path:
description: File or directory path to upload
required: true
retention-days:
description: Number of days to retain the artifact
required: false
default: "30"
if-no-files-found:
description: Behaviour when no files are found — 'warn', 'error', or 'ignore'
required: false
default: warn
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# forgejo/upload-artifact v4 — https://code.forgejo.org/forgejo/upload-artifact/commits/tag/v4
- name: Upload artifact
uses: https://code.forgejo.org/forgejo/upload-artifact@16871d9e8cfcf27ff31822cac382bbb5450f1e1e
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
retention-days: ${{ inputs.retention-days }}
if-no-files-found: ${{ inputs.if-no-files-found }}

25
vacuum-lint/README.md Normal file
View file

@ -0,0 +1,25 @@
# vacuum-lint
Action for validating and linting OpenAPI specifications using [Vacuum](https://github.com/daveshanley/vacuum).
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `spec-dir` | No | `spec` | Directory containing the OpenAPI spec |
| `spec-filename` | No | `openapi.json` | Filename of the OpenAPI spec |
| `rules-filename` | No | `vacuum.rules.yaml` | Filename of the lint rules config file |
| `ignore-filename` | No | `vacuum.ignore.yaml` | Filename of the lint ignore file |
| `min-score` | No | `70` | Minimum linting score for the check to pass |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/vacuum-lint@vacuum-lint-v1
```
## Notes
- If `rules-filename` is found inside `spec-dir`, it is passed to Vacuum via `-r` to apply custom rulesets; otherwise Vacuum uses its default rules.
- If `ignore-filename` is found inside `spec-dir`, it is passed to Vacuum via `--ignore-file` to suppress known violations.
- The action fails when the computed linting score falls below `min-score`.

65
vacuum-lint/action.yml Normal file
View file

@ -0,0 +1,65 @@
name: Vacuum Lint
description: >
Validate and lint OpenAPI specifications using Vacuum.
inputs:
spec-dir:
description: Directory containing OpenAPI spec
required: false
default: "spec"
spec-filename:
description: Filename of the OpenAPI spec
required: false
default: "openapi.json"
rules-filename:
description: Filename of the lint rules config file
required: false
default: "vacuum.rules.yaml"
ignore-filename:
description: Filename of the lint ignore file
required: false
default: "vacuum.ignore.yaml"
min-score:
description: Minimum linting score for the check to pass
required: false
default: "70"
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
- name: Install Vacuum
shell: bash
run: curl -fsSL https://raw.githubusercontent.com/daveshanley/vacuum/8222bba0c8b21a3a94faf472e06c4db06f06c6ce/bin/install.sh | sudo sh > /dev/null 2>&1
- name: Lint Spec
shell: bash
env:
SPEC_DIR: ${{ inputs.spec-dir }}
SPEC_FILE: ${{ inputs.spec-filename }}
RULES_FILE: ${{ inputs.rules-filename }}
IGNORE_FILE: ${{ inputs.ignore-filename }}
MIN_SCORE: ${{ inputs.min-score }}
run: |
echo "Linting: [$SPEC_DIR/$SPEC_FILE]"
# base command
CMD="vacuum lint $SPEC_DIR/$SPEC_FILE -x --min-score $MIN_SCORE"
# check for rules file
if [ -f "$SPEC_DIR/$RULES_FILE" ]; then
CMD="$CMD -r $SPEC_DIR/$RULES_FILE"
echo " - using ruleset [$SPEC_DIR/$RULES_FILE]"
fi
# check for ignore file
if [ -f "$SPEC_DIR/$IGNORE_FILE" ]; then
CMD="$CMD --ignore-file $SPEC_DIR/$IGNORE_FILE"
echo " - using ignore file [$SPEC_DIR/$IGNORE_FILE]"
fi
# execute command
$CMD
echo "Linted: [$SPEC_DIR/$SPEC_FILE]"
echo ""