Compare commits

..

312 Commits

Author SHA1 Message Date
GitHub Actions
4fae81efe4 chore: bump VERSION to 0.2.57 2024-01-01 02:17:35 +00:00
dependabot[bot]
238a495579 build(deps): bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#2134)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 21:45:57 +00:00
dependabot[bot]
74dcce467d build(deps): bump github.com/containerd/containerd from 1.7.2 to 1.7.11 (#2136)
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.7.2 to 1.7.11.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.7.2...v1.7.11)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 21:32:29 +00:00
GitHub Actions
0c60f9749a chore: bump VERSION to 0.2.56 2023-12-17 22:10:10 +00:00
dependabot[bot]
6b0ef97c52 build(deps): bump actions/stale from 8 to 9 (#2120)
Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-17 09:14:56 +00:00
raffis
0806c8b109 feat: support config env expansion (#2063)
Signed-off-by: Raffael Sahli <raffael.sahli@doodle.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-17 08:59:13 +00:00
dependabot[bot]
0dfb06748e build(deps): bump actions/checkout from 3 to 4 (#1998)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-16 23:47:32 +00:00
dependabot[bot]
603b44b585 build(deps): bump github.com/creack/pty from 1.1.20 to 1.1.21 (#2099)
Bumps [github.com/creack/pty](https://github.com/creack/pty) from 1.1.20 to 1.1.21.
- [Release notes](https://github.com/creack/pty/releases)
- [Commits](https://github.com/creack/pty/compare/v1.1.20...v1.1.21)

---
updated-dependencies:
- dependency-name: github.com/creack/pty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-12-16 23:18:33 +00:00
Jon Jensen
00fbfa754c Fix noisy runs-on error logging (#2102)
Move the logging back up a level to fix a minor logging issue introduced in #2088

`RunContext`s for composite actions have dummy/blank `Job`s with no `runs-on`,
meaning their calls to `withGithubEnv` would result in an inaccurate log message
complaining that `'runs-on' key not defined in ...`

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-16 23:04:54 +00:00
ChristopherHX
1b10028447 fix: IsHost is defined as false on windows (#2093)
* fix: IsHost is defined as false on windows

* Update docker_run.go

* Update docker_run.go
2023-12-16 14:46:17 -08:00
dependabot[bot]
9cecf94039 build(deps): bump actions/setup-go from 4 to 5 (#2118)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-11 03:07:56 +00:00
dependabot[bot]
29f4123b5c build(deps): bump megalinter/megalinter from 7.6.0 to 7.7.0 (#2119)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.6.0 to 7.7.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.6.0...v7.7.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 02:53:36 +00:00
dependabot[bot]
85c3b3b541 build(deps): bump github.com/go-git/go-git/v5 from 5.10.1 to 5.11.0 (#2117)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.10.1 to 5.11.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.10.1...v5.11.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 02:29:35 +00:00
dependabot[bot]
2b47c99bb7 build(deps): bump golang.org/x/term from 0.14.0 to 0.15.0 (#2112)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/term/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 02:49:24 +00:00
dependabot[bot]
3c405a0d94 build(deps): bump github.com/go-git/go-git/v5 from 5.10.0 to 5.10.1 (#2114)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.10.0...v5.10.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-12-04 02:37:03 +00:00
dependabot[bot]
899a1f206e build(deps): bump github.com/moby/buildkit from 0.12.3 to 0.12.4 (#2113)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.12.3 to 0.12.4.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.12.3...v0.12.4)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 02:24:02 +00:00
GitHub Actions
95ff5bf299 chore: bump VERSION to 0.2.55 2023-12-01 02:17:43 +00:00
dependabot[bot]
bd10c9a801 build(deps): bump megalinter/megalinter from 7.5.0 to 7.6.0 (#2098)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.5.0 to 7.6.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.5.0...v7.6.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-20 03:19:49 +00:00
dependabot[bot]
3d0cb3d82b build(deps): bump actions/github-script from 6 to 7 (#2097)
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 02:53:47 +00:00
GitHub Actions
7693697f4c chore: bump VERSION to 0.2.54 2023-11-14 11:52:13 +00:00
dependabot[bot]
4dcb9b7a13 build(deps): bump golang.org/x/term from 0.13.0 to 0.14.0 (#2091)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/term/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 02:57:59 +00:00
Jon Jensen
55477899e7 Evaluate if condition when calling a reusable workflow (#2087)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-11-12 20:01:32 +00:00
raffis
04011b6b78 feat: support runs-on labels and group (#2062)
Signed-off-by: Raffael Sahli <raffael.sahli@doodle.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-11-12 19:46:38 +00:00
Björn Brauer
c8f847d82d Evaluate all service values (#2054)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-12 18:30:21 +00:00
ChristopherHX
74b0fe8ba9 fix: (#2075)
network-scoped alias is supported only for containers in user defined networks

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-12 18:09:25 +00:00
Jon Jensen
18b4714e38 Don't set GITHUB_TOKEN (#2089)
This needs to be explicitly in the `env` to be consistent with GitHub

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-12 17:52:08 +00:00
Jon Jensen
610358e1c3 Support array expressions in runs-on (#2088)
* Support array expressions in runs-on

* Simplify appproach to use EvaluateYamlNode, fix case-sensitivity bug

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-12 17:40:06 +00:00
Andreas Taylor
1c16fd1967 Use unique name for reusable workflow (#2015)
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-11-12 17:21:41 +00:00
dependabot[bot]
55b09a04cd build(deps): bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#2079)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 02:50:57 +00:00
Jason Song
5a79256ee4 fix: panic (#2071)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-04 14:10:53 +00:00
GitHub Actions
1bb2ee7098 chore: bump VERSION to 0.2.53 2023-11-01 02:13:36 +00:00
dependabot[bot]
84a4025bc8 build(deps): bump github.com/docker/docker (#2067)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.6+incompatible to 24.0.7+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.6...v24.0.7)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-30 03:54:16 +00:00
dependabot[bot]
fb4f29fd6d build(deps): bump github.com/creack/pty from 1.1.18 to 1.1.20 (#2068)
Bumps [github.com/creack/pty](https://github.com/creack/pty) from 1.1.18 to 1.1.20.
- [Release notes](https://github.com/creack/pty/releases)
- [Commits](https://github.com/creack/pty/compare/v1.1.18...v1.1.20)

---
updated-dependencies:
- dependency-name: github.com/creack/pty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-30 03:33:28 +00:00
dependabot[bot]
3e5c62977f build(deps): bump megalinter/megalinter from 7.4.0 to 7.5.0 (#2070)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.4.0...v7.5.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-30 03:16:31 +00:00
dependabot[bot]
83bfbcdafd build(deps): bump go.etcd.io/bbolt from 1.3.7 to 1.3.8 (#2065)
Bumps [go.etcd.io/bbolt](https://github.com/etcd-io/bbolt) from 1.3.7 to 1.3.8.
- [Release notes](https://github.com/etcd-io/bbolt/releases)
- [Commits](https://github.com/etcd-io/bbolt/compare/v1.3.7...v1.3.8)

---
updated-dependencies:
- dependency-name: go.etcd.io/bbolt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-30 03:01:40 +00:00
dependabot[bot]
3d65b0f73f build(deps): bump github.com/docker/cli (#2069)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 24.0.6+incompatible to 24.0.7+incompatible.
- [Commits](https://github.com/docker/cli/compare/v24.0.6...v24.0.7)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-30 02:46:59 +00:00
dependabot[bot]
854e3e9ec5 build(deps): bump github.com/go-git/go-git/v5 from 5.9.0 to 5.10.0 (#2066)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.9.0...v5.10.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 02:28:08 +00:00
dependabot[bot]
db71c41d17 build(deps): bump github.com/mattn/go-isatty from 0.0.19 to 0.0.20 (#2059)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.19 to 0.0.20.
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.19...v0.0.20)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 03:25:32 +00:00
dependabot[bot]
db6e477e25 build(deps): bump github.com/moby/buildkit from 0.12.2 to 0.12.3 (#2060)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.12.2 to 0.12.3.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.12.2...v0.12.3)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 03:05:34 +00:00
Sam Foo
ceeb6c160c Add support for service containers (#1949)
* Support services (#42)

Removed createSimpleContainerName and AutoRemove flag

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/42
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>

* Support services options (#45)

Reviewed-on: https://gitea.com/gitea/act/pulls/45
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>

* Support intepolation for `env` of `services` (#47)

Reviewed-on: https://gitea.com/gitea/act/pulls/47
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>

* Support services `credentials` (#51)

If a service's image is from a container registry requires authentication, `act_runner` will need `credentials` to pull the image, see [documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idcredentials).
Currently, `act_runner` incorrectly uses the `credentials` of `containers` to pull services' images and the `credentials` of services won't be used, see the related code: 0c1f2edb99/pkg/runner/run_context.go (L228-L269)

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/51
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>

* Add ContainerMaxLifetime and ContainerNetworkMode options

from: b9c20dcaa4

* Fix container network issue (#56)

Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177

- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if  `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
  - If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`.  Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
  - Because on the one hand,  `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
  - On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore  the stage of `docker start`, because the name of these containers and netwoks won't be repeat.

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>

* Check volumes (#60)

This PR adds a `ValidVolumes` config. Users can specify the volumes (including bind mounts) that can be mounted to containers by this config.

Options related to volumes:
- [jobs.<job_id>.container.volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainervolumes)
- [jobs.<job_id>.services.<service_id>.volumes](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idvolumes)

In addition, volumes specified by `options` will also be checked.

Currently, the following default volumes (see a72822b3f8/pkg/runner/run_context.go (L116-L166)) will be added to `ValidVolumes`:
- `act-toolcache`
- `<container-name>` and `<container-name>-env`
- `/var/run/docker.sock` (We need to add a new configuration to control whether the docker daemon can be mounted)

Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/60
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>

* Remove ContainerMaxLifetime; fix lint

* Remove unused ValidVolumes

* Remove ConnectToNetwork

* Add docker stubs

* Close docker clients to prevent file descriptor leaks

* Fix the error when removing network in self-hosted mode (#69)

Fixes https://gitea.com/gitea/act_runner/issues/255

Reviewed-on: https://gitea.com/gitea/act/pulls/69
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>

* Move service container and network cleanup to rc.cleanUpJobContainer

* Add --network flag; default to host if not using service containers or set explicitly

* Correctly close executor to prevent fd leak

* Revert to tail instead of full path

* fix network duplication

* backport networkingConfig for aliaes

* don't hardcode netMode host

* Convert services test to table driven tests

* Add failing tests for services

* Expose service container ports onto the host

* Set container network mode in artifacts server test to host mode

* Log container network mode when creating/starting a container

* fix: Correctly handle ContainerNetworkMode

* fix: missing service container network

* Always remove service containers

Although we usually keep containers running if the workflow errored
(unless `--rm` is given) in order to facilitate debugging and we have
a flag (`--reuse`) to always keep containers running in order to speed
up repeated `act` invocations, I believe that these should only apply
to job containers and not service containers, because changing the
network settings on a service container requires re-creating it anyway.

* Remove networks only if no active endpoints exist

* Ensure job containers are stopped before starting a new job

* fix: go build -tags WITHOUT_DOCKER

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: ZauberNerd <zaubernerd@zaubernerd.de>
2023-10-19 09:24:52 +00:00
Jon Jensen
ace4cd47c7 Fix float formatting (#2018)
Format floats the same way as actions/runner (precision 15, remove
trailing zeroes)

See: 67d70803a9/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/NumberToken.cs (L34)
2023-10-13 20:01:04 +00:00
dependabot[bot]
99067a9c1e build(deps): bump golang.org/x/net from 0.15.0 to 0.17.0 (#2045)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 23:30:36 +00:00
dependabot[bot]
e7e158cd7e build(deps): bump github.com/docker/distribution (#2037)
Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.2+incompatible to 2.8.3+incompatible.
- [Release notes](https://github.com/docker/distribution/releases)
- [Commits](https://github.com/docker/distribution/compare/v2.8.2...v2.8.3)

---
updated-dependencies:
- dependency-name: github.com/docker/distribution
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-09 02:52:56 +00:00
dependabot[bot]
3c730d7924 build(deps): bump golang.org/x/term from 0.12.0 to 0.13.0 (#2036)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/term/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 02:35:38 +00:00
ChristopherHX
976df8bae5 fix action_ref (composite action) (#2020)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-03 23:13:05 +00:00
ChristopherHX
7c7d80ebdd fix: use actions/runner hashfiles in container (#1940)
* fix: use actions/runner hashfiles in container

Previously hashfiles ran on the host,
this don't work for container generated content

* fix: lint

* fix: lint

* fix assign follow symlink flag

Co-authored-by: Jason Song <i@wolfogre.com>

---------

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-03 22:56:18 +00:00
ChristopherHX
2f479ba024 Fix image survey for large images (#2022)
ubuntu 22.04 based large image is now available

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-03 22:39:49 +00:00
Diego Sousa
5718555f7a [ Variables ] - Add missing documentation for repository variables (#2032)
* docs: 📝 add vars instructions by cli and file loading

* docs: 📝 adjust varibles instructions
2023-10-03 22:02:22 +00:00
GitHub Actions
44ea01c209 chore: bump VERSION to 0.2.52 2023-10-01 02:15:05 +00:00
dependabot[bot]
2be4def7be build(deps): bump github.com/rhysd/actionlint from 1.6.25 to 1.6.26 (#2026)
Bumps [github.com/rhysd/actionlint](https://github.com/rhysd/actionlint) from 1.6.25 to 1.6.26.
- [Release notes](https://github.com/rhysd/actionlint/releases)
- [Changelog](https://github.com/rhysd/actionlint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rhysd/actionlint/compare/v1.6.25...v1.6.26)

---
updated-dependencies:
- dependency-name: github.com/rhysd/actionlint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-09-25 03:24:27 +00:00
dependabot[bot]
3d47885894 build(deps): bump megalinter/megalinter from 7.3.0 to 7.4.0 (#2025)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.3.0...v7.4.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 03:06:54 +00:00
GitHub Actions
c241ecda31 chore: bump VERSION to 0.2.51 2023-09-23 11:36:33 +00:00
dependabot[bot]
b637d79ec3 build(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.9.0 (#2011)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.8.1 to 5.9.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.8.1...v5.9.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-09-18 03:47:27 +00:00
dependabot[bot]
83af8f8767 build(deps): bump github.com/opencontainers/image-spec (#2010)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc4 to 1.1.0-rc5.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/compare/v1.1.0-rc4...v1.1.0-rc5)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-09-18 03:34:14 +00:00
dependabot[bot]
60060a7c9c build(deps): bump gotest.tools/v3 from 3.5.0 to 3.5.1 (#2009)
Bumps [gotest.tools/v3](https://github.com/gotestyourself/gotest.tools) from 3.5.0 to 3.5.1.
- [Release notes](https://github.com/gotestyourself/gotest.tools/releases)
- [Commits](https://github.com/gotestyourself/gotest.tools/compare/v3.5.0...v3.5.1)

---
updated-dependencies:
- dependency-name: gotest.tools/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-09-18 03:20:50 +00:00
dependabot[bot]
d134079807 build(deps): bump github.com/go-git/go-billy/v5 from 5.4.1 to 5.5.0 (#2012)
Bumps [github.com/go-git/go-billy/v5](https://github.com/go-git/go-billy) from 5.4.1 to 5.5.0.
- [Release notes](https://github.com/go-git/go-billy/releases)
- [Commits](https://github.com/go-git/go-billy/compare/v5.4.1...v5.5.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-billy/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 03:02:39 +00:00
dependabot[bot]
1891bef433 build(deps): bump docker/setup-qemu-action from 2 to 3 (#2007)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-09-18 02:33:21 +00:00
dependabot[bot]
2911b2172c build(deps): bump goreleaser/goreleaser-action from 4 to 5 (#2008)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 4 to 5.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 02:15:28 +00:00
Oliver Bell
935e37c25b feat: support node20 runtime (#1988)
* feat: support node20

* fix

* more fixes

* maybe final fixes?

* format
2023-09-15 02:24:46 +00:00
Elian Doran
19764bcb06 feat: support interpolation in <job>.container.options (#1958) 2023-09-12 06:35:25 -07:00
dependabot[bot]
c84a3ef6d0 build(deps): bump github.com/docker/docker (#2000)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.5+incompatible to 24.0.6+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.5...v24.0.6)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-09-11 02:53:05 +00:00
dependabot[bot]
bd9032de0a build(deps): bump github.com/docker/cli (#1999)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 24.0.5+incompatible to 24.0.6+incompatible.
- [Commits](https://github.com/docker/cli/compare/v24.0.5...v24.0.6)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 02:39:24 +00:00
dependabot[bot]
1d32507b52 build(deps): bump golang.org/x/term from 0.11.0 to 0.12.0 (#1992)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/term/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 02:56:21 +00:00
GitHub Actions
80b0955303 chore: bump VERSION to 0.2.50 2023-09-01 02:12:28 +00:00
dependabot[bot]
0c12273eba build(deps): bump github.com/moby/buildkit from 0.12.1 to 0.12.2 (#1986)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.12.1 to 0.12.2.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.12.1...v0.12.2)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 02:48:43 +00:00
dependabot[bot]
323bee9ab5 build(deps): bump github.com/moby/patternmatcher from 0.5.0 to 0.6.0 (#1985)
Bumps [github.com/moby/patternmatcher](https://github.com/moby/patternmatcher) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/moby/patternmatcher/releases)
- [Commits](https://github.com/moby/patternmatcher/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/moby/patternmatcher
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 02:33:21 +00:00
Nathan Shaaban
7286b43b0e fix: fail if no stages were found (#1970)
* fix: fail if no stages were found

Adds a warning message if act is cannot find any stages to run
with the filters provided.

Reproduction:
- run `act -j gibberish`

Desired behavior: some indication I did something silly
Actual behavior: no output, just exit with success.

As a human who often makes spelling mistakes,
it would be nice if act warned me what I was doing that was silly
rather than exiting apparently doing
nothing with no obvious indication
I did something wrong.

* Revert "fix: fail if no stages were found"

This reverts commit 226adf1c15cf4c01d516a05dc923507e6999978d.

* fix: fail if no stages were found

Errors if no stages were found with the given filters.
Prints out a helpful error message, pointing users
in the right place for how to specify which stage to run.

Reproduction:
- run `act -j gibberish`

Desired behavior: some indication I did something silly
Actual behavior: no output, just exit with success.

As a human who often makes spelling mistakes,
it would be nice if act warned me what I was doing that was silly
rather than exiting apparently doing
nothing with no obvious indication
I did something wrong.
2023-08-21 17:53:47 +00:00
dependabot[bot]
f64c267dac build(deps): bump golangci/golangci-lint-action from 3.6.0 to 3.7.0 (#1978)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.6.0...v3.7.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-21 03:16:32 +00:00
ChristopherHX
7ba9f30f37 Mention act user guide in act (#1973)
* CONTRIBUTING.md mention act user guide

* Mention the act user guide in the README
2023-08-17 14:43:17 +00:00
dependabot[bot]
2a0a0a1a62 build(deps): bump megalinter/megalinter from 7.2.1 to 7.3.0 (#1965)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.2.1 to 7.3.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.2.1...v7.3.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 02:19:39 +00:00
sitiom
f55ae1a0bc ci: change winget job runner to ubuntu-latest (#1959)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-11 06:14:41 +00:00
TheFox0x7
9f06ca75e4 change podman socket path (#1961)
add podman user socket

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-11 05:33:53 +00:00
Elian Doran
a00fd960a5 Fix "Unknown server OS" for Docker container --device option (#1957)
The `--device` option would do platform-dependent validation, but the
OS was not passed as an argument. When a user added the `--device` option
to the container, it would result in a "Unknown server OS" error.
2023-08-09 18:21:05 +00:00
Eng Zer Jun
8a9e4f9f38 refactor: remove unnecessary nil check in RunContext (#1955)
From the Go docs:

  "For a nil slice, the number of iterations is 0" [1]

Therefore, an additional nil check for `job.RunsOn()` before the loop is
unnecessary because `job.RunsOn()` returns a `[]string`.

[1]: https://go.dev/ref/spec#For_range

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-08-09 12:41:12 +00:00
ChristopherHX
a42f3cf1cd feat: Add new Action Cache (#1913)
* feat: Add new Action Cache

* fix some linter errors / warnings

* fix lint

* fix empty fpath parameter returns empty archive

* rename fpath to includePrefix
2023-08-08 16:07:23 +00:00
ChristopherHX
83140951bf feat: cmd support for windows (#1941)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-08 15:44:25 +00:00
ChristopherHX
6468dd7fc8 feat: Add CopyTarStream to Container Interface (#1912)
* feat: CopyTarStream

Prepare for new process and thread safe action cache

* fix unused param

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-08 15:18:25 +00:00
ChristopherHX
f0ca0abc40 refactor: docker build BuildContext field (#1914)
The old Container input parameter was not flexible enough

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-08 15:02:11 +00:00
ChristopherHX
73d5f78294 Shorten or/and in expressions (#1939)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-08 14:30:13 +00:00
ChristopherHX
0b4c67a4aa prefer pwsh on windows if found (#1942)
* prefer pwsh on windows if found

prefer bash over sh if found

One windows test no longer defines a default shell to test if it's pwsh

* add dep

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-08 14:14:46 +00:00
dependabot[bot]
3939f48e6d build(deps): bump megalinter/megalinter from 7.1.0 to 7.2.1 (#1931)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.1.0 to 7.2.1.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/oxsecurity/megalinter/compare/v7.1.0...v7.2.1)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
dependency-type: direct:production
update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 13:37:31 +00:00
dependabot[bot]
74b74e847b build(deps): bump golang.org/x/term from 0.10.0 to 0.11.0 (#1948)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/term/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-08-07 02:33:09 +00:00
dependabot[bot]
c8127155bc build(deps): bump github.com/moby/buildkit from 0.12.0 to 0.12.1 (#1947)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.12.0...v0.12.1)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 02:17:55 +00:00
GitHub Actions
d5d8548546 chore: bump VERSION to 0.2.49 2023-08-01 02:21:38 +00:00
dependabot[bot]
8bf10cf876 build(deps): bump github.com/go-git/go-git/v5 from 5.8.0 to 5.8.1 (#1934)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.8.0 to 5.8.1.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.8.0...v5.8.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-07-31 03:49:57 +00:00
dependabot[bot]
8e6c7c11fe build(deps): bump github.com/docker/cli (#1932)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 24.0.4+incompatible to 24.0.5+incompatible.
- [Commits](https://github.com/docker/cli/compare/v24.0.4...v24.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-07-31 03:29:28 +00:00
dependabot[bot]
d720ff09a2 build(deps): bump github.com/docker/docker (#1933)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.5-0.20230714235725-36e9e796c6fc+incompatible to 24.0.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/commits/v24.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-31 03:12:43 +00:00
dependabot[bot]
17bf4fc5af build(deps): bump github.com/go-git/go-git/v5 from 5.7.0 to 5.8.0 (#1925)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.7.0...v5.8.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 02:53:35 +00:00
taytzehao
67f4baa618 local runner doc (#1911)
* local runner doc

* correct would would

---------

Co-authored-by: tzehaoo <tzehao@intnt.ai>
Co-authored-by: Casey Lee <cplee@nektos.com>
2023-07-21 16:56:11 +00:00
Thomas Eddy
83b0a5b1f2 Add new CLI flag to log just the job id and not the entire job name (#1920)
* Add new CLI flag to log just the job id and not the entire job name

* Up the action test timeout to 20m from 15m
2023-07-19 21:45:44 +00:00
dependabot[bot]
4810f69367 build(deps): bump github.com/moby/buildkit from 0.11.6 to 0.12.0 (#1917)
update moby/moby to snapshot
2023-07-17 19:55:17 +00:00
dependabot[bot]
e343ea9d5f build(deps): bump golang.org/x/term from 0.9.0 to 0.10.0 (#1906)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/term/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-11 15:14:01 +00:00
Casey Lee
94bc8b319c Bump dockercli (#1905)
* updates to support newer version of docker sdk

Bumps [github.com/docker/cli](https://github.com/docker/cli) from 24.0.2+incompatible to 24.0.4+incompatible.
- [Commits](https://github.com/docker/cli/compare/v24.0.2...v24.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: upgrade to go 1.20

* feat: upgrade to go 1.20

* chore: use go version from go.mod

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 21:55:53 -07:00
Josh McCullough
808cf5a2c3 throw when invalid uses key is provided (#1804)
* throw if `uses` is invalid

* update JobType to return error

* lint

* put //nolint:dupl on wrong test

* update error message to remove end punctuation

* lint

* update remote job type check

* move if statement

* rm nolint:dupl ... we'll see how that goes

---------

Co-authored-by: Casey Lee <cplee@nektos.com>
2023-07-10 21:27:43 -07:00
Jason Song
8c7c0f53c1 fix: handle zero size (#1888) 2023-07-10 20:35:27 -07:00
Casey Lee
724ec918c9 chore: upgrade golangci-lint and address findings (#1904) 2023-07-10 17:12:12 -07:00
dependabot[bot]
79f93beef2 build(deps): bump golangci/golangci-lint-action from 3.5.0 to 3.6.0 (#1868)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Casey Lee <cplee@nektos.com>
2023-07-10 23:55:19 +00:00
dependabot[bot]
70956f2929 build(deps): bump gotest.tools/v3 from 3.4.0 to 3.5.0 (#1892)
Bumps [gotest.tools/v3](https://github.com/gotestyourself/gotest.tools) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/gotestyourself/gotest.tools/releases)
- [Commits](https://github.com/gotestyourself/gotest.tools/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: gotest.tools/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Casey Lee <cplee@nektos.com>
2023-07-10 16:22:50 -07:00
Reisen Usagi
ef79bb284d Normalize path outputs emitted by the artifact server download endpoint (#1898)
Co-authored-by: Casey Lee <cplee@nektos.com>
2023-07-10 16:19:30 -07:00
GitHub Actions
3a0a6425a8 chore: bump VERSION to 0.2.48 2023-07-10 20:03:46 +00:00
dependabot[bot]
4c8da8558d build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1884)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 17:51:29 +00:00
dependabot[bot]
f40d0b873d build(deps): bump github.com/opencontainers/image-spec (#1893)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc.3 to 1.1.0-rc4.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/compare/v1.1.0-rc3...v1.1.0-rc4)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Casey Lee <cplee@nektos.com>
2023-07-10 17:31:59 +00:00
Jason Song
15618d1187 Remove archives.replacements in goreleaser.yaml (#1895)
* fix: update name template in goreleaser

* fix: add arm version

* fix: space

* fix: --clean

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-07-10 17:15:16 +00:00
ChristopherHX
e597046195 refactor: open boltdb only while using it (#1879)
* refactor: open boltdb only while using it

* patch

* Update handler_test.go

* Update handler_test.go

* Update handler_test.go

* Update handler.go

* timeout * 10

* pr feedback

* fixup
2023-07-10 16:57:06 +00:00
GitHub Actions
310cb79e81 chore: bump VERSION to 0.2.47 2023-07-01 02:34:27 +00:00
Sam Foo
e60018a6d9 Allow inputs for workflow_calls (#1845) 2023-06-27 17:32:04 +00:00
dependabot[bot]
70e1e37280 build(deps): bump github.com/opencontainers/image-spec (#1883)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc2.0.20221005185240-3a7f492d3f1b to 1.1.0-rc.3.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc3)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 04:00:05 +00:00
dependabot[bot]
3d1d8a9aca build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1882)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 03:43:21 +00:00
dependabot[bot]
f9dcb58db2 build(deps): bump github.com/opencontainers/image-spec (#1881)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc2.0.20221005185240-3a7f492d3f1b to 1.1.0-rc.3.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc3)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 03:27:46 +00:00
dependabot[bot]
a0307d3b7c build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1880)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 03:12:03 +00:00
dependabot[bot]
4fc176f556 build(deps): bump github.com/rhysd/actionlint from 1.6.24 to 1.6.25 (#1870)
Bumps [github.com/rhysd/actionlint](https://github.com/rhysd/actionlint) from 1.6.24 to 1.6.25.
- [Release notes](https://github.com/rhysd/actionlint/releases)
- [Changelog](https://github.com/rhysd/actionlint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rhysd/actionlint/compare/v1.6.24...v1.6.25)

---
updated-dependencies:
- dependency-name: github.com/rhysd/actionlint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-19 04:18:27 +00:00
dependabot[bot]
df44dffd30 build(deps): bump github.com/AlecAivazis/survey/v2 from 2.3.6 to 2.3.7 (#1872)
Bumps [github.com/AlecAivazis/survey/v2](https://github.com/AlecAivazis/survey) from 2.3.6 to 2.3.7.
- [Release notes](https://github.com/AlecAivazis/survey/releases)
- [Commits](https://github.com/AlecAivazis/survey/compare/v2.3.6...v2.3.7)

---
updated-dependencies:
- dependency-name: github.com/AlecAivazis/survey/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-19 04:03:04 +00:00
dependabot[bot]
b2191ae204 build(deps): bump golang.org/x/term from 0.8.0 to 0.9.0 (#1869)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/term/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-19 03:30:12 +00:00
dependabot[bot]
ef608854d0 build(deps): bump github.com/opencontainers/image-spec (#1871)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc2.0.20221005185240-3a7f492d3f1b to 1.1.0-rc.3.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc3)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 03:12:23 +00:00
Marius Zwicker
74c27db4dd Allow to override location of action cache dir (#1863)
Adds an option to specify the directory below which actions
and host workspaces will be stored. If left empty the previous
location at $XDG_CACHE_HOME/act or $HOME/.cache/act will be used
respectively.

Co-authored-by: Casey Lee <cplee@nektos.com>
2023-06-15 01:16:00 +00:00
Jason Song
24348ff1ee Drop disappeared wei/curl@v1 (#1864)
* chore: trigger actions

* fix: use curl command directly

* fix: use node:16-buster

* fix: remove --fail-with-body

* chore: format codes
2023-06-13 21:24:31 +00:00
dependabot[bot]
b92d95f899 build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1858)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-12 04:28:49 +00:00
dependabot[bot]
3555d65d08 build(deps): bump megalinter/megalinter from 7.0.4 to 7.1.0 (#1857)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.0.4 to 7.1.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.0.4...v7.1.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-12 03:33:28 +00:00
dependabot[bot]
e912ab32c2 build(deps): bump github.com/mattn/go-isatty from 0.0.18 to 0.0.19 (#1859)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.18 to 0.0.19.
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.18...v0.0.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 03:13:51 +00:00
Troy Gaines
80f6de51d5 fix: spelling mistake in readme (#1854)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-10 18:42:54 +00:00
Kuan Yong
6ce45e3f24 feature: Add support for github action variables (#1833)
* feature: Add support for github action variables

* add --var flag for github variables

* unitests: Updated unittests to cover vars context.

* Remove syntax extension for vars and correct unit tests

* Update pkg/runner/expression.go

Co-authored-by: ChristopherHX <christopher.homberger@web.de>

---------

Co-authored-by: kuanyong-wong-partior <kuanyong.wong@partior.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-06-10 18:09:27 +00:00
psa
3ac2b726f2 Fix bug in processing jobs on platforms without Docker (#1834)
* Log incoming jobs.

Log the full contents of the job protobuf to make debugging jobs easier

* Ensure that the parallel executor always uses at least one thread.

The caller may mis-calculate the number of CPUs as zero, in which case
ensure that at least one thread is spawned.

* Use runtime.NumCPU for CPU counts.

For hosts without docker, GetHostInfo() returns a blank struct which
has zero CPUs and causes downstream trouble.

---------

Co-authored-by: Paul Armstrong <psa@users.noreply.gitea.com>
Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-06 03:00:54 +00:00
dependabot[bot]
c70a6743f6 build(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.4 (#1842)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-05 04:46:40 +00:00
dependabot[bot]
04b5c739d2 build(deps): bump megalinter/megalinter from 7.0.2 to 7.0.4 (#1838)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 7.0.2 to 7.0.4.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v7.0.2...v7.0.4)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-05 04:27:58 +00:00
dependabot[bot]
e48c07988e build(deps): bump github.com/docker/cli (#1843)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 24.0.1+incompatible to 24.0.2+incompatible.
- [Commits](https://github.com/docker/cli/compare/v24.0.1...v24.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-05 04:06:57 +00:00
dependabot[bot]
0b05ee8e0b build(deps): bump github.com/imdario/mergo from 0.3.15 to 0.3.16 (#1840)
Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.15 to 0.3.16.
- [Release notes](https://github.com/imdario/mergo/releases)
- [Commits](https://github.com/imdario/mergo/compare/v0.3.15...v0.3.16)

---
updated-dependencies:
- dependency-name: github.com/imdario/mergo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-05 03:51:00 +00:00
dependabot[bot]
e150310da7 build(deps): bump github.com/sirupsen/logrus from 1.9.2 to 1.9.3 (#1841)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.2 to 1.9.3.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.2...v1.9.3)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-05 03:35:57 +00:00
dependabot[bot]
34db86138b build(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.5.0 (#1839)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 03:13:17 +00:00
GitHub Actions
b0d0cec71f chore: bump VERSION to 0.2.46 2023-06-01 02:39:05 +00:00
dependabot[bot]
26f1b1f4b6 build(deps): bump github.com/opencontainers/image-spec (#1829)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc2.0.20221005185240-3a7f492d3f1b to 1.1.0-rc.3.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc3)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-31 17:36:12 +00:00
dependabot[bot]
9c1f1f8d84 build(deps): bump github.com/docker/cli (#1816)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.6+incompatible to 24.0.1+incompatible.
- [Commits](https://github.com/docker/cli/compare/v23.0.6...v24.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-31 15:43:03 +00:00
Joseph Mearman
3cfc2cf9c3 add instruction for using gh auth token (#1831) 2023-05-31 14:24:24 +00:00
dependabot[bot]
b0996e0577 build(deps): bump github.com/go-git/go-git/v5 (#1830)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.6.2-0.20230411180853-ce62f3e9ff86 to 5.7.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/commits/v5.7.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-29 03:35:06 +00:00
dependabot[bot]
481999f59d build(deps): bump megalinter/megalinter from 6.22.2 to 7.0.2 (#1827)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.22.2 to 7.0.2.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.22.2...v7.0.2)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 03:12:45 +00:00
ab-pkandhari
11dd2ac745 fix: Update ARCH environment variable used in runners/actions (#1818) 2023-05-23 12:26:47 +00:00
dependabot[bot]
16c574cd26 build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1817)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-22 04:02:38 +00:00
dependabot[bot]
06054d2931 build(deps): bump github.com/sirupsen/logrus from 1.9.0 to 1.9.2 (#1814)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.0 to 1.9.2.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.0...v1.9.2)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-22 03:29:31 +00:00
dependabot[bot]
1371215aab build(deps): bump codecov/codecov-action from 3.1.3 to 3.1.4 (#1813)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.1.3...v3.1.4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 03:12:14 +00:00
dependabot[bot]
1eacf23dcb build(deps): bump github.com/opencontainers/image-spec (#1807)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc2.0.20221005185240-3a7f492d3f1b to 1.1.0-rc.3.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc3)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-15 03:43:06 +00:00
dependabot[bot]
c00810bdf4 build(deps): bump github.com/docker/docker (#1806)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.5+incompatible to 23.0.6+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.5...v23.0.6)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-15 03:28:21 +00:00
dependabot[bot]
39abbce4e6 build(deps): bump github.com/docker/cli (#1805)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.5+incompatible to 23.0.6+incompatible.
- [Commits](https://github.com/docker/cli/compare/v23.0.5...v23.0.6)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-15 03:12:21 +00:00
dependabot[bot]
b5fa24537f build(deps): bump github.com/docker/distribution (#1801)
Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.1+incompatible to 2.8.2+incompatible.
- [Release notes](https://github.com/docker/distribution/releases)
- [Commits](https://github.com/docker/distribution/compare/v2.8.1...v2.8.2)

---
updated-dependencies:
- dependency-name: github.com/docker/distribution
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-11 21:19:02 +00:00
dependabot[bot]
046f5aa715 build(deps): bump github.com/cloudflare/circl from 1.1.0 to 1.3.3 (#1800)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.1.0 to 1.3.3.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.1.0...v1.3.3)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-11 21:00:01 +00:00
dependabot[bot]
65ef31f102 build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1791)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-08 03:15:32 +00:00
benbaker76
f84a566ded Replace backslash in GetActPath() for Windows (#1777)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-03 19:12:36 +00:00
ChristopherHX
8913375af8 feat: implement steps.timeout-minutes (#1776)
* feat: implement steps.timeout-minutes

* Add imports

* refactor code

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-03 18:08:11 +00:00
ChristopherHX
ca9b783491 fix: don't allow -self-hosted mode as container image (#1783)
* fix: don't allow `-self-hosted` mode as container image

* fix: jobcontainer in hostmode platform

* Update run_context.go

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-03 17:49:17 +00:00
ChristopherHX
568124ca69 Refactor evaluate yaml node do not alter nested nodes (#1761)
* refactor: EvaluateYamlNode do not alter nested nodes

* fix build error

* fix op

* fix lint

* ...

* fixup

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-03 17:26:28 +00:00
ChristopherHX
aa21277380 fix: fallback to unauthenticated pull (#1774)
* fix: fallback to unauthenticated pull

* move logger def

* fixup

* add import

* .

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-03 16:44:26 +00:00
Shubh Bapna
4721abfa6d fix: remove hardcoded reference to github.com when using reusable remote workflows and remote actions (#1784)
* fix filename for remote reusable workflow and remove hardcoded reference to github.com

* remove hardcoded reference to github.com for remote action
2023-05-03 15:46:28 +00:00
dependabot[bot]
3eb6e83ea4 build(deps): bump github.com/docker/cli (#1780)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.4+incompatible to 23.0.5+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v23.0.4...v23.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-01 03:49:17 +00:00
dependabot[bot]
15eb1fa92a build(deps): bump github.com/opencontainers/image-spec (#1781)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc2.0.20221005185240-3a7f492d3f1b to 1.1.0-rc.3.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc3)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-01 03:30:50 +00:00
dependabot[bot]
1f9bbe12dd build(deps): bump github.com/docker/docker (#1779)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.4+incompatible to 23.0.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.4...v23.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 03:14:03 +00:00
GitHub Actions
f1df2ca5d6 chore: bump VERSION to 0.2.45 2023-05-01 02:18:34 +00:00
Jason Song
d77991c95a Support cache (#1770)
* feat: port

* fix: use httprouter

* fix: WriteHeader

* fix: bolthold

* fix: bugs

* chore: one less file

* test: test handler

* fix: bug in id

* test: fix cases

* chore: tidy

* fix: use atomic.Int32

* fix: use atomic.Store

* feat: support close

* chore: lint

* fix: cache keys are case insensitive

* fix: options

* fix: use options

* fix: close

* fix: ignore close error

* Revert "fix: close"

This reverts commit d53ea7568ba03908eb153031c435008fd47e7ccb.

* fix: cacheUrlKey

* fix: nil close

* chore: lint code

* fix: test key

* test: case insensitive

* chore: lint
2023-04-28 15:57:40 +00:00
Markus Wolf
c81a770bc5 chore: run act from cli on linux (#1758)
* chore: run act from cli on linux

To prevent issues like #1756 in the future, we need to
run act from the cli through all the root setup code.
This ensures that the basic CLI setup can succeed.

Co-authored-by: Björn Brauer <zaubernerd@zaubernerd.de>

* chore: set platform spec to use

---------

Co-authored-by: Björn Brauer <zaubernerd@zaubernerd.de>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-04-25 17:32:34 +00:00
ChristopherHX
7cbb1a910e Revert breaking docker socket changes (#1763)
* fix: rework docker socket changes

* fixup

* fixup

* fixes

* patch

* ...

* lint

* Fix docker outputs windows

* fix type

* Revert containerDaemonSocket breaking change

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-25 16:31:17 +00:00
Zettat123
baf3bcf48b avoid using log.Fatal (#1759) 2023-04-25 02:09:54 +00:00
dependabot[bot]
2ea7891787 build(deps): bump github.com/moby/buildkit from 0.11.5 to 0.11.6 (#1755)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-24 04:02:30 +00:00
dependabot[bot]
16f35f64eb build(deps): bump github.com/docker/docker (#1754)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.3+incompatible to 23.0.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.3...v23.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-24 03:46:51 +00:00
dependabot[bot]
ded31bdbc0 build(deps): bump github.com/docker/cli (#1753)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.3+incompatible to 23.0.4+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v23.0.3...v23.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-24 03:28:43 +00:00
dependabot[bot]
60bcfb5d4f build(deps): bump codecov/codecov-action from 3.1.2 to 3.1.3 (#1752)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-24 03:13:14 +00:00
R
7c6237d93f ci: deduplicate running workflows (#1751)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-23 19:18:38 +00:00
R
b6718fdf5d fix: try finding a socket, otherwise fail, respect user choice (#1745)
* fix: try finding a socket, otherwise fail, respect user choice

* Update cmd/root.go

Co-authored-by: Jason Song <i@wolfogre.com>

* Update cmd/root.go

Co-authored-by: Jason Song <i@wolfogre.com>

---------

Co-authored-by: Jason Song <i@wolfogre.com>
2023-04-23 19:02:56 +00:00
Jason Song
3715266494 Improve watchAndRun (#1743)
* fix: improve watchAndRun

* fix: lint

* fix: lint

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-19 04:19:40 +00:00
M.Yamashita
35d6e9f71e Remove the comment-out code. (#1691)
Co-authored-by: Jason Song <i@wolfogre.com>
2023-04-19 03:46:00 +00:00
Andy Wang
d970056601 typo: fix expression of warning message on macOS (#1693)
Co-authored-by: Jason Song <i@wolfogre.com>
2023-04-19 03:00:33 +00:00
ChristopherHX
9884da0122 fix: environment handling windows (host mode) (#1732)
* fix: environment handling windows (host mode)

* fixup

* fixup

* add more tests

* fixup

* fix setenv

* fixes

* [skip ci] Apply suggestions from code review

Co-authored-by: Jason Song <i@wolfogre.com>

* Update side effects

---------

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-18 18:09:57 +00:00
Björn Brauer
de0644499a fix: ensure networkmode "host" unless explicitly specified (#1739)
act defaults network mode to "host", but when `--container-options` are
passed on the CLI, it uses the docker CLI options parser, which fills
empty values with defaults, in which case network mode is set to
"default".
Unless the user explicitly sets `--container-options="--network=xxx"`,
we should always default to "host", to keep act's behaviour.

Co-authored-by: Markus Wolf <markus.wolf@new-work.se>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-18 14:37:59 +00:00
Jason Song
816a7d410a Avoid using log.Fatal in pkg/* (#1705)
* fix: common

* fix: in runner

* fix: decodeNode

* fix: GetMatrixes

* Revert "fix: common"

This reverts commit 6599803b6ae3b7adc168ef41b4afd4d89fc22f34.

* fix: GetOutboundIP

* test: fix cases

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-18 14:17:36 +00:00
ChristopherHX
50dcc57e4b feat: support yaml env/secrets/inputs file (#1733)
* support yaml env/secrets/inputs file

* Update root.go

* read the docs again..

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-18 13:35:31 +00:00
dependabot[bot]
6d527bf1a3 build(deps): bump codecov/codecov-action from 3.1.1 to 3.1.2 (#1735)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 03:13:12 +00:00
ChristopherHX
97749a27b9 fix: ghc assignment typo (#1729)
* fix: ghc assignment typo

* fixup server_url
2023-04-13 14:09:29 +00:00
ChristopherHX
d70b225e85 fix: reusable workflow panic (#1728)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-13 13:47:59 +00:00
Markus Wolf
c0130ed030 fix: add server_url attribute to github context (#1727)
* fix: add `server_url` attribute to github context

The `server_urL` attribute was missing in the `github` context.
Previously it was exposed as environment variable only.

Closes #1726

* fix: also set `api_url` and `graphql_url` attributes
2023-04-13 13:09:28 +00:00
R
148a545021 build(deps): update go-git to ce62f3e9ff86270538a514a68d3bd5563a733e3b (#1725) 2023-04-11 21:39:36 +00:00
dependabot[bot]
65a925b4ed build(deps): bump github.com/docker/docker (#1719)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.2+incompatible to 23.0.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.2...v23.0.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-10 19:47:01 +00:00
dependabot[bot]
c5ce502f9f build(deps): bump github.com/spf13/cobra from 1.6.1 to 1.7.0 (#1724)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-10 18:58:51 +00:00
dependabot[bot]
f2bd194c7f build(deps): bump github.com/docker/cli (#1722)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.2+incompatible to 23.0.3+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v23.0.2...v23.0.3)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-10 04:05:09 +00:00
dependabot[bot]
4ab812c1b1 build(deps): bump megalinter/megalinter from 6.22.1 to 6.22.2 (#1720)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.22.1 to 6.22.2.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.22.1...v6.22.2)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-10 03:50:21 +00:00
dependabot[bot]
5a331d2d99 build(deps): bump github.com/rhysd/actionlint from 1.6.23 to 1.6.24 (#1721)
Bumps [github.com/rhysd/actionlint](https://github.com/rhysd/actionlint) from 1.6.23 to 1.6.24.
- [Release notes](https://github.com/rhysd/actionlint/releases)
- [Changelog](https://github.com/rhysd/actionlint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rhysd/actionlint/compare/v1.6.23...v1.6.24)

---
updated-dependencies:
- dependency-name: github.com/rhysd/actionlint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-10 03:33:33 +00:00
dependabot[bot]
3ae0a9dfb7 build(deps): bump golang.org/x/term from 0.6.0 to 0.7.0 (#1723)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-10 03:12:55 +00:00
dependabot[bot]
24b04dfa55 build(deps): bump github.com/docker/cli (#1712)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.1+incompatible to 23.0.2+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v23.0.1...v23.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-08 15:23:08 +00:00
dependabot[bot]
68c72b9a51 build(deps): bump github.com/docker/docker (#1711)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.1+incompatible to 23.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.1...v23.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-03 03:49:06 +00:00
dependabot[bot]
ad7a8a3559 build(deps): bump megalinter/megalinter from 6.21.0 to 6.22.1 (#1710)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.21.0 to 6.22.1.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.21.0...v6.22.1)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-03 03:13:25 +00:00
GitHub Actions
220d6f1251 chore: bump VERSION to 0.2.44 2023-04-01 02:17:52 +00:00
dependabot[bot]
6745999c10 build(deps): bump github.com/opencontainers/runc from 1.1.3 to 1.1.5 (#1708)
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.3 to 1.1.5.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/v1.1.5/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.1.3...v1.1.5)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-31 13:43:08 +00:00
Jason Song
8518d70bdf feat: improve GetOutboundIP (#1707)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-31 13:28:08 +00:00
Jason Song
d3dfde055a fix: use os.UserHomeDir (#1706)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-31 13:08:46 +00:00
Kris
75ffa205c4 Make sure working directory is respected when configured from matrix (#1686)
* Make sure working directory is respected when configured from matrix

* Fix regression by setting Workingdirectory on stepRun instead of step or too early
2023-03-28 12:24:03 +00:00
dependabot[bot]
351ae99bc1 build(deps): bump github.com/moby/buildkit from 0.11.4 to 0.11.5 (#1703)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.4 to 0.11.5.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.4...v0.11.5)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-27 04:23:12 +00:00
dependabot[bot]
385d71a215 build(deps): bump github.com/mattn/go-isatty from 0.0.17 to 0.0.18 (#1702)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.17 to 0.0.18.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.17...v0.0.18)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-27 04:04:40 +00:00
dependabot[bot]
f764ecb283 build(deps): bump github.com/imdario/mergo from 0.3.14 to 0.3.15 (#1701)
Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.14 to 0.3.15.
- [Release notes](https://github.com/imdario/mergo/releases)
- [Commits](https://github.com/imdario/mergo/compare/v0.3.14...v0.3.15)

---
updated-dependencies:
- dependency-name: github.com/imdario/mergo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-27 03:45:30 +00:00
dependabot[bot]
d65cf869e6 build(deps): bump actions/stale from 7 to 8 (#1700)
Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-27 03:29:53 +00:00
dependabot[bot]
775a128cd4 build(deps): bump megalinter/megalinter from 6.20.1 to 6.21.0 (#1699)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.20.1 to 6.21.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.20.1...v6.21.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 03:13:54 +00:00
Joseph Mearman
9fab59954c full path required for extension install (#1670)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-03-19 21:02:11 -07:00
dependabot[bot]
8c28c9fd8f build(deps): bump actions/setup-go from 3 to 4 (#1689)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-20 03:31:00 +00:00
dependabot[bot]
2fa0a5f769 build(deps): bump github.com/imdario/mergo from 0.3.13 to 0.3.14 (#1688)
Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.13 to 0.3.14.
- [Release notes](https://github.com/imdario/mergo/releases)
- [Commits](https://github.com/imdario/mergo/compare/v0.3.13...v0.3.14)

---
updated-dependencies:
- dependency-name: github.com/imdario/mergo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 03:12:28 +00:00
dependabot[bot]
a6c95ef2a7 build(deps): bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#1650)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-03-19 23:56:15 +00:00
dependabot[bot]
5a2112a7f8 build(deps): bump megalinter/megalinter from 6.20.0 to 6.20.1 (#1679)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.20.0 to 6.20.1.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.20.0...v6.20.1)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-19 23:37:58 +00:00
dependabot[bot]
fad986af5f build(deps): bump fregante/setup-git-user from 1 to 2 (#1664)
Bumps [fregante/setup-git-user](https://github.com/fregante/setup-git-user) from 1 to 2.
- [Release notes](https://github.com/fregante/setup-git-user/releases)
- [Commits](https://github.com/fregante/setup-git-user/compare/v1...v2)

---
updated-dependencies:
- dependency-name: fregante/setup-git-user
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-19 23:21:57 +00:00
Yann Pellegrini
35cac27f4c Add example command for collecting artifacts (#1671)
Co-authored-by: Markus Wolf <KnisterPeter@users.noreply.github.com>
2023-03-19 22:47:47 +00:00
Shubh Bapna
636c8a34ae feat: specify matrix on command line (#1675)
* added matrix option

* select the correct subset of matrix configuration after producing all the matrix configuration

* add tests

* update readme

* lint fix

* remove matrix from readme

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-19 17:25:55 +00:00
Shubham Agrawal
09de42f067 Support for docker steps in host environment (#1674)
* Support for docker steps in host environment

* removed workdir changes
2023-03-14 14:07:31 +00:00
Markus Wolf
6744e68ee2 fix: correct ref and ref_name (#1672)
* fix: correct ref and ref_name

The ref in the GitHub context is always full qualified
(e.g. refs/heads/branch, refs/tags/v1).
The ref_name is the ref with the strippep prefix.
In case of pull_requests, this is the merge commit ref
(e.g. refs/pull/123/merge -> 123/merge).

* test: update test data
2023-03-09 20:03:13 +00:00
Jason Song
ac5dd8feb8 fix: return err in walk (#1667)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-08 15:13:11 +00:00
ChristopherHX
24440d9f15 test: Enshure ForcePull config doesn't break docker actions (1661) 2023-03-08 14:57:49 +00:00
ChristopherHX
f3c88b5091 fix: crash if the id tool fails to run in the container (1660) 2023-03-08 14:41:25 +00:00
dependabot[bot]
aeee2052de build(deps): bump github.com/moby/buildkit from 0.11.3 to 0.11.4 (#1669)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.3 to 0.11.4.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.3...v0.11.4)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-07 20:27:33 +00:00
dependabot[bot]
7ee2138418 build(deps): bump megalinter/megalinter from 6.19.0 to 6.20.0 (#1665)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.19.0 to 6.20.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.19.0...v6.20.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-06 03:30:59 +00:00
dependabot[bot]
8dbd151fa7 build(deps): bump golang.org/x/term from 0.5.0 to 0.6.0 (#1666)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 03:12:42 +00:00
Tony Soloveyv
6601d8d8e8 Improve XDG Spec supporting (#1656)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-03 14:39:02 +00:00
Jason Song
19abab6375 fix: safe file name (#1651)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-03 08:38:33 +00:00
Alex Savchuk
973dd7f7ef fix: compare properties of Invalid types (#1645)
* fix: compare properties of Invalid types

 fix: compare properties of Invalid types #1643

* fix linter problem

* Fix review comment
2023-03-03 08:16:33 +00:00
github-actions[bot]
44b510f48c chore: bump VERSION to 0.2.43 2023-03-01 02:33:29 +00:00
Alex Savchuk
5500c928eb fix: github.job property is empty, GITHUB_JOB should be job id (#1646)
* fix: github.job property is empty, GITHUB_JOB should be job id

fix: github.job property is empty #1621
fix: GITHUB_JOB should be the id not the name #1473

* fix linter problem.

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-27 19:10:31 +00:00
dependabot[bot]
04d12b0206 build(deps): bump github.com/opencontainers/image-spec (#1649)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.0.3-0.20220303224323-02efb9a75ee1 to 1.1.0-rc2.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Commits](https://github.com/opencontainers/image-spec/commits/v1.1.0-rc2)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 03:25:06 +00:00
Markus Wolf
89cb558558 fix: update output handling for reusable workflows (#1521)
* fix: map job output for reusable workflows

This fixes the job outputs for reusable workflows. There is
a required indirection. Before this we took the outputs from
all jobs which is not what users express with the workflow
outputs.

* fix: remove double evaluation

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-23 22:34:47 +00:00
ChristopherHX
53095d76f4 fix: crash malformed composite action (#1616)
* fix: crash malformed composite action

* Add remote composite action test

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-23 22:16:07 +00:00
R
4b56aced15 docs: remove help section (#1648)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-23 18:51:01 +00:00
ChristopherHX
05eaeaa528 feat: workflowpattern package (#1618)
* feat: workflowpattern package

* nolint:gocyclo

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-23 18:21:08 +00:00
dependabot[bot]
4eba04b229 build(deps): bump github.com/containerd/containerd from 1.6.16 to 1.6.18 (#1637)
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.6.16 to 1.6.18.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.6.16...v1.6.18)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-23 17:31:11 +00:00
R
8790c9b8e6 fix: add GITHUB_STEP_SUMMARY (#1607) 2023-02-23 15:24:44 +00:00
dependabot[bot]
b7a9eb9fbf build(deps): bump golang.org/x/net from 0.4.0 to 0.7.0 (#1636)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.4.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-20 03:44:53 +00:00
dependabot[bot]
2f55276cea build(deps): bump github.com/moby/buildkit from 0.11.2 to 0.11.3 (#1635)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.2 to 0.11.3.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.2...v0.11.3)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-20 03:21:34 +00:00
Josh Soref
be4a1477a5 fix: tolerate workflow that needs a missing job (#1595) (#1619)
Change planner functions to return errors

This enables createStages to return `unable to build dependency graph`

Fix PlanEvent to properly report errors relating to events/workflows
2023-02-16 16:41:59 +00:00
ChristopherHX
21ea3d0d5f chore: Remove obsolete Container.UpdateFromPath (#1631)
* chore: Remove obsolete Container.UpdateFromPath

* remove unused import
2023-02-16 16:11:26 +00:00
Jason Song
1316307313 chore: use new style octal (#1630)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-16 15:34:51 +00:00
Jason Song
b0a5068f6d fix: don't override env (#1629) 2023-02-16 15:16:46 +00:00
sitiom
34ab8150bf ci: add Winget Releaser workflow (#1623)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-13 17:59:19 +00:00
dependabot[bot]
8048baf435 build(deps): bump github.com/opencontainers/selinux (#1625)
Bumps [github.com/opencontainers/selinux](https://github.com/opencontainers/selinux) from 1.10.2 to 1.11.0.
- [Release notes](https://github.com/opencontainers/selinux/releases)
- [Commits](https://github.com/opencontainers/selinux/compare/v1.10.2...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/selinux
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-13 04:19:59 +00:00
dependabot[bot]
7c3c5349ab build(deps): bump github.com/docker/docker (#1624)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.0+incompatible to 23.0.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.0...v23.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-13 03:49:37 +00:00
dependabot[bot]
98ad62fcef build(deps): bump github.com/docker/cli (#1626)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.0+incompatible to 23.0.1+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v23.0.0...v23.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-13 03:33:22 +00:00
sitiom
c378a7d28b chore: add Winget installation instructions (#1622) 2023-02-11 18:25:22 +00:00
Aidan
44333c758a Pass dockerfile to build executor (#1606)
This allows testing actions with non standard dockerfile names

Signed-off-by: Aidan Jensen <aidan@artificial.com>
2023-02-08 17:14:43 +00:00
dependabot[bot]
36dbbc1dfa build(deps): bump github.com/docker/cli from 23.0.0-rc.1+incompatible to 23.0.0+incompatible (#1611)
* build(deps): bump github.com/docker/cli

Bumps [github.com/docker/cli](https://github.com/docker/cli) from 23.0.0-rc.1+incompatible to 23.0.0+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v23.0.0-rc.1...v23.0.0)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* update-test

* update test

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-02-06 12:33:42 +00:00
dependabot[bot]
f41e9127a8 build(deps): bump github.com/docker/docker (#1613)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.0-rc.3+incompatible to 23.0.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.0-rc.3...v23.0.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-06 03:25:34 +00:00
dependabot[bot]
460c78da07 build(deps): bump megalinter/megalinter from 6.18.0 to 6.19.0 (#1610)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.18.0 to 6.19.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.18.0...v6.19.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-06 03:05:59 +00:00
dependabot[bot]
22dc1e0e7e build(deps): bump github.com/go-git/go-billy/v5 from 5.4.0 to 5.4.1 (#1612)
Bumps [github.com/go-git/go-billy/v5](https://github.com/go-git/go-billy) from 5.4.0 to 5.4.1.
- [Release notes](https://github.com/go-git/go-billy/releases)
- [Commits](https://github.com/go-git/go-billy/compare/v5.4.0...v5.4.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-billy/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-06 02:51:12 +00:00
dependabot[bot]
281a52f0d6 build(deps): bump github.com/joho/godotenv from 1.4.0 to 1.5.1 (#1614)
Bumps [github.com/joho/godotenv](https://github.com/joho/godotenv) from 1.4.0 to 1.5.1.
- [Release notes](https://github.com/joho/godotenv/releases)
- [Commits](https://github.com/joho/godotenv/compare/v1.4.0...v1.5.1)

---
updated-dependencies:
- dependency-name: github.com/joho/godotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 02:36:56 +00:00
ChristopherHX
e775fea265 refactor: GITHUB_ENV command / remove env.PATH (#1503)
* fix: GITHUB_ENV / PATH handling

* apply workaround

* add ctx to ApplyExtraPath

* fix: Do not leak step env in composite

See https://github.com/nektos/act/pull/1585 for a test

* add more tests

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-04 13:35:13 +00:00
Aidan
24c16fbf25 Update max container name length (#1597)
* Update max container name length

Signed-off-by: Aidan Jensen <aidan@artificial.com>

* Use hashed name instead to prevent conflicts

Signed-off-by: Aidan Jensen <aidan@artificial.com>

---------

Signed-off-by: Aidan Jensen <aidan@artificial.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-03 19:54:19 +00:00
Shubh Bapna
ce168f9595 feat: allow overriding of GITHUB_ env variables (#1582)
* allow overriding of GITHUB_ env variables

* bug fix for overriding env vars with empty string

* revert step.go

* refactor github_context to prevent lint failures. added more setters

* added ability to override github env variables

* handled base and head ref
2023-02-03 19:35:49 +00:00
Aidan
c4b64ec1c1 Docker build fixes (#1596)
- Join relative path and split dockerfile off to get context

Signed-off-by: Aidan Jensen <aidan@artificial.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-02-03 09:07:16 +00:00
ChristopherHX
f91b2aa5db fix: Apply forcePull only for prebuild docker actions (#1599) 2023-02-02 17:24:35 +00:00
github-actions[bot]
72d03214c5 chore: bump VERSION to 0.2.42 2023-02-02 00:56:57 +00:00
Casey Lee
42b9b73d38 feat: cache notices to reduce frequency of upgrade notifications (#1592)
* feat: cache notices to reduce frequency of upgrade notifications

* fix: reduce WriteFile permissions

* fix: remove reference to deprecated lib

* fix: handle HTTP status 304
2023-02-01 16:54:57 -08:00
Casey Lee
787388daf5 chore: fix release script to trigger gh-act 2023-01-31 18:55:22 -08:00
github-actions[bot]
b91e4b0d55 chore: bump VERSION to 0.2.41 2023-02-01 02:32:39 +00:00
dependabot[bot]
e259dc2835 build(deps): bump github.com/moby/buildkit from 0.11.1 to 0.11.2 (#1589)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.1 to 0.11.2.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.1...v0.11.2)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-30 02:40:41 +00:00
dependabot[bot]
70ae63bd10 build(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#1590)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 02:26:24 +00:00
R
01952d05c8 cI: make stalebot less annoying (#1587)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-29 16:44:53 +00:00
ChristopherHX
3748772201 test: Do not leak step env in composite (#1585)
* test: Do not leak step env in composite

To prevent merging regressions.

* Update runner_test.go
2023-01-29 14:47:56 +00:00
ChristopherHX
932863bef5 feat: step summary of test results (#1580)
* feat: step summary of test results

* fix: indent style

* fix: handle failed tests

* fix upload / create a logs artifact

* Update checks.yml

* fix: always upload logs

* fix: run success

* Move steps into a composite action

* use args and not the hardcoded ones

* format composite action

* format
2023-01-25 08:14:51 +00:00
dependabot[bot]
09e9f6a1ac build(deps): bump github.com/moby/buildkit from 0.11.0 to 0.11.1 (#1576)
Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.11.0 to 0.11.1.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.11.0...v0.11.1)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-23 04:06:21 +00:00
dependabot[bot]
a2856aed75 build(deps): bump golang.org/x/term from 0.3.0 to 0.4.0 (#1577)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-23 03:42:24 +00:00
dependabot[bot]
612e08f113 build(deps): bump github.com/docker/docker (#1573)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.0-rc.1+incompatible to 23.0.0-rc.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.0-rc.1...v23.0.0-rc.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-23 03:25:31 +00:00
dependabot[bot]
1b7212f5fd build(deps): bump github.com/rhysd/actionlint from 1.6.22 to 1.6.23 (#1574)
Bumps [github.com/rhysd/actionlint](https://github.com/rhysd/actionlint) from 1.6.22 to 1.6.23.
- [Release notes](https://github.com/rhysd/actionlint/releases)
- [Changelog](https://github.com/rhysd/actionlint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rhysd/actionlint/compare/v1.6.22...v1.6.23)

---
updated-dependencies:
- dependency-name: github.com/rhysd/actionlint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-23 03:11:28 +00:00
dependabot[bot]
75d19d0ff5 build(deps): bump actions/github-script from 5 to 6 (#1572)
Bumps [actions/github-script](https://github.com/actions/github-script) from 5 to 6.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 02:56:38 +00:00
Casey Lee
7ada9d3f74 chore: update docs for installing act as GH CLI extension 2023-01-20 10:22:58 -08:00
Casey Lee
78e1ceb0a8 feat: release extension 2023-01-20 10:13:18 -08:00
Casey Lee
d690a5fee9 feat: release extension 2023-01-20 10:11:34 -08:00
Casey Lee
19e6929398 feat: release extension 2023-01-20 10:10:20 -08:00
ChristopherHX
b00babd0d9 refactor: pull and rebuild docker by default (#1569)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-20 15:46:43 +00:00
Markus Wolf
82a8c1e80d feat: add remote reusable workflows (#1525)
* feat: add remote reusable workflows

This changes adds cloning of a remote repository to
run a workflow included in it.

Closes #826

* fix: defer plan creation until clone is done

We need wait for the full clone (and only clone once)
before we start to plan the execution for a remote workflow

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-19 20:49:11 +00:00
Yoshiaki Yoshida
566b9d843e Fixed auto-generated platform configuration with Micro size image (#1566)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-19 06:49:16 +00:00
Alexandre Lavigne
7ebcc1c816 Feature/allow worktrees (#1530)
* Use go-git to find remote URL

* Use go-git package to resolve HEAD revision (commit sha1)

* Use go-git to find checked-out reference

* Remove unused functions
2023-01-19 06:29:23 +00:00
github-actions[bot]
3e23b4fbb5 chore: bump VERSION to 0.2.40 2023-01-16 21:35:04 +00:00
Casey Lee
63ae215071 fix: update artifact server to address GHSL-2023-004 (#1565) 2023-01-16 21:01:54 +00:00
github-actions[bot]
efb12b7f12 chore: bump VERSION to 0.2.39 2023-01-16 20:07:23 +00:00
Casey Lee
b3c06dc19d chore: fix promote script 2023-01-16 09:57:35 -08:00
Casey Lee
0d0780580c chore: bump version 2023-01-16 09:54:43 -08:00
Casey Lee
872a69548c chore: makefile update 2023-01-16 09:43:53 -08:00
Casey Lee
33cb8bca64 chore: bump version 2023-01-16 09:39:52 -08:00
dependabot[bot]
d8ba8cbf17 build(deps): bump github.com/moby/buildkit from 0.10.6 to 0.11.0 (#1563)
* build(deps): bump github.com/moby/buildkit from 0.10.6 to 0.11.0

Bumps [github.com/moby/buildkit](https://github.com/moby/buildkit) from 0.10.6 to 0.11.0.
- [Release notes](https://github.com/moby/buildkit/releases)
- [Commits](https://github.com/moby/buildkit/compare/v0.10.6...v0.11.0)

---
updated-dependencies:
- dependency-name: github.com/moby/buildkit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: use new patternmatcher.Matches

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Casey Lee <caseypl@amazon.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-16 17:26:22 +00:00
Casey Lee
003947ea33 chore: push new version 2023-01-16 09:11:29 -08:00
Philippe Schenker
0988b47752 Readme: Fix shell installation (#1547)
Use the -s (silent) flag so curl does not pass metainformation like
downloadspeed etc to bash.

Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-01-16 14:30:11 +00:00
Robin Breathe
d064863f9b fix: allow override of artifact server bind address (#1560)
* Prior to this change, the artifact server always binds to the detected
  "outbound IP", breaks functionality when that IP is unroutable.
  For example, Zscaler assigns the host a local CGNAT address,
  100.64.0.1, which is unreachable from Docker Desktop.
* Add the `--artifact-server-addr` flag to allow override of the address
  to which the artifact server binds, defaulting to the existing
  behaviour.

Fixes: #1559
2023-01-16 14:12:20 +00:00
Casey Lee
93907931df feat: add check for newer versions (#1562)
* feat: add check for newer versions

* fix: support JSON logger and rever updates to go.mod

* fix: keep version updated in source code

* fix: lint errors

* fix: revert go.*
2023-01-15 10:30:41 +00:00
Shubh Bapna
767e6a8696 Input (#1524)
* added input flags

* added input as part of the action event and added test cases

* updated readme

Co-authored-by: ChristopherHX <christopher.homberger@web.de>
2023-01-13 19:28:17 +00:00
ChristopherHX
b2fb9e64ac refactor: remove docker image list reference filter (#1501)
* refactor: remove docker reference filter

* make it work

* solve logic failure

* Another mistake

* another one

* revert signature of ImageExistsLocally

It is better to keep two return values
2023-01-13 17:52:54 +00:00
ChristopherHX
8b4f210872 fix: add-matcher fails github workflow (#1532)
* fix: add-matcher fails github workflow

* make linter happy
2023-01-13 17:01:40 +00:00
Björn Brauer
3f3b25ae84 feat: add support for building docker actions with private registries (#1557)
This commit adds a new `LoadDockerAuthConfigs` function, which loads all
registry auths that are configured on the host and sends them with the build
command to the docker daemon.

This is needed in case act builds a docker action and the images referenced in
that docker action are located on private registries or otherwise require
authentication (e.g. to get a higher rate limit).

The code is adapted from how the docker cli works:
257ff41304/cli/command/image/build.go (L323-L332)

Co-authored-by: Markus Wolf <mail@markus-wolf.de>

Co-authored-by: Markus Wolf <mail@markus-wolf.de>
2023-01-12 21:29:30 +00:00
dependabot[bot]
3ac756b37a build(deps): bump github.com/containerd/containerd from 1.6.6 to 1.6.12 (#1554)
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.6.6 to 1.6.12.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.6.6...v1.6.12)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 22:23:48 +00:00
ChristopherHX
7e8d070811 feat: Allow building without docker support (#1507)
* feat: Allow building without docker support

* fix macos build tag

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 22:08:57 +00:00
ChristopherHX
a53a1c2000 fix: extra path lost in composite actions (#1531)
* test: define test case of path issues

Test case for #1528

* test: add multi arch grep

* fix: Always use current ExtraPath

* replace setup-node with run step

* Update push.yml

* yaml mistake

Co-authored-by: Markus Wolf <mail@markus-wolf.de>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 21:55:05 +00:00
Markus Wolf
3f4a6dc462 test: make sure workflow_call is not a github event calling our workflow (#1520)
Since reusable workflows are defining inputs and ouputs using the
on.workflow_call syntax, this could also be triggered by a workflow_call
event. That event does not exist within GitHub and we should make
sure our worklow is not called by that kind of 'synthetic' event.

See 74da5b085c (r1042413431)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 21:43:12 +00:00
Markus Wolf
b14398eff3 fix: preserve job result state in case of failure (#1519)
* fix: preserve job result state in case of failure

There is just one job field for the job result. This is also true for
matrix jobs. We need to preserve the failure state of a job to
have the whole job failing in case of one permuation of the matrix failed.

Closes #1518

* test: remove continue-on-error on job level

This feature is not yet supported by act and if implemented
would make this test invalid

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-01-10 21:31:12 +00:00
dependabot[bot]
f0c6fa11be build(deps): bump megalinter/megalinter from 6.17.0 to 6.18.0 (#1550)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.17.0 to 6.18.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.17.0...v6.18.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 21:16:52 +00:00
dependabot[bot]
cfc1b91aa1 build(deps): bump megalinter/megalinter from 6.16.0 to 6.17.0 (#1540)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.16.0 to 6.17.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.16.0...v6.17.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 02:43:57 +00:00
dependabot[bot]
0d388572a8 build(deps): bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17 (#1539)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 02:27:22 +00:00
dependabot[bot]
9c7085195c build(deps): bump github.com/go-git/go-billy/v5 from 5.3.1 to 5.4.0 (#1541)
Bumps [github.com/go-git/go-billy/v5](https://github.com/go-git/go-billy) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/go-git/go-billy/releases)
- [Commits](https://github.com/go-git/go-billy/compare/v5.3.1...v5.4.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-billy/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 02:13:45 +00:00
dependabot[bot]
e3a722e7c6 build(deps): bump megalinter/megalinter from 6.15.0 to 6.16.0 (#1534)
Bumps [megalinter/megalinter](https://github.com/megalinter/megalinter) from 6.15.0 to 6.16.0.
- [Release notes](https://github.com/megalinter/megalinter/releases)
- [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/megalinter/megalinter/compare/v6.15.0...v6.16.0)

---
updated-dependencies:
- dependency-name: megalinter/megalinter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-26 02:28:46 +00:00
dependabot[bot]
d3ce2b0a46 build(deps): bump actions/stale from 6 to 7 (#1535)
Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-26 02:13:17 +00:00
ChristopherHX
4989f444f1 revert: deprecation of containerArchitecture (#1514)
* fix: ci snaphot job

* revert: deprecation of containerArchitecture

This option isn't part of parsed docker cli flags

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-19 21:24:05 +00:00
dependabot[bot]
c044035d9e build(deps): bump goreleaser/goreleaser-action from 3 to 4 (#1515)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 3 to 4.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 15:46:21 +00:00
dependabot[bot]
767969502a build(deps): bump github.com/docker/cli (#1516)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 20.10.21+incompatible to 20.10.22+incompatible.
- [Release notes](https://github.com/docker/cli/releases)
- [Commits](https://github.com/docker/cli/compare/v20.10.21...v20.10.22)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-19 15:33:25 +00:00
dependabot[bot]
469d024c1f build(deps): bump github.com/docker/docker (#1517)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.21+incompatible to 20.10.22+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v20.10.21...v20.10.22)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-19 15:20:49 +00:00
ChristopherHX
6ab71ecebb fix: align runner.os / runner.arch to known values (#1510)
* fix: align runner.os / runner.arch to known values

* .

* .

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-19 14:58:55 +00:00
Aaron Holmes
bef9b5c3c7 feat: Support "result" on "needs" context. (#1497)
* Support "result" on "needs" context.

This change adds "result" to a job's "needs" context, as documented [here](https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context). `act` currently tracks the success/failure/cancelled status of a job, but does not include this value the `needs` context.

Fixes #1367

* Change `Needs` to use a new struct rather than the open type `interface{}`.

Related #1497
Fixes #1367

* Add integration test to "needs" context change.

Relates: #1497

* feat: allow to spawn and run a local reusable workflow (#1423)

* feat: allow to spawn and run a local reusable workflow

This change contains the ability to parse/plan/run a local
reusable workflow.
There are still numerous things missing:

- inputs
- secrets
- outputs

* feat: add workflow_call inputs

* test: improve inputs test

* feat: add input defaults

* feat: allow expressions in inputs

* feat: use context specific expression evaluator

* refactor: prepare for better re-usability

* feat: add secrets for reusable workflows

* test: use secrets during test run

* feat: handle reusable workflow outputs

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* refactor: fix savestate in pre steps (#1466)

* refactor: fix savestate in pre steps

* fix pre steps collision

* fix tests

* remove

* enable tests

* Update pkg/runner/action.go

* Rename InterActionState to IntraActionState

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* fix: tail (not absolute) as entrypoint of job container (#1506)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

* Fix conflict in merge.

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-19 08:37:53 +00:00
ChristopherHX
0c8c082ac0 fix: tail (not absolute) as entrypoint of job container (#1506)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-15 17:31:59 +00:00
ChristopherHX
b8d7e947cf refactor: fix savestate in pre steps (#1466)
* refactor: fix savestate in pre steps

* fix pre steps collision

* fix tests

* remove

* enable tests

* Update pkg/runner/action.go

* Rename InterActionState to IntraActionState

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-15 17:08:31 +00:00
Markus Wolf
a8e05cded6 feat: allow to spawn and run a local reusable workflow (#1423)
* feat: allow to spawn and run a local reusable workflow

This change contains the ability to parse/plan/run a local
reusable workflow.
There are still numerous things missing:

- inputs
- secrets
- outputs

* feat: add workflow_call inputs

* test: improve inputs test

* feat: add input defaults

* feat: allow expressions in inputs

* feat: use context specific expression evaluator

* refactor: prepare for better re-usability

* feat: add secrets for reusable workflows

* test: use secrets during test run

* feat: handle reusable workflow outputs

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-15 16:45:22 +00:00
ChristopherHX
d281230cce refactor: fix add-path / GITHUB_PATH commands (#1472)
* fix: add-path / GITHUB_PATH commands

* Disable old code

* fix: missing mock

* Update tests

* fix tests

* UpdateExtraPath skip on dryrun

* patch test

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-09 11:16:15 +00:00
ChristopherHX
7d9951200f feat: JobLoggerFactory (#1496)
Remove overriding io.Stdout in TestMaskValues to prevent deadlock in GitHub Actions
2022-12-09 10:25:32 +00:00
Markus Wolf
0f8861b9e3 fix: handle env-vars case sensitive (#1493)
Closes #1488
2022-12-07 15:31:33 +00:00
ChristopherHX
8c5748a55c fix: step env is unavailable in with property expr (#1458)
* fix: step env is unavailable in with property expr

* don't run the test on windows

* fix: composite action add missing shell

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-06 16:46:20 +00:00
ChristopherHX
57bf4d27a2 refactor: share UpdateFromEnv logic (#1457)
* refactor: share UpdateFromEnv logic

* Add test for GITHUB_OUTPUT

Co-authored-by: Ben Randall <veleek@gmail.com>

* Add GITHUB_STATE test

* Add test for the old broken parser

Co-authored-by: Ben Randall <veleek@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-06 16:19:27 +00:00
ChristopherHX
4c2524ab4d feat: --container-options (#1462)
* feat: `--container-options`

This deprecates the following options
- `--privileged`
- `--container-cap-add`
- `--container-cap-drop`
- `--container-architecture`
- `--userns`

* Merge binds/mounts, add desc

* avoid linter error

* fix: apply options to step env / deprecate warning

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-06 15:58:47 +00:00
ChristopherHX
d9fe63ec24 refactor: move autoremove into the jobexecutor (#1463)
* refactor: move autoremove into the jobexecutor

breaking: docker container are removed after job exit

* reduce complexity

* remove linter exception

* reduce cyclic complexity

* fix: always allow 1 min for stopping and removing the runner, even if we were cancelled

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-06 15:45:06 +00:00
ChristopherHX
7073eac240 docs: clarifying skipping steps / jobs (#1480)
* docs: clarifying skipping steps / jobs

* fix lint

* Update README.md

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-06 15:09:13 +00:00
Brice Dutheil
1797775be3 Pass LANG=C.UTF-8 to environment (#1476)
* fix: pass LANG=C.UTF-8 to environment

Fixes: #1308

* fix: pass LANG=C.UTF-8 to environment in container only

Fixes: #1308
Signed-off-by: Brice Dutheil <brice.dutheil@gmail.com>

Signed-off-by: Brice Dutheil <brice.dutheil@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2022-12-06 10:36:39 +00:00
1647 changed files with 475432 additions and 4623 deletions

View File

@@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
ARG CHOCOVERSION=1.1.0 ARG CHOCOVERSION=1.1.0

77
.github/actions/run-tests/action.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: 'run-tests'
description: 'Runs go test and upload a step summary'
inputs:
filter:
description: 'The go test pattern for the tests to run'
required: false
default: ''
upload-logs-name:
description: 'Choose the name of the log artifact'
required: false
default: logs-${{ github.job }}-${{ strategy.job-index }}
upload-logs:
description: 'If true uploads logs of each tests as an artifact'
required: false
default: 'true'
runs:
using: composite
steps:
- uses: actions/github-script@v6
with:
github-token: none # No reason to grant access to the GITHUB_TOKEN
script: |
let myOutput = '';
var fs = require('fs');
var uploadLogs = process.env.UPLOAD_LOGS === 'true';
if(uploadLogs) {
await io.mkdirP('logs');
}
var filename = null;
const options = {};
options.ignoreReturnCode = true;
options.env = Object.assign({}, process.env);
delete options.env.ACTIONS_RUNTIME_URL;
delete options.env.ACTIONS_RUNTIME_TOKEN;
delete options.env.ACTIONS_CACHE_URL;
options.listeners = {
stdout: (data) => {
for(line of data.toString().split('\n')) {
if(/^\s*(===\s[^\s]+\s|---\s[^\s]+:\s)/.test(line)) {
if(uploadLogs) {
var runprefix = "=== RUN ";
if(line.startsWith(runprefix)) {
filename = "logs/" + line.substring(runprefix.length).replace(/[^A-Za-z0-9]/g, '-') + ".txt";
fs.writeFileSync(filename, line + "\n");
} else if(filename) {
fs.appendFileSync(filename, line + "\n");
filename = null;
}
}
myOutput += line + "\n";
} else if(filename) {
fs.appendFileSync(filename, line + "\n");
}
}
}
};
var args = ['test', '-v', '-cover', '-coverprofile=coverage.txt', '-covermode=atomic', '-timeout', '20m'];
var filter = process.env.FILTER;
if(filter) {
args.push('-run');
args.push(filter);
}
args.push('./...');
var exitcode = await exec.exec('go', args, options);
if(process.env.GITHUB_STEP_SUMMARY) {
core.summary.addCodeBlock(myOutput);
await core.summary.write();
}
process.exit(exitcode);
env:
FILTER: ${{ inputs.filter }}
UPLOAD_LOGS: ${{ inputs.upload-logs }}
- uses: actions/upload-artifact@v3
if: always() && inputs.upload-logs == 'true' && !env.ACT
with:
name: ${{ inputs.upload-logs-name }}
path: logs

View File

@@ -1,10 +1,13 @@
name: checks name: checks
on: [pull_request, workflow_dispatch] on: [pull_request, workflow_dispatch]
concurrency:
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
env: env:
ACT_OWNER: ${{ github.repository_owner }} ACT_OWNER: ${{ github.repository_owner }}
ACT_REPOSITORY: ${{ github.repository }} ACT_REPOSITORY: ${{ github.repository }}
GO_VERSION: 1.18
CGO_ENABLED: 0 CGO_ENABLED: 0
jobs: jobs:
@@ -12,17 +15,18 @@ jobs:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version-file: go.mod
check-latest: true check-latest: true
- uses: golangci/golangci-lint-action@v3.3.1 - uses: golangci/golangci-lint-action@v3.7.0
with: with:
version: v1.47.2 version: v1.53
- uses: megalinter/megalinter/flavors/go@v6.15.0 only-new-issues: true
- uses: megalinter/megalinter/flavors/go@v7.7.0
env: env:
DEFAULT_BRANCH: master DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -34,14 +38,14 @@ jobs:
name: test-linux name: test-linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version-file: go.mod
check-latest: true check-latest: true
- uses: actions/cache@v3 - uses: actions/cache@v3
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
@@ -50,9 +54,14 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go- ${{ runner.os }}-go-
- run: go test -v -cover -coverprofile=coverage.txt -covermode=atomic -timeout 15m ./... - name: Run Tests
uses: ./.github/actions/run-tests
with:
upload-logs-name: logs-linux
- name: Run act from cli
run: go run main.go -P ubuntu-latest=node:16-buster-slim -C ./pkg/runner/testdata/ -W ./basic/push.yml
- name: Upload Codecov report - name: Upload Codecov report
uses: codecov/codecov-action@v3.1.1 uses: codecov/codecov-action@v3.1.4
with: with:
files: coverage.txt files: coverage.txt
fail_ci_if_error: true # optional (default = false) fail_ci_if_error: true # optional (default = false)
@@ -66,24 +75,27 @@ jobs:
name: test-${{matrix.os}} name: test-${{matrix.os}}
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version-file: go.mod
check-latest: true check-latest: true
- run: go test -v -run ^TestRunEventHostEnvironment$ ./... - name: Run Tests
# TODO merge coverage with test-linux uses: ./.github/actions/run-tests
with:
filter: '^TestRunEventHostEnvironment$'
upload-logs-name: logs-${{ matrix.os }}
snapshot: snapshot:
name: snapshot name: snapshot
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version-file: go.mod
check-latest: true check-latest: true
- uses: actions/cache@v3 - uses: actions/cache@v3
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
@@ -93,10 +105,10 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-go- ${{ runner.os }}-go-
- name: GoReleaser - name: GoReleaser
uses: goreleaser/goreleaser-action@v3 uses: goreleaser/goreleaser-action@v5
with: with:
version: latest version: latest
args: release --snapshot --rm-dist args: release --snapshot --clean
- name: Capture x86_64 (64-bit) Linux binary - name: Capture x86_64 (64-bit) Linux binary
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -4,23 +4,20 @@ on:
- cron: '0 2 1 * *' - cron: '0 2 1 * *'
workflow_dispatch: {} workflow_dispatch: {}
env:
GO_VERSION: 1.18
jobs: jobs:
release: release:
name: promote name: promote
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
ref: master ref: master
token: ${{ secrets.GORELEASER_GITHUB_TOKEN }} token: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
- uses: fregante/setup-git-user@v1 - uses: fregante/setup-git-user@v2
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version-file: go.mod
check-latest: true check-latest: true
- uses: actions/cache@v3 - uses: actions/cache@v3
if: ${{ !env.ACT }} if: ${{ !env.ACT }}

View File

@@ -4,20 +4,17 @@ on:
tags: tags:
- v* - v*
env:
GO_VERSION: 1.18
jobs: jobs:
release: release:
name: release name: release
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version-file: go.mod
check-latest: true check-latest: true
- uses: actions/cache@v3 - uses: actions/cache@v3
if: ${{ !env.ACT }} if: ${{ !env.ACT }}
@@ -27,15 +24,38 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-go- ${{ runner.os }}-go-
- name: GoReleaser - name: GoReleaser
uses: goreleaser/goreleaser-action@v3 uses: goreleaser/goreleaser-action@v5
with: with:
version: latest version: latest
args: release --rm-dist args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
- name: Winget
uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: nektos.act
installers-regex: '_Windows_\w+\.zip$'
token: ${{ secrets.WINGET_TOKEN }}
- name: Chocolatey - name: Chocolatey
uses: ./.github/actions/choco uses: ./.github/actions/choco
with: with:
version: ${{ github.ref }} version: ${{ github.ref }}
apiKey: ${{ secrets.CHOCO_APIKEY }} apiKey: ${{ secrets.CHOCO_APIKEY }}
push: true push: true
- name: GitHub CLI extension
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
script: |
const mainRef = (await github.rest.git.getRef({
owner: 'nektos',
repo: 'gh-act',
ref: 'heads/main',
})).data;
console.log(mainRef);
github.rest.git.createRef({
owner: 'nektos',
repo: 'gh-act',
ref: context.ref,
sha: mainRef.object.sha,
});

View File

@@ -8,7 +8,7 @@ jobs:
name: Stale name: Stale
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v6 - uses: actions/stale@v9
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Issue is stale and will be closed in 14 days unless there is new activity' stale-issue-message: 'Issue is stale and will be closed in 14 days unless there is new activity'
@@ -19,5 +19,5 @@ jobs:
exempt-pr-labels: 'stale-exempt' exempt-pr-labels: 'stale-exempt'
remove-stale-when-updated: 'True' remove-stale-when-updated: 'True'
operations-per-run: 500 operations-per-run: 500
days-before-stale: 30 days-before-stale: 180
days-before-close: 14 days-before-close: 14

1
.gitignore vendored
View File

@@ -31,4 +31,3 @@ coverage.txt
# megalinter # megalinter
report/ report/
act

View File

@@ -19,17 +19,15 @@ linters-settings:
- pkg: 'github.com/stretchr/testify/assert' - pkg: 'github.com/stretchr/testify/assert'
alias: assert alias: assert
depguard: depguard:
list-type: blacklist rules:
include-go-root: true main:
packages: deny:
- github.com/pkg/errors - pkg: github.com/pkg/errors
- gotest.tools/v3/assert desc: Please use "errors" package from standard library
- log - pkg: gotest.tools/v3
packages-with-error-message: desc: Please keep tests unified using only github.com/stretchr/testify
- github.com/pkg/errors: 'Please use "errors" package from standard library' - pkg: log
- gotest.tools/v3: 'Please keep tests unified using only github.com/stretchr/testify' desc: Please keep logging unified using only github.com/sirupsen/logrus
- log: 'Please keep logging unified using only github.com/sirupsen/logrus'
linters: linters:
enable: enable:
- megacheck - megacheck

View File

@@ -22,13 +22,13 @@ builds:
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
archives: archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}' - name_template: >-
replacements: {{ .ProjectName }}_
darwin: Darwin {{- title .Os }}_
linux: Linux {{- if eq .Arch "amd64" }}x86_64
windows: Windows {{- else if eq .Arch "386" }}i386
386: i386 {{- else }}{{ .Arch }}{{ end }}
amd64: x86_64 {{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip format: zip

View File

@@ -14,7 +14,7 @@ DISABLE_LINTERS:
- MARKDOWN_MARKDOWN_LINK_CHECK - MARKDOWN_MARKDOWN_LINK_CHECK
- REPOSITORY_CHECKOV - REPOSITORY_CHECKOV
- REPOSITORY_TRIVY - REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: (.*testdata/*|install.sh|pkg/container/docker_cli.go|pkg/container/DOCKER_LICENSE) FILTER_REGEX_EXCLUDE: (.*testdata/*|install.sh|pkg/container/docker_cli.go|pkg/container/DOCKER_LICENSE|VERSION)
MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.yml MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.yml
PARALLEL: false PARALLEL: false
PRINT_ALPACA: false PRINT_ALPACA: false

View File

@@ -1,6 +1,7 @@
{ {
"go.lintTool": "golangci-lint", "go.lintTool": "golangci-lint",
"go.lintFlags": ["--fix"], "go.lintFlags": ["--fix"],
"go.testTimeout": "300s",
"[json]": { "[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },

View File

@@ -35,7 +35,7 @@ New issues can be created with in our [GitHub repo](https://github.com/nektos/ac
### <a id="pr"></a>Pull Requests ### <a id="pr"></a>Pull Requests
Pull requests should target the `master` branch. Please also reference the issue from the description of the pull request using [special keyword syntax](https://help.github.com/articles/closing-issues-via-commit-messages/) to auto close the issue when the PR is merged. For example, include the phrase `fixes #14` in the PR description to have issue #14 auto close. Pull requests should target the `master` branch. Please also reference the issue from the description of the pull request using [special keyword syntax](https://help.github.com/articles/closing-issues-via-commit-messages/) to auto close the issue when the PR is merged. For example, include the phrase `fixes #14` in the PR description to have issue #14 auto close. Please send documentation updates for the [act user guide](https://nektosact.com) to [nektos/act-docs](https://github.com/nektos/act-docs).
### <a id="style"></a> Styleguide ### <a id="style"></a> Styleguide

View File

@@ -1,6 +1,5 @@
MIT License MIT License
Copyright (c) 2022 The Gitea Authors
Copyright (c) 2019 Copyright (c) 2019
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@@ -96,12 +96,18 @@ ifneq ($(shell git status -s),)
@echo "Unable to promote a dirty workspace" @echo "Unable to promote a dirty workspace"
@exit 1 @exit 1
endif endif
echo -n $(NEW_VERSION) > VERSION
git add VERSION
git commit -m "chore: bump VERSION to $(NEW_VERSION)"
git tag -a -m "releasing v$(NEW_VERSION)" v$(NEW_VERSION) git tag -a -m "releasing v$(NEW_VERSION)" v$(NEW_VERSION)
git push origin master
git push origin v$(NEW_VERSION) git push origin v$(NEW_VERSION)
.PHONY: snapshot .PHONY: snapshot
snapshot: snapshot:
goreleaser build \ goreleaser build \
--rm-dist \ --clean \
--single-target \ --single-target \
--snapshot --snapshot
.PHONY: clean all

148
README.md
View File

@@ -1,17 +1,3 @@
## Naming rules:
Branches:
- `main`: default branch, contains custom changes.
- `nektos/master`: mirror for `master` of [nektos/act](https://github.com/nektos/act/).
Tags:
- `vX.YZ.*`: based on `nektos/vX.Y.Z`, contains custom changes.
- `nektos/vX.Y.Z`: mirror for `vX.Y.Z` of [nektos/act](https://github.com/nektos/act/).
---
![act-logo](https://github.com/nektos/act/wiki/img/logo-150.png) ![act-logo](https://github.com/nektos/act/wiki/img/logo-150.png)
# Overview [![push](https://github.com/nektos/act/workflows/push/badge.svg?branch=master&event=push)](https://github.com/nektos/act/actions) [![Join the chat at https://gitter.im/nektos/act](https://badges.gitter.im/nektos/act.svg)](https://gitter.im/nektos/act?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/nektos/act)](https://goreportcard.com/report/github.com/nektos/act) [![awesome-runners](https://img.shields.io/badge/listed%20on-awesome--runners-blue.svg)](https://github.com/jonico/awesome-runners) # Overview [![push](https://github.com/nektos/act/workflows/push/badge.svg?branch=master&event=push)](https://github.com/nektos/act/actions) [![Join the chat at https://gitter.im/nektos/act](https://badges.gitter.im/nektos/act.svg)](https://gitter.im/nektos/act?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/nektos/act)](https://goreportcard.com/report/github.com/nektos/act) [![awesome-runners](https://img.shields.io/badge/listed%20on-awesome--runners-blue.svg)](https://github.com/jonico/awesome-runners)
@@ -31,6 +17,10 @@ Let's see it in action with a [sample repo](https://github.com/cplee/github-acti
![Demo](https://github.com/nektos/act/wiki/quickstart/act-quickstart-2.gif) ![Demo](https://github.com/nektos/act/wiki/quickstart/act-quickstart-2.gif)
# Act User Guide
Please look at the [act user guide](https://nektosact.com) for more documentation.
# Installation # Installation
## Necessary prerequisites for running `act` ## Necessary prerequisites for running `act`
@@ -85,6 +75,14 @@ choco install act-cli
scoop install act scoop install act
``` ```
### [Winget](https://learn.microsoft.com/en-us/windows/package-manager/) (Windows)
[![Winget package](https://repology.org/badge/version-for-repo/winget/act-run-github-actions.svg)](https://repology.org/project/act-run-github-actions/versions)
```shell
winget install nektos.act
```
### [AUR](https://aur.archlinux.org/packages/act/) (Linux) ### [AUR](https://aur.archlinux.org/packages/act/) (Linux)
[![aur-shield](https://img.shields.io/aur/version/act)](https://aur.archlinux.org/packages/act/) [![aur-shield](https://img.shields.io/aur/version/act)](https://aur.archlinux.org/packages/act/)
@@ -122,6 +120,14 @@ Using the latest [Nix command](https://nixos.wiki/wiki/Nix_command), you can run
nix run nixpkgs#act nix run nixpkgs#act
``` ```
## Installation as GitHub CLI extension
Act can be installed as a [GitHub CLI](https://cli.github.com/) extension:
```sh
gh extension install https://github.com/nektos/gh-act
```
## Other install options ## Other install options
### Bash script ### Bash script
@@ -129,7 +135,7 @@ nix run nixpkgs#act
Run this command in your terminal: Run this command in your terminal:
```shell ```shell
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
``` ```
### Manual download ### Manual download
@@ -162,6 +168,9 @@ act pull_request
# Run a specific job: # Run a specific job:
act -j test act -j test
# Collect artifacts to the /tmp/artifacts folder:
act --artifact-server-path /tmp/artifacts
# Run a job in a specific workflow (useful if you have duplicate job names) # Run a job in a specific workflow (useful if you have duplicate job names)
act -j lint -W .github/workflows/checks.yml act -j lint -W .github/workflows/checks.yml
@@ -177,49 +186,6 @@ act -v
When running `act` for the first time, it will ask you to choose image to be used as default. When running `act` for the first time, it will ask you to choose image to be used as default.
It will save that information to `~/.actrc`, please refer to [Configuration](#configuration) for more information about `.actrc` and to [Runners](#runners) for information about used/available Docker images. It will save that information to `~/.actrc`, please refer to [Configuration](#configuration) for more information about `.actrc` and to [Runners](#runners) for information about used/available Docker images.
# Flags
```none
-a, --actor string user that triggered the event (default "nektos/act")
--replace-ghe-action-with-github-com If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com=github/super-linter)
--replace-ghe-action-token-with-github-com If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token
--artifact-server-path string Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.
--artifact-server-port string Defines the port where the artifact server listens (will only bind to localhost). (default "34567")
-b, --bind bind working directory to container, rather than copy
--container-architecture string Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.
--container-cap-add stringArray kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)
--container-cap-drop stringArray kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)
--container-daemon-socket string Path to Docker daemon socket which will be mounted to containers (default "/var/run/docker.sock")
--defaultbranch string the name of the main branch
--detect-event Use first event type from workflow as event that triggered the workflow
-C, --directory string working directory (default ".")
-n, --dryrun dryrun mode
--env stringArray env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)
--env-file string environment file to read and use as env in the containers (default ".env")
-e, --eventpath string path to event JSON file
--github-instance string GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server. (default "github.com")
-g, --graph draw workflows
-h, --help help for act
--insecure-secrets NOT RECOMMENDED! Doesn't hide secrets while printing logs.
-j, --job string run job
-l, --list list workflows
--no-recurse Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag
-P, --platform stringArray custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)
--privileged use privileged mode
-p, --pull pull docker image(s) even if already present
-q, --quiet disable logging of output from steps
--rebuild rebuild local action docker image(s) even if already present
-r, --reuse don't remove container(s) on successfully completed workflow(s) to maintain state between runs
--rm automatically remove container(s)/volume(s) after a workflow(s) failure
-s, --secret stringArray secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)
--secret-file string file with list of secrets to read from (e.g. --secret-file .secrets) (default ".secrets")
--use-gitignore Controls whether paths specified in .gitignore should be copied into container (default true)
--userns string user namespace to use
-v, --verbose verbose output
-w, --watch watch the contents of the local repo and run when files change
-W, --workflows string path to workflow file(s) (default "./.github/workflows/")
```
## `GITHUB_TOKEN` ## `GITHUB_TOKEN`
GitHub [automatically provides](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret) a `GITHUB_TOKEN` secret when running workflows inside GitHub. GitHub [automatically provides](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret) a `GITHUB_TOKEN` secret when running workflows inside GitHub.
@@ -230,6 +196,12 @@ If your workflow depends on this token, you need to create a [personal access to
act -s GITHUB_TOKEN=[insert token or leave blank and omit equals for secure input] act -s GITHUB_TOKEN=[insert token or leave blank and omit equals for secure input]
``` ```
If [GitHub CLI](https://cli.github.com/) is installed, the [`gh auth token`](https://cli.github.com/manual/gh_auth_token) command can be used to automatically pass the token to act
```bash
act -s GITHUB_TOKEN="$(gh auth token)"
```
**WARNING**: `GITHUB_TOKEN` will be logged in shell history if not inserted through secure input or (depending on your shell config) the command is prefixed with a whitespace. **WARNING**: `GITHUB_TOKEN` will be logged in shell history if not inserted through secure input or (depending on your shell config) the command is prefixed with a whitespace.
# Known Issues # Known Issues
@@ -304,6 +276,15 @@ If you need an environment that works just like the corresponding GitHub runner
- [`catthehacker/ubuntu:full-*`](https://github.com/catthehacker/docker_images/pkgs/container/ubuntu) - built from Packer template provided by GitHub, see [catthehacker/virtual-environments-fork](https://github.com/catthehacker/virtual-environments-fork) or [catthehacker/docker_images](https://github.com/catthehacker/docker_images) for more information - [`catthehacker/ubuntu:full-*`](https://github.com/catthehacker/docker_images/pkgs/container/ubuntu) - built from Packer template provided by GitHub, see [catthehacker/virtual-environments-fork](https://github.com/catthehacker/virtual-environments-fork) or [catthehacker/docker_images](https://github.com/catthehacker/docker_images) for more information
## Using local runner images
The `--pull` flag is set to true by default due to a breaking on older default docker images. This would pull the docker image everytime act is executed.
Set `--pull` to false if a local docker image is needed
```sh
act --pull=false
```
## Use an alternative runner image ## Use an alternative runner image
To use a different image for the runner, use the `-P` option. To use a different image for the runner, use the `-P` option.
@@ -334,6 +315,14 @@ To run `act` with secrets, you can enter them interactively, supply them as envi
- `act --secret-file my.secrets` - load secrets values from `my.secrets` file. - `act --secret-file my.secrets` - load secrets values from `my.secrets` file.
- secrets file format is the same as `.env` format - secrets file format is the same as `.env` format
# Vars
To run `act` with repository variables that are acessible inside the workflow via ${{ vars.VARIABLE }}, you can enter them interactively or load them from a file. The following options are available for providing github repository variables:
- `act --var VARIABLE=somevalue` - use `somevalue` as the value for `VARIABLE`.
- `act --var-file my.variables` - load variables values from `my.variables` file.
- variables file format is the same as `.env` format
# Configuration # Configuration
You can provide default configuration flags to `act` by either creating a `./.actrc` or a `~/.actrc` file. Any flags in the files will be applied before any flags provided directly on the command line. For example, a file like below will always use the `nektos/act-environments-ubuntu:18.04` image for the `ubuntu-latest` runner: You can provide default configuration flags to `act` by either creating a `./.actrc` or a `~/.actrc` file. Any flags in the files will be applied before any flags provided directly on the command line. For example, a file like below will always use the `nektos/act-environments-ubuntu:18.04` image for the `ubuntu-latest` runner:
@@ -356,10 +345,41 @@ MY_ENV_VAR=MY_ENV_VAR_VALUE
MY_2ND_ENV_VAR="my 2nd env var value" MY_2ND_ENV_VAR="my 2nd env var value"
``` ```
# Skipping jobs
You cannot use the `env` context in job level if conditions, but you can add a custom event property to the `github` context. You can use this method also on step level if conditions.
```yml
on: push
jobs:
deploy:
if: ${{ !github.event.act }} # skip during local actions testing
runs-on: ubuntu-latest
steps:
- run: exit 0
```
And use this `event.json` file with act otherwise the Job will run:
```json
{
"act": true
}
```
Run act like
```sh
act -e event.json
```
_Hint: you can add / append `-e event.json` as a line into `./.actrc`_
# Skipping steps # Skipping steps
Act adds a special environment variable `ACT` that can be used to skip a step that you Act adds a special environment variable `ACT` that can be used to skip a step that you
don't want to run locally. E.g. a step that posts a Slack message or bumps a version number. don't want to run locally. E.g. a step that posts a Slack message or bumps a version number.
**You cannot use this method in job level if conditions, see [Skipping jobs](#skipping-jobs)**
```yml ```yml
- name: Some step - name: Some step
@@ -391,7 +411,7 @@ act pull_request -e pull-request.json
Act will properly provide `github.head_ref` and `github.base_ref` to the action as expected. Act will properly provide `github.head_ref` and `github.base_ref` to the action as expected.
## Pass Inputs to Manually Triggered Workflows # Pass Inputs to Manually Triggered Workflows
Example workflow file Example workflow file
@@ -417,6 +437,14 @@ jobs:
echo "Hello ${{ github.event.inputs.NAME }} and ${{ github.event.inputs.SOME_VALUE }}!" echo "Hello ${{ github.event.inputs.NAME }} and ${{ github.event.inputs.SOME_VALUE }}!"
``` ```
## via input or input-file flag
- `act --input NAME=somevalue` - use `somevalue` as the value for `NAME` input.
- `act --input-file my.input` - load input values from `my.input` file.
- input file format is the same as `.env` format
## via JSON
Example JSON payload file conveniently named `payload.json` Example JSON payload file conveniently named `payload.json`
```json ```json
@@ -453,7 +481,7 @@ Want to contribute to act? Awesome! Check out the [contributing guidelines](CONT
## Manually building from source ## Manually building from source
- Install Go tools 1.18+ - (<https://golang.org/doc/install>) - Install Go tools 1.20+ - (<https://golang.org/doc/install>)
- Clone this repo `git clone git@github.com:nektos/act.git` - Clone this repo `git clone git@github.com:nektos/act.git`
- Run unit tests with `make test` - Run unit tests with `make test`
- Build and install: `make install` - Build and install: `make install`

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.2.57

27
cmd/dir.go Normal file
View File

@@ -0,0 +1,27 @@
package cmd
import (
"os"
"path/filepath"
log "github.com/sirupsen/logrus"
)
var (
UserHomeDir string
CacheHomeDir string
)
func init() {
home, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
UserHomeDir = home
if v := os.Getenv("XDG_CACHE_HOME"); v != "" {
CacheHomeDir = v
} else {
CacheHomeDir = filepath.Join(UserHomeDir, ".cache")
}
}

View File

@@ -16,20 +16,25 @@ type Input struct {
reuseContainers bool reuseContainers bool
bindWorkdir bool bindWorkdir bool
secrets []string secrets []string
vars []string
envs []string envs []string
inputs []string
platforms []string platforms []string
dryrun bool dryrun bool
forcePull bool forcePull bool
forceRebuild bool forceRebuild bool
noOutput bool noOutput bool
envfile string envfile string
inputfile string
secretfile string secretfile string
varfile string
insecureSecrets bool insecureSecrets bool
defaultBranch string defaultBranch string
privileged bool privileged bool
usernsMode string usernsMode string
containerArchitecture string containerArchitecture string
containerDaemonSocket string containerDaemonSocket string
containerOptions string
noWorkflowRecurse bool noWorkflowRecurse bool
useGitIgnore bool useGitIgnore bool
githubInstance string githubInstance string
@@ -37,12 +42,21 @@ type Input struct {
containerCapDrop []string containerCapDrop []string
autoRemove bool autoRemove bool
artifactServerPath string artifactServerPath string
artifactServerAddr string
artifactServerPort string artifactServerPort string
noCacheServer bool
cacheServerPath string
cacheServerAddr string
cacheServerPort uint16
jsonLogger bool jsonLogger bool
noSkipCheckout bool noSkipCheckout bool
remoteName string remoteName string
replaceGheActionWithGithubCom []string replaceGheActionWithGithubCom []string
replaceGheActionTokenWithGithubCom string replaceGheActionTokenWithGithubCom string
matrix []string
actionCachePath string
logPrefixJobID bool
networkName string
} }
func (i *Input) resolve(path string) string { func (i *Input) resolve(path string) string {
@@ -69,6 +83,10 @@ func (i *Input) Secretfile() string {
return i.resolve(i.secretfile) return i.resolve(i.secretfile)
} }
func (i *Input) Varfile() string {
return i.resolve(i.varfile)
}
// Workdir returns path to workdir // Workdir returns path to workdir
func (i *Input) Workdir() string { func (i *Input) Workdir() string {
return i.resolve(".") return i.resolve(".")
@@ -83,3 +101,8 @@ func (i *Input) WorkflowsPath() string {
func (i *Input) EventPath() string { func (i *Input) EventPath() string {
return i.resolve(i.eventPath) return i.resolve(i.eventPath)
} }
// Inputfile returns the path to the input file
func (i *Input) Inputfile() string {
return i.resolve(i.inputfile)
}

140
cmd/notices.go Normal file
View File

@@ -0,0 +1,140 @@
package cmd
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
type Notice struct {
Level string `json:"level"`
Message string `json:"message"`
}
func displayNotices(input *Input) {
select {
case notices := <-noticesLoaded:
if len(notices) > 0 {
noticeLogger := log.New()
if input.jsonLogger {
noticeLogger.SetFormatter(&log.JSONFormatter{})
} else {
noticeLogger.SetFormatter(&log.TextFormatter{
DisableQuote: true,
DisableTimestamp: true,
PadLevelText: true,
})
}
fmt.Printf("\n")
for _, notice := range notices {
level, err := log.ParseLevel(notice.Level)
if err != nil {
level = log.InfoLevel
}
noticeLogger.Log(level, notice.Message)
}
}
case <-time.After(time.Second * 1):
log.Debugf("Timeout waiting for notices")
}
}
var noticesLoaded = make(chan []Notice)
func loadVersionNotices(version string) {
go func() {
noticesLoaded <- getVersionNotices(version)
}()
}
const NoticeURL = "https://api.nektosact.com/notices"
func getVersionNotices(version string) []Notice {
if os.Getenv("ACT_DISABLE_VERSION_CHECK") == "1" {
return nil
}
noticeURL, err := url.Parse(NoticeURL)
if err != nil {
log.Error(err)
return nil
}
query := noticeURL.Query()
query.Add("os", runtime.GOOS)
query.Add("arch", runtime.GOARCH)
query.Add("version", version)
noticeURL.RawQuery = query.Encode()
client := &http.Client{}
req, err := http.NewRequest("GET", noticeURL.String(), nil)
if err != nil {
log.Debug(err)
return nil
}
etag := loadNoticesEtag()
if etag != "" {
log.Debugf("Conditional GET for notices etag=%s", etag)
req.Header.Set("If-None-Match", etag)
}
resp, err := client.Do(req)
if err != nil {
log.Debug(err)
return nil
}
newEtag := resp.Header.Get("Etag")
if newEtag != "" {
log.Debugf("Saving notices etag=%s", newEtag)
saveNoticesEtag(newEtag)
}
defer resp.Body.Close()
notices := []Notice{}
if resp.StatusCode == 304 {
log.Debug("No new notices")
return nil
}
if err := json.NewDecoder(resp.Body).Decode(&notices); err != nil {
log.Debug(err)
return nil
}
return notices
}
func loadNoticesEtag() string {
p := etagPath()
content, err := os.ReadFile(p)
if err != nil {
log.Debugf("Unable to load etag from %s: %e", p, err)
}
return strings.TrimSuffix(string(content), "\n")
}
func saveNoticesEtag(etag string) {
p := etagPath()
err := os.WriteFile(p, []byte(strings.TrimSuffix(etag, "\n")), 0o600)
if err != nil {
log.Debugf("Unable to save etag to %s: %e", p, err)
}
}
func etagPath() string {
dir := filepath.Join(CacheHomeDir, "act")
if err := os.MkdirAll(dir, 0o777); err != nil {
log.Fatal(err)
}
return filepath.Join(dir, ".notices.etag")
}

View File

@@ -15,7 +15,7 @@ func (i *Input) newPlatforms() map[string]string {
for _, p := range i.platforms { for _, p := range i.platforms {
pParts := strings.Split(p, "=") pParts := strings.Split(p, "=")
if len(pParts) == 2 { if len(pParts) == 2 {
platforms[pParts[0]] = pParts[1] platforms[strings.ToLower(pParts[0])] = pParts[1]
} }
} }
return platforms return platforms

View File

@@ -12,13 +12,16 @@ import (
"strings" "strings"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/adrg/xdg"
"github.com/andreaskoch/go-fswatch" "github.com/andreaskoch/go-fswatch"
docker_container "github.com/docker/docker/api/types/container"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/mitchellh/go-homedir"
gitignore "github.com/sabhiram/go-gitignore" gitignore "github.com/sabhiram/go-gitignore"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"github.com/nektos/act/pkg/artifactcache"
"github.com/nektos/act/pkg/artifacts" "github.com/nektos/act/pkg/artifacts"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
@@ -30,13 +33,14 @@ import (
func Execute(ctx context.Context, version string) { func Execute(ctx context.Context, version string) {
input := new(Input) input := new(Input)
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "act [event name to run] [flags]\n\nIf no event name passed, will default to \"on: push\"\nIf actions handles only one event it will be used as default instead of \"on: push\"", Use: "act [event name to run] [flags]\n\nIf no event name passed, will default to \"on: push\"\nIf actions handles only one event it will be used as default instead of \"on: push\"",
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.", Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.",
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: newRunCommand(ctx, input), RunE: newRunCommand(ctx, input),
PersistentPreRun: setupLogging, PersistentPreRun: setup(input),
Version: version, PersistentPostRun: cleanup(input),
SilenceUsage: true, Version: version,
SilenceUsage: true,
} }
rootCmd.Flags().BoolP("watch", "w", false, "watch the contents of the local repo and run when files change") rootCmd.Flags().BoolP("watch", "w", false, "watch the contents of the local repo and run when files change")
rootCmd.Flags().BoolP("list", "l", false, "list workflows") rootCmd.Flags().BoolP("list", "l", false, "list workflows")
@@ -46,12 +50,14 @@ func Execute(ctx context.Context, version string) {
rootCmd.Flags().StringVar(&input.remoteName, "remote-name", "origin", "git remote name that will be used to retrieve url of git repo") rootCmd.Flags().StringVar(&input.remoteName, "remote-name", "origin", "git remote name that will be used to retrieve url of git repo")
rootCmd.Flags().StringArrayVarP(&input.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)") rootCmd.Flags().StringArrayVarP(&input.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)")
rootCmd.Flags().StringArrayVar(&input.vars, "var", []string{}, "variable to make available to actions with optional value (e.g. --var myvar=foo or --var myvar)")
rootCmd.Flags().StringArrayVarP(&input.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)") rootCmd.Flags().StringArrayVarP(&input.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)")
rootCmd.Flags().StringArrayVarP(&input.inputs, "input", "", []string{}, "action input to make available to actions (e.g. --input myinput=foo)")
rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)") rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)")
rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "don't remove container(s) on successfully completed workflow(s) to maintain state between runs") rootCmd.Flags().BoolVarP(&input.reuseContainers, "reuse", "r", false, "don't remove container(s) on successfully completed workflow(s) to maintain state between runs")
rootCmd.Flags().BoolVarP(&input.bindWorkdir, "bind", "b", false, "bind working directory to container, rather than copy") rootCmd.Flags().BoolVarP(&input.bindWorkdir, "bind", "b", false, "bind working directory to container, rather than copy")
rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", false, "pull docker image(s) even if already present") rootCmd.Flags().BoolVarP(&input.forcePull, "pull", "p", true, "pull docker image(s) even if already present")
rootCmd.Flags().BoolVarP(&input.forceRebuild, "rebuild", "", false, "rebuild local action docker image(s) even if already present") rootCmd.Flags().BoolVarP(&input.forceRebuild, "rebuild", "", true, "rebuild local action docker image(s) even if already present")
rootCmd.Flags().BoolVarP(&input.autodetectEvent, "detect-event", "", false, "Use first event type from workflow as event that triggered the workflow") rootCmd.Flags().BoolVarP(&input.autodetectEvent, "detect-event", "", false, "Use first event type from workflow as event that triggered the workflow")
rootCmd.Flags().StringVarP(&input.eventPath, "eventpath", "e", "", "path to event JSON file") rootCmd.Flags().StringVarP(&input.eventPath, "eventpath", "e", "", "path to event JSON file")
rootCmd.Flags().StringVar(&input.defaultBranch, "defaultbranch", "", "the name of the main branch") rootCmd.Flags().StringVar(&input.defaultBranch, "defaultbranch", "", "the name of the main branch")
@@ -63,23 +69,35 @@ func Execute(ctx context.Context, version string) {
rootCmd.Flags().BoolVar(&input.autoRemove, "rm", false, "automatically remove container(s)/volume(s) after a workflow(s) failure") rootCmd.Flags().BoolVar(&input.autoRemove, "rm", false, "automatically remove container(s)/volume(s) after a workflow(s) failure")
rootCmd.Flags().StringArrayVarP(&input.replaceGheActionWithGithubCom, "replace-ghe-action-with-github-com", "", []string{}, "If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com =github/super-linter)") rootCmd.Flags().StringArrayVarP(&input.replaceGheActionWithGithubCom, "replace-ghe-action-with-github-com", "", []string{}, "If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com =github/super-linter)")
rootCmd.Flags().StringVar(&input.replaceGheActionTokenWithGithubCom, "replace-ghe-action-token-with-github-com", "", "If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token") rootCmd.Flags().StringVar(&input.replaceGheActionTokenWithGithubCom, "replace-ghe-action-token-with-github-com", "", "If you are using replace-ghe-action-with-github-com and you want to use private actions on GitHub, you have to set personal access token")
rootCmd.Flags().StringArrayVarP(&input.matrix, "matrix", "", []string{}, "specify which matrix configuration to include (e.g. --matrix java:13")
rootCmd.PersistentFlags().StringVarP(&input.actor, "actor", "a", "nektos/act", "user that triggered the event") rootCmd.PersistentFlags().StringVarP(&input.actor, "actor", "a", "nektos/act", "user that triggered the event")
rootCmd.PersistentFlags().StringVarP(&input.workflowsPath, "workflows", "W", "./.github/workflows/", "path to workflow file(s)") rootCmd.PersistentFlags().StringVarP(&input.workflowsPath, "workflows", "W", "./.github/workflows/", "path to workflow file(s)")
rootCmd.PersistentFlags().BoolVarP(&input.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag") rootCmd.PersistentFlags().BoolVarP(&input.noWorkflowRecurse, "no-recurse", "", false, "Flag to disable running workflows from subdirectories of specified path in '--workflows'/'-W' flag")
rootCmd.PersistentFlags().StringVarP(&input.workdir, "directory", "C", ".", "working directory") rootCmd.PersistentFlags().StringVarP(&input.workdir, "directory", "C", ".", "working directory")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output") rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVar(&input.jsonLogger, "json", false, "Output logs in json format") rootCmd.PersistentFlags().BoolVar(&input.jsonLogger, "json", false, "Output logs in json format")
rootCmd.PersistentFlags().BoolVar(&input.logPrefixJobID, "log-prefix-job-id", false, "Output the job id within non-json logs instead of the entire name")
rootCmd.PersistentFlags().BoolVarP(&input.noOutput, "quiet", "q", false, "disable logging of output from steps") rootCmd.PersistentFlags().BoolVarP(&input.noOutput, "quiet", "q", false, "disable logging of output from steps")
rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "dryrun mode") rootCmd.PersistentFlags().BoolVarP(&input.dryrun, "dryrun", "n", false, "dryrun mode")
rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)") rootCmd.PersistentFlags().StringVarP(&input.secretfile, "secret-file", "", ".secrets", "file with list of secrets to read from (e.g. --secret-file .secrets)")
rootCmd.PersistentFlags().StringVarP(&input.varfile, "var-file", "", ".vars", "file with list of vars to read from (e.g. --var-file .vars)")
rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.") rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.")
rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers") rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers")
rootCmd.PersistentFlags().StringVarP(&input.inputfile, "input-file", "", ".input", "input file to read and use as action input")
rootCmd.PersistentFlags().StringVarP(&input.containerArchitecture, "container-architecture", "", "", "Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.") rootCmd.PersistentFlags().StringVarP(&input.containerArchitecture, "container-architecture", "", "", "Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.")
rootCmd.PersistentFlags().StringVarP(&input.containerDaemonSocket, "container-daemon-socket", "", "/var/run/docker.sock", "Path to Docker daemon socket which will be mounted to containers") rootCmd.PersistentFlags().StringVarP(&input.containerDaemonSocket, "container-daemon-socket", "", "", "URI to Docker Engine socket (e.g.: unix://~/.docker/run/docker.sock or - to disable bind mounting the socket)")
rootCmd.PersistentFlags().StringVarP(&input.containerOptions, "container-options", "", "", "Custom docker container options for the job container without an options property in the job definition")
rootCmd.PersistentFlags().StringVarP(&input.githubInstance, "github-instance", "", "github.com", "GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server.") rootCmd.PersistentFlags().StringVarP(&input.githubInstance, "github-instance", "", "github.com", "GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server.")
rootCmd.PersistentFlags().StringVarP(&input.artifactServerPath, "artifact-server-path", "", "", "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.") rootCmd.PersistentFlags().StringVarP(&input.artifactServerPath, "artifact-server-path", "", "", "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.")
rootCmd.PersistentFlags().StringVarP(&input.artifactServerPort, "artifact-server-port", "", "34567", "Defines the port where the artifact server listens (will only bind to localhost).") rootCmd.PersistentFlags().StringVarP(&input.artifactServerAddr, "artifact-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the artifact server binds.")
rootCmd.PersistentFlags().StringVarP(&input.artifactServerPort, "artifact-server-port", "", "34567", "Defines the port where the artifact server listens.")
rootCmd.PersistentFlags().BoolVarP(&input.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout") rootCmd.PersistentFlags().BoolVarP(&input.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout")
rootCmd.PersistentFlags().BoolVarP(&input.noCacheServer, "no-cache-server", "", false, "Disable cache server")
rootCmd.PersistentFlags().StringVarP(&input.cacheServerPath, "cache-server-path", "", filepath.Join(CacheHomeDir, "actcache"), "Defines the path where the cache server stores caches.")
rootCmd.PersistentFlags().StringVarP(&input.cacheServerAddr, "cache-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the cache server binds.")
rootCmd.PersistentFlags().Uint16VarP(&input.cacheServerPort, "cache-server-port", "", 0, "Defines the port where the artifact server listens. 0 means a randomly available port.")
rootCmd.PersistentFlags().StringVarP(&input.actionCachePath, "action-cache-path", "", filepath.Join(CacheHomeDir, "act"), "Defines the path where the actions get cached and host workspaces created.")
rootCmd.PersistentFlags().StringVarP(&input.networkName, "network", "", "host", "Sets a docker network name. Defaults to host.")
rootCmd.SetArgs(args()) rootCmd.SetArgs(args())
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
@@ -88,26 +106,52 @@ func Execute(ctx context.Context, version string) {
} }
func configLocations() []string { func configLocations() []string {
home, err := homedir.Dir() configFileName := ".actrc"
if err != nil {
log.Fatal(err)
}
// reference: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html // reference: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
var actrcXdg string var actrcXdg string
if xdg, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok && xdg != "" { for _, fileName := range []string{"act/actrc", configFileName} {
actrcXdg = filepath.Join(xdg, ".actrc") if foundConfig, err := xdg.SearchConfigFile(fileName); foundConfig != "" && err == nil {
} else { actrcXdg = foundConfig
actrcXdg = filepath.Join(home, ".config", ".actrc") break
}
} }
return []string{ return []string{
filepath.Join(home, ".actrc"), filepath.Join(UserHomeDir, configFileName),
actrcXdg, actrcXdg,
filepath.Join(".", ".actrc"), filepath.Join(".", configFileName),
} }
} }
var commonSocketPaths = []string{
"/var/run/docker.sock",
"/run/podman/podman.sock",
"$HOME/.colima/docker.sock",
"$XDG_RUNTIME_DIR/docker.sock",
"$XDG_RUNTIME_DIR/podman/podman.sock",
`\\.\pipe\docker_engine`,
"$HOME/.docker/run/docker.sock",
}
// returns socket path or false if not found any
func socketLocation() (string, bool) {
if dockerHost, exists := os.LookupEnv("DOCKER_HOST"); exists {
return dockerHost, true
}
for _, p := range commonSocketPaths {
if _, err := os.Lstat(os.ExpandEnv(p)); err == nil {
if strings.HasPrefix(p, `\\.\`) {
return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), true
}
return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), true
}
}
return "", false
}
func args() []string { func args() []string {
actrc := configLocations() actrc := configLocations()
@@ -121,15 +165,6 @@ func args() []string {
} }
func bugReport(ctx context.Context, version string) error { func bugReport(ctx context.Context, version string) error {
var commonSocketPaths = []string{
"/var/run/docker.sock",
"/var/run/podman/podman.sock",
"$HOME/.colima/docker.sock",
"$XDG_RUNTIME_DIR/docker.sock",
`\\.\pipe\docker_engine`,
"$HOME/.docker/run/docker.sock",
}
sprintf := func(key, val string) string { sprintf := func(key, val string) string {
return fmt.Sprintf("%-24s%s\n", key, val) return fmt.Sprintf("%-24s%s\n", key, val)
} }
@@ -140,19 +175,20 @@ func bugReport(ctx context.Context, version string) error {
report += sprintf("NumCPU:", fmt.Sprint(runtime.NumCPU())) report += sprintf("NumCPU:", fmt.Sprint(runtime.NumCPU()))
var dockerHost string var dockerHost string
if dockerHost = os.Getenv("DOCKER_HOST"); dockerHost == "" { var exists bool
dockerHost = "DOCKER_HOST environment variable is unset/empty." if dockerHost, exists = os.LookupEnv("DOCKER_HOST"); !exists {
dockerHost = "DOCKER_HOST environment variable is not set"
} else if dockerHost == "" {
dockerHost = "DOCKER_HOST environment variable is empty."
} }
report += sprintf("Docker host:", dockerHost) report += sprintf("Docker host:", dockerHost)
report += fmt.Sprintln("Sockets found:") report += fmt.Sprintln("Sockets found:")
for _, p := range commonSocketPaths { for _, p := range commonSocketPaths {
if strings.HasPrefix(p, `$`) { if _, err := os.Lstat(os.ExpandEnv(p)); err != nil {
v := strings.Split(p, `/`)[0]
p = strings.Replace(p, v, os.Getenv(strings.TrimPrefix(v, `$`)), 1)
}
if _, err := os.Stat(p); err != nil {
continue continue
} else if _, err := os.Stat(os.ExpandEnv(p)); err != nil {
report += fmt.Sprintf("\t%s(broken)\n", p)
} else { } else {
report += fmt.Sprintf("\t%s\n", p) report += fmt.Sprintf("\t%s\n", p)
} }
@@ -231,7 +267,8 @@ func readArgsFile(file string, split bool) []string {
}() }()
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
for scanner.Scan() { for scanner.Scan() {
arg := strings.TrimSpace(scanner.Text()) arg := os.ExpandEnv(strings.TrimSpace(scanner.Text()))
if strings.HasPrefix(arg, "-") && split { if strings.HasPrefix(arg, "-") && split {
args = append(args, regexp.MustCompile(`\s`).Split(arg, 2)...) args = append(args, regexp.MustCompile(`\s`).Split(arg, 2)...)
} else if !split { } else if !split {
@@ -241,16 +278,57 @@ func readArgsFile(file string, split bool) []string {
return args return args
} }
func setupLogging(cmd *cobra.Command, _ []string) { func setup(_ *Input) func(*cobra.Command, []string) {
verbose, _ := cmd.Flags().GetBool("verbose") return func(cmd *cobra.Command, _ []string) {
if verbose { verbose, _ := cmd.Flags().GetBool("verbose")
log.SetLevel(log.DebugLevel) if verbose {
log.SetLevel(log.DebugLevel)
}
loadVersionNotices(cmd.Version)
} }
} }
func cleanup(inputs *Input) func(*cobra.Command, []string) {
return func(cmd *cobra.Command, _ []string) {
displayNotices(inputs)
}
}
func parseEnvs(env []string, envs map[string]string) bool {
if env != nil {
for _, envVar := range env {
e := strings.SplitN(envVar, `=`, 2)
if len(e) == 2 {
envs[e[0]] = e[1]
} else {
envs[e[0]] = ""
}
}
return true
}
return false
}
func readYamlFile(file string) (map[string]string, error) {
content, err := os.ReadFile(file)
if err != nil {
return nil, err
}
ret := map[string]string{}
if err = yaml.Unmarshal(content, &ret); err != nil {
return nil, err
}
return ret, nil
}
func readEnvs(path string, envs map[string]string) bool { func readEnvs(path string, envs map[string]string) bool {
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
env, err := godotenv.Read(path) var env map[string]string
if ext := filepath.Ext(path); ext == ".yml" || ext == ".yaml" {
env, err = readYamlFile(path)
} else {
env, err = godotenv.Read(path)
}
if err != nil { if err != nil {
log.Fatalf("Error loading from %s: %v", path, err) log.Fatalf("Error loading from %s: %v", path, err)
} }
@@ -262,6 +340,35 @@ func readEnvs(path string, envs map[string]string) bool {
return false return false
} }
func parseMatrix(matrix []string) map[string]map[string]bool {
// each matrix entry should be of the form - string:string
r := regexp.MustCompile(":")
matrixes := make(map[string]map[string]bool)
for _, m := range matrix {
matrix := r.Split(m, 2)
if len(matrix) < 2 {
log.Fatalf("Invalid matrix format. Failed to parse %s", m)
}
if _, ok := matrixes[matrix[0]]; !ok {
matrixes[matrix[0]] = make(map[string]bool)
}
matrixes[matrix[0]][matrix[1]] = true
}
return matrixes
}
func isDockerHostURI(daemonPath string) bool {
if protoIndex := strings.Index(daemonPath, "://"); protoIndex != -1 {
scheme := daemonPath[:protoIndex]
if strings.IndexFunc(scheme, func(r rune) bool {
return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z')
}) == -1 {
return true
}
}
return false
}
//nolint:gocyclo //nolint:gocyclo
func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error { func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
@@ -273,33 +380,58 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
return bugReport(ctx, cmd.Version) return bugReport(ctx, cmd.Version)
} }
// Prefer DOCKER_HOST, don't override it
socketPath, hasDockerHost := os.LookupEnv("DOCKER_HOST")
if !hasDockerHost {
// a - in containerDaemonSocket means don't mount, preserve this value
// otherwise if input.containerDaemonSocket is a filepath don't use it as socketPath
skipMount := input.containerDaemonSocket == "-" || !isDockerHostURI(input.containerDaemonSocket)
if input.containerDaemonSocket != "" && !skipMount {
socketPath = input.containerDaemonSocket
} else {
socket, found := socketLocation()
if !found {
log.Errorln("daemon Docker Engine socket not found and containerDaemonSocket option was not set")
} else {
socketPath = socket
}
if !skipMount {
input.containerDaemonSocket = socketPath
}
}
os.Setenv("DOCKER_HOST", socketPath)
}
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" && input.containerArchitecture == "" { if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" && input.containerArchitecture == "" {
l := log.New() l := log.New()
l.SetFormatter(&log.TextFormatter{ l.SetFormatter(&log.TextFormatter{
DisableQuote: true, DisableQuote: true,
DisableTimestamp: true, DisableTimestamp: true,
}) })
l.Warnf(" \U000026A0 You are using Apple M1 chip and you have not specified container architecture, you might encounter issues while running act. If so, try running it with '--container-architecture linux/amd64'. \U000026A0 \n") l.Warnf(" \U000026A0 You are using Apple M-series chip and you have not specified container architecture, you might encounter issues while running act. If so, try running it with '--container-architecture linux/amd64'. \U000026A0 \n")
} }
log.Debugf("Loading environment from %s", input.Envfile()) log.Debugf("Loading environment from %s", input.Envfile())
envs := make(map[string]string) envs := make(map[string]string)
if input.envs != nil { _ = parseEnvs(input.envs, envs)
for _, envVar := range input.envs {
e := strings.SplitN(envVar, `=`, 2)
if len(e) == 2 {
envs[e[0]] = e[1]
} else {
envs[e[0]] = ""
}
}
}
_ = readEnvs(input.Envfile(), envs) _ = readEnvs(input.Envfile(), envs)
log.Debugf("Loading action inputs from %s", input.Inputfile())
inputs := make(map[string]string)
_ = parseEnvs(input.inputs, inputs)
_ = readEnvs(input.Inputfile(), inputs)
log.Debugf("Loading secrets from %s", input.Secretfile()) log.Debugf("Loading secrets from %s", input.Secretfile())
secrets := newSecrets(input.secrets) secrets := newSecrets(input.secrets)
_ = readEnvs(input.Secretfile(), secrets) _ = readEnvs(input.Secretfile(), secrets)
log.Debugf("Loading vars from %s", input.Varfile())
vars := newSecrets(input.vars)
_ = readEnvs(input.Varfile(), vars)
matrixes := parseMatrix(input.matrix)
log.Debugf("Evaluated matrix inclusions: %v", matrixes)
planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), input.noWorkflowRecurse) planner, err := model.NewWorkflowPlanner(input.WorkflowsPath(), input.noWorkflowRecurse)
if err != nil { if err != nil {
return err return err
@@ -329,7 +461,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
var filterPlan *model.Plan var filterPlan *model.Plan
// Determine the event name to be filtered // Determine the event name to be filtered
var filterEventName string = "" var filterEventName string
if len(args) > 0 { if len(args) > 0 {
log.Debugf("Using first passed in arguments event for filtering: %s", args[0]) log.Debugf("Using first passed in arguments event for filtering: %s", args[0])
@@ -341,23 +473,35 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
filterEventName = events[0] filterEventName = events[0]
} }
var plannerErr error
if jobID != "" { if jobID != "" {
log.Debugf("Preparing plan with a job: %s", jobID) log.Debugf("Preparing plan with a job: %s", jobID)
filterPlan = planner.PlanJob(jobID) filterPlan, plannerErr = planner.PlanJob(jobID)
} else if filterEventName != "" { } else if filterEventName != "" {
log.Debugf("Preparing plan for a event: %s", filterEventName) log.Debugf("Preparing plan for a event: %s", filterEventName)
filterPlan = planner.PlanEvent(filterEventName) filterPlan, plannerErr = planner.PlanEvent(filterEventName)
} else { } else {
log.Debugf("Preparing plan with all jobs") log.Debugf("Preparing plan with all jobs")
filterPlan = planner.PlanAll() filterPlan, plannerErr = planner.PlanAll()
}
if filterPlan == nil && plannerErr != nil {
return plannerErr
} }
if list { if list {
return printList(filterPlan) err = printList(filterPlan)
if err != nil {
return err
}
return plannerErr
} }
if graph { if graph {
return drawGraph(filterPlan) err = drawGraph(filterPlan)
if err != nil {
return err
}
return plannerErr
} }
// plan with triggered jobs // plan with triggered jobs
@@ -385,10 +529,13 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
// build the plan for this run // build the plan for this run
if jobID != "" { if jobID != "" {
log.Debugf("Planning job: %s", jobID) log.Debugf("Planning job: %s", jobID)
plan = planner.PlanJob(jobID) plan, plannerErr = planner.PlanJob(jobID)
} else { } else {
log.Debugf("Planning jobs for event: %s", eventName) log.Debugf("Planning jobs for event: %s", eventName)
plan = planner.PlanEvent(eventName) plan, plannerErr = planner.PlanEvent(eventName)
}
if plan == nil && plannerErr != nil {
return plannerErr
} }
// check to see if the main branch was defined // check to see if the main branch was defined
@@ -414,6 +561,19 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
input.platforms = readArgsFile(cfgLocations[0], true) input.platforms = readArgsFile(cfgLocations[0], true)
} }
} }
deprecationWarning := "--%s is deprecated and will be removed soon, please switch to cli: `--container-options \"%[2]s\"` or `.actrc`: `--container-options %[2]s`."
if input.privileged {
log.Warnf(deprecationWarning, "privileged", "--privileged")
}
if len(input.usernsMode) > 0 {
log.Warnf(deprecationWarning, "userns", fmt.Sprintf("--userns=%s", input.usernsMode))
}
if len(input.containerCapAdd) > 0 {
log.Warnf(deprecationWarning, "container-cap-add", fmt.Sprintf("--cap-add=%s", input.containerCapAdd))
}
if len(input.containerCapDrop) > 0 {
log.Warnf(deprecationWarning, "container-cap-drop", fmt.Sprintf("--cap-drop=%s", input.containerCapDrop))
}
// run the plan // run the plan
config := &runner.Config{ config := &runner.Config{
@@ -425,11 +585,15 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
ForceRebuild: input.forceRebuild, ForceRebuild: input.forceRebuild,
ReuseContainers: input.reuseContainers, ReuseContainers: input.reuseContainers,
Workdir: input.Workdir(), Workdir: input.Workdir(),
ActionCacheDir: input.actionCachePath,
BindWorkdir: input.bindWorkdir, BindWorkdir: input.bindWorkdir,
LogOutput: !input.noOutput, LogOutput: !input.noOutput,
JSONLogger: input.jsonLogger, JSONLogger: input.jsonLogger,
LogPrefixJobID: input.logPrefixJobID,
Env: envs, Env: envs,
Secrets: secrets, Secrets: secrets,
Vars: vars,
Inputs: inputs,
Token: secrets["GITHUB_TOKEN"], Token: secrets["GITHUB_TOKEN"],
InsecureSecrets: input.insecureSecrets, InsecureSecrets: input.insecureSecrets,
Platforms: input.newPlatforms(), Platforms: input.newPlatforms(),
@@ -437,44 +601,68 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
UsernsMode: input.usernsMode, UsernsMode: input.usernsMode,
ContainerArchitecture: input.containerArchitecture, ContainerArchitecture: input.containerArchitecture,
ContainerDaemonSocket: input.containerDaemonSocket, ContainerDaemonSocket: input.containerDaemonSocket,
ContainerOptions: input.containerOptions,
UseGitIgnore: input.useGitIgnore, UseGitIgnore: input.useGitIgnore,
GitHubInstance: input.githubInstance, GitHubInstance: input.githubInstance,
ContainerCapAdd: input.containerCapAdd, ContainerCapAdd: input.containerCapAdd,
ContainerCapDrop: input.containerCapDrop, ContainerCapDrop: input.containerCapDrop,
AutoRemove: input.autoRemove, AutoRemove: input.autoRemove,
ArtifactServerPath: input.artifactServerPath, ArtifactServerPath: input.artifactServerPath,
ArtifactServerAddr: input.artifactServerAddr,
ArtifactServerPort: input.artifactServerPort, ArtifactServerPort: input.artifactServerPort,
NoSkipCheckout: input.noSkipCheckout, NoSkipCheckout: input.noSkipCheckout,
RemoteName: input.remoteName, RemoteName: input.remoteName,
ReplaceGheActionWithGithubCom: input.replaceGheActionWithGithubCom, ReplaceGheActionWithGithubCom: input.replaceGheActionWithGithubCom,
ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom, ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom,
Matrix: matrixes,
ContainerNetworkMode: docker_container.NetworkMode(input.networkName),
} }
r, err := runner.New(config) r, err := runner.New(config)
if err != nil { if err != nil {
return err return err
} }
cancel := artifacts.Serve(ctx, input.artifactServerPath, input.artifactServerPort) cancel := artifacts.Serve(ctx, input.artifactServerPath, input.artifactServerAddr, input.artifactServerPort)
const cacheURLKey = "ACTIONS_CACHE_URL"
var cacheHandler *artifactcache.Handler
if !input.noCacheServer && envs[cacheURLKey] == "" {
var err error
cacheHandler, err = artifactcache.StartHandler(input.cacheServerPath, input.cacheServerAddr, input.cacheServerPort, common.Logger(ctx))
if err != nil {
return err
}
envs[cacheURLKey] = cacheHandler.ExternalURL() + "/"
}
ctx = common.WithDryrun(ctx, input.dryrun) ctx = common.WithDryrun(ctx, input.dryrun)
if watch, err := cmd.Flags().GetBool("watch"); err != nil { if watch, err := cmd.Flags().GetBool("watch"); err != nil {
return err return err
} else if watch { } else if watch {
return watchAndRun(ctx, r.NewPlanExecutor(plan)) err = watchAndRun(ctx, r.NewPlanExecutor(plan))
if err != nil {
return err
}
return plannerErr
} }
executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error { executor := r.NewPlanExecutor(plan).Finally(func(ctx context.Context) error {
cancel() cancel()
_ = cacheHandler.Close()
return nil return nil
}) })
return executor(ctx) err = executor(ctx)
if err != nil {
return err
}
return plannerErr
} }
} }
func defaultImageSurvey(actrc string) error { func defaultImageSurvey(actrc string) error {
var answer string var answer string
confirmation := &survey.Select{ confirmation := &survey.Select{
Message: "Please choose the default image you want to use with act:\n\n - Large size image: +20GB Docker image, includes almost all tools used on GitHub Actions (IMPORTANT: currently only ubuntu-18.04 platform is available)\n - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with all actions\n - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions\n\nDefault image and other options can be changed manually in ~/.actrc (please refer to https://github.com/nektos/act#configuration for additional information about file structure)", Message: "Please choose the default image you want to use with act:\n - Large size image: ca. 17GB download + 53.1GB storage, you will need 75GB of free disk space, snapshots of GitHub Hosted Runners without snap and pulled docker images\n - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with most actions\n - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions\n\nDefault image and other options can be changed manually in ~/.actrc (please refer to https://github.com/nektos/act#configuration for additional information about file structure)",
Help: "If you want to know why act asks you that, please go to https://github.com/nektos/act/issues/107", Help: "If you want to know why act asks you that, please go to https://github.com/nektos/act/issues/107",
Default: "Medium", Default: "Medium",
Options: []string{"Large", "Medium", "Micro"}, Options: []string{"Large", "Medium", "Micro"},
@@ -488,11 +676,11 @@ func defaultImageSurvey(actrc string) error {
var option string var option string
switch answer { switch answer {
case "Large": case "Large":
option = "-P ubuntu-latest=catthehacker/ubuntu:full-latest\n-P ubuntu-latest=catthehacker/ubuntu:full-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:full-18.04\n" option = "-P ubuntu-latest=catthehacker/ubuntu:full-latest\n-P ubuntu-22.04=catthehacker/ubuntu:full-22.04\n-P ubuntu-20.04=catthehacker/ubuntu:full-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:full-18.04\n"
case "Medium": case "Medium":
option = "-P ubuntu-latest=catthehacker/ubuntu:act-latest\n-P ubuntu-22.04=catthehacker/ubuntu:act-22.04\n-P ubuntu-20.04=catthehacker/ubuntu:act-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:act-18.04\n" option = "-P ubuntu-latest=catthehacker/ubuntu:act-latest\n-P ubuntu-22.04=catthehacker/ubuntu:act-22.04\n-P ubuntu-20.04=catthehacker/ubuntu:act-20.04\n-P ubuntu-18.04=catthehacker/ubuntu:act-18.04\n"
case "Micro": case "Micro":
option = "-P ubuntu-latest=node:16-buster-slim\n-P -P ubuntu-22.04=node:16-bullseye-slim\n ubuntu-20.04=node:16-buster-slim\n-P ubuntu-18.04=node:16-buster-slim\n" option = "-P ubuntu-latest=node:16-buster-slim\n-P ubuntu-22.04=node:16-bullseye-slim\n-P ubuntu-20.04=node:16-buster-slim\n-P ubuntu-18.04=node:16-buster-slim\n"
} }
f, err := os.Create(actrc) f, err := os.Create(actrc)
@@ -515,45 +703,47 @@ func defaultImageSurvey(actrc string) error {
} }
func watchAndRun(ctx context.Context, fn common.Executor) error { func watchAndRun(ctx context.Context, fn common.Executor) error {
recurse := true
checkIntervalInSeconds := 2
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { if err != nil {
return err return err
} }
var ignore *gitignore.GitIgnore ignoreFile := filepath.Join(dir, ".gitignore")
if _, err := os.Stat(filepath.Join(dir, ".gitignore")); !os.IsNotExist(err) { ignore := &gitignore.GitIgnore{}
ignore, _ = gitignore.CompileIgnoreFile(filepath.Join(dir, ".gitignore")) if info, err := os.Stat(ignoreFile); err == nil && !info.IsDir() {
} else { ignore, err = gitignore.CompileIgnoreFile(ignoreFile)
ignore = &gitignore.GitIgnore{} if err != nil {
return fmt.Errorf("compile %q: %w", ignoreFile, err)
}
} }
folderWatcher := fswatch.NewFolderWatcher( folderWatcher := fswatch.NewFolderWatcher(
dir, dir,
recurse, true,
ignore.MatchesPath, ignore.MatchesPath,
checkIntervalInSeconds, 2, // 2 seconds
) )
folderWatcher.Start() folderWatcher.Start()
defer folderWatcher.Stop()
go func() { // run once before watching
for folderWatcher.IsRunning() { if err := fn(ctx); err != nil {
if err = fn(ctx); err != nil { return err
break }
}
log.Debugf("Watching %s for changes", dir) for folderWatcher.IsRunning() {
for changes := range folderWatcher.ChangeDetails() { log.Debugf("Watching %s for changes", dir)
log.Debugf("%s", changes.String()) select {
if err = fn(ctx); err != nil { case <-ctx.Done():
break return nil
} case changes := <-folderWatcher.ChangeDetails():
log.Debugf("Watching %s for changes", dir) log.Debugf("%s", changes.String())
if err := fn(ctx); err != nil {
return err
} }
} }
}() }
<-ctx.Done()
folderWatcher.Stop() return nil
return err
} }

111
go.mod
View File

@@ -1,85 +1,90 @@
module github.com/nektos/act module github.com/nektos/act
go 1.18 go 1.20
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.6 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Masterminds/semver v1.5.0 github.com/Masterminds/semver v1.5.0
github.com/adrg/xdg v0.4.0
github.com/andreaskoch/go-fswatch v1.0.0 github.com/andreaskoch/go-fswatch v1.0.0
github.com/creack/pty v1.1.18 github.com/creack/pty v1.1.21
github.com/docker/cli v20.10.21+incompatible github.com/docker/cli v24.0.7+incompatible
github.com/docker/distribution v2.8.1+incompatible github.com/docker/distribution v2.8.3+incompatible
github.com/docker/docker v20.10.21+incompatible github.com/docker/docker v24.0.7+incompatible // 24.0 branch
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/go-git/go-billy/v5 v5.3.1 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.4.2 github.com/go-git/go-git/v5 v5.11.0
github.com/go-ini/ini v1.67.0 github.com/imdario/mergo v0.3.16
github.com/imdario/mergo v0.3.13 github.com/joho/godotenv v1.5.1
github.com/joho/godotenv v1.4.0
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-isatty v0.0.20
github.com/mitchellh/go-homedir v1.1.0 github.com/moby/buildkit v0.12.4
github.com/moby/buildkit v0.10.6 github.com/moby/patternmatcher v0.6.0
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/image-spec v1.1.0-rc5
github.com/opencontainers/selinux v1.10.2 github.com/opencontainers/selinux v1.11.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rhysd/actionlint v1.6.22 github.com/rhysd/actionlint v1.6.26
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.4
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a
go.etcd.io/bbolt v1.3.8
golang.org/x/term v0.15.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.4.0 gotest.tools/v3 v3.5.1
) )
require ( require (
github.com/Microsoft/go-winio v0.5.2 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/hcsshim v0.9.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/containerd/cgroups v1.0.3 // indirect github.com/containerd/containerd v1.7.11 // indirect
github.com/containerd/containerd v1.6.6 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/fatih/color v1.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/fatih/color v1.15.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.5.7 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/moby/sys/mount v0.3.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.6.0 // indirect github.com/moby/term v0.0.0-20200312100748-672ec06f55cd // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.2 // indirect github.com/opencontainers/runc v1.1.7 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.3.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sergi/go-diff v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
replace github.com/go-git/go-git/v5 => github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220315170230-29ec1bc1e5db

1210
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
_ "embed"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@@ -9,7 +10,8 @@ import (
"github.com/nektos/act/cmd" "github.com/nektos/act/cmd"
) )
var version = "v0.2.27-dev" // Manually bump after tagging next release //go:embed VERSION
var version string
func main() { func main() {
ctx := context.Background() ctx := context.Background()

8
pkg/artifactcache/doc.go Normal file
View File

@@ -0,0 +1,8 @@
// Package artifactcache provides a cache handler for the runner.
//
// Inspired by https://github.com/sp-ricard-valverde/github-act-cache-server
//
// TODO: Authorization
// TODO: Restrictions for accessing a cache, see https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache
// TODO: Force deleting cache entries, see https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
package artifactcache

View File

@@ -0,0 +1,530 @@
package artifactcache
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus"
"github.com/timshannon/bolthold"
"go.etcd.io/bbolt"
"github.com/nektos/act/pkg/common"
)
const (
urlBase = "/_apis/artifactcache"
)
type Handler struct {
dir string
storage *Storage
router *httprouter.Router
listener net.Listener
server *http.Server
logger logrus.FieldLogger
gcing int32 // TODO: use atomic.Bool when we can use Go 1.19
gcAt time.Time
outboundIP string
}
func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger) (*Handler, error) {
h := &Handler{}
if logger == nil {
discard := logrus.New()
discard.Out = io.Discard
logger = discard
}
logger = logger.WithField("module", "artifactcache")
h.logger = logger
if dir == "" {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
dir = filepath.Join(home, ".cache", "actcache")
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return nil, err
}
h.dir = dir
storage, err := NewStorage(filepath.Join(dir, "cache"))
if err != nil {
return nil, err
}
h.storage = storage
if outboundIP != "" {
h.outboundIP = outboundIP
} else if ip := common.GetOutboundIP(); ip == nil {
return nil, fmt.Errorf("unable to determine outbound IP address")
} else {
h.outboundIP = ip.String()
}
router := httprouter.New()
router.GET(urlBase+"/cache", h.middleware(h.find))
router.POST(urlBase+"/caches", h.middleware(h.reserve))
router.PATCH(urlBase+"/caches/:id", h.middleware(h.upload))
router.POST(urlBase+"/caches/:id", h.middleware(h.commit))
router.GET(urlBase+"/artifacts/:id", h.middleware(h.get))
router.POST(urlBase+"/clean", h.middleware(h.clean))
h.router = router
h.gcCache()
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) // listen on all interfaces
if err != nil {
return nil, err
}
server := &http.Server{
ReadHeaderTimeout: 2 * time.Second,
Handler: router,
}
go func() {
if err := server.Serve(listener); err != nil && errors.Is(err, net.ErrClosed) {
logger.Errorf("http serve: %v", err)
}
}()
h.listener = listener
h.server = server
return h, nil
}
func (h *Handler) ExternalURL() string {
// TODO: make the external url configurable if necessary
return fmt.Sprintf("http://%s:%d",
h.outboundIP,
h.listener.Addr().(*net.TCPAddr).Port)
}
func (h *Handler) Close() error {
if h == nil {
return nil
}
var retErr error
if h.server != nil {
err := h.server.Close()
if err != nil {
retErr = err
}
h.server = nil
}
if h.listener != nil {
err := h.listener.Close()
if errors.Is(err, net.ErrClosed) {
err = nil
}
if err != nil {
retErr = err
}
h.listener = nil
}
return retErr
}
func (h *Handler) openDB() (*bolthold.Store, error) {
return bolthold.Open(filepath.Join(h.dir, "bolt.db"), 0o644, &bolthold.Options{
Encoder: json.Marshal,
Decoder: json.Unmarshal,
Options: &bbolt.Options{
Timeout: 5 * time.Second,
NoGrowSync: bbolt.DefaultOptions.NoGrowSync,
FreelistType: bbolt.DefaultOptions.FreelistType,
},
})
}
// GET /_apis/artifactcache/cache
func (h *Handler) find(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
keys := strings.Split(r.URL.Query().Get("keys"), ",")
// cache keys are case insensitive
for i, key := range keys {
keys[i] = strings.ToLower(key)
}
version := r.URL.Query().Get("version")
db, err := h.openDB()
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
defer db.Close()
cache, err := h.findCache(db, keys, version)
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
if cache == nil {
h.responseJSON(w, r, 204)
return
}
if ok, err := h.storage.Exist(cache.ID); err != nil {
h.responseJSON(w, r, 500, err)
return
} else if !ok {
_ = db.Delete(cache.ID, cache)
h.responseJSON(w, r, 204)
return
}
h.responseJSON(w, r, 200, map[string]any{
"result": "hit",
"archiveLocation": fmt.Sprintf("%s%s/artifacts/%d", h.ExternalURL(), urlBase, cache.ID),
"cacheKey": cache.Key,
})
}
// POST /_apis/artifactcache/caches
func (h *Handler) reserve(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
api := &Request{}
if err := json.NewDecoder(r.Body).Decode(api); err != nil {
h.responseJSON(w, r, 400, err)
return
}
// cache keys are case insensitive
api.Key = strings.ToLower(api.Key)
cache := api.ToCache()
cache.FillKeyVersionHash()
db, err := h.openDB()
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
defer db.Close()
if err := db.FindOne(cache, bolthold.Where("KeyVersionHash").Eq(cache.KeyVersionHash)); err != nil {
if !errors.Is(err, bolthold.ErrNotFound) {
h.responseJSON(w, r, 500, err)
return
}
} else {
h.responseJSON(w, r, 400, fmt.Errorf("already exist"))
return
}
now := time.Now().Unix()
cache.CreatedAt = now
cache.UsedAt = now
if err := db.Insert(bolthold.NextSequence(), cache); err != nil {
h.responseJSON(w, r, 500, err)
return
}
// write back id to db
if err := db.Update(cache.ID, cache); err != nil {
h.responseJSON(w, r, 500, err)
return
}
h.responseJSON(w, r, 200, map[string]any{
"cacheId": cache.ID,
})
}
// PATCH /_apis/artifactcache/caches/:id
func (h *Handler) upload(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
if err != nil {
h.responseJSON(w, r, 400, err)
return
}
cache := &Cache{}
db, err := h.openDB()
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
defer db.Close()
if err := db.Get(id, cache); err != nil {
if errors.Is(err, bolthold.ErrNotFound) {
h.responseJSON(w, r, 400, fmt.Errorf("cache %d: not reserved", id))
return
}
h.responseJSON(w, r, 500, err)
return
}
if cache.Complete {
h.responseJSON(w, r, 400, fmt.Errorf("cache %v %q: already complete", cache.ID, cache.Key))
return
}
db.Close()
start, _, err := parseContentRange(r.Header.Get("Content-Range"))
if err != nil {
h.responseJSON(w, r, 400, err)
return
}
if err := h.storage.Write(cache.ID, start, r.Body); err != nil {
h.responseJSON(w, r, 500, err)
}
h.useCache(id)
h.responseJSON(w, r, 200)
}
// POST /_apis/artifactcache/caches/:id
func (h *Handler) commit(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
if err != nil {
h.responseJSON(w, r, 400, err)
return
}
cache := &Cache{}
db, err := h.openDB()
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
defer db.Close()
if err := db.Get(id, cache); err != nil {
if errors.Is(err, bolthold.ErrNotFound) {
h.responseJSON(w, r, 400, fmt.Errorf("cache %d: not reserved", id))
return
}
h.responseJSON(w, r, 500, err)
return
}
if cache.Complete {
h.responseJSON(w, r, 400, fmt.Errorf("cache %v %q: already complete", cache.ID, cache.Key))
return
}
db.Close()
size, err := h.storage.Commit(cache.ID, cache.Size)
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
// write real size back to cache, it may be different from the current value when the request doesn't specify it.
cache.Size = size
db, err = h.openDB()
if err != nil {
h.responseJSON(w, r, 500, err)
return
}
defer db.Close()
cache.Complete = true
if err := db.Update(cache.ID, cache); err != nil {
h.responseJSON(w, r, 500, err)
return
}
h.responseJSON(w, r, 200)
}
// GET /_apis/artifactcache/artifacts/:id
func (h *Handler) get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)
if err != nil {
h.responseJSON(w, r, 400, err)
return
}
h.useCache(id)
h.storage.Serve(w, r, uint64(id))
}
// POST /_apis/artifactcache/clean
func (h *Handler) clean(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// TODO: don't support force deleting cache entries
// see: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
h.responseJSON(w, r, 200)
}
func (h *Handler) middleware(handler httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
h.logger.Debugf("%s %s", r.Method, r.RequestURI)
handler(w, r, params)
go h.gcCache()
}
}
// if not found, return (nil, nil) instead of an error.
func (h *Handler) findCache(db *bolthold.Store, keys []string, version string) (*Cache, error) {
if len(keys) == 0 {
return nil, nil
}
key := keys[0] // the first key is for exact match.
cache := &Cache{
Key: key,
Version: version,
}
cache.FillKeyVersionHash()
if err := db.FindOne(cache, bolthold.Where("KeyVersionHash").Eq(cache.KeyVersionHash)); err != nil {
if !errors.Is(err, bolthold.ErrNotFound) {
return nil, err
}
} else if cache.Complete {
return cache, nil
}
stop := fmt.Errorf("stop")
for _, prefix := range keys[1:] {
found := false
if err := db.ForEach(bolthold.Where("Key").Ge(prefix).And("Version").Eq(version).SortBy("Key"), func(v *Cache) error {
if !strings.HasPrefix(v.Key, prefix) {
return stop
}
if v.Complete {
cache = v
found = true
return stop
}
return nil
}); err != nil {
if !errors.Is(err, stop) {
return nil, err
}
}
if found {
return cache, nil
}
}
return nil, nil
}
func (h *Handler) useCache(id int64) {
db, err := h.openDB()
if err != nil {
return
}
defer db.Close()
cache := &Cache{}
if err := db.Get(id, cache); err != nil {
return
}
cache.UsedAt = time.Now().Unix()
_ = db.Update(cache.ID, cache)
}
func (h *Handler) gcCache() {
if atomic.LoadInt32(&h.gcing) != 0 {
return
}
if !atomic.CompareAndSwapInt32(&h.gcing, 0, 1) {
return
}
defer atomic.StoreInt32(&h.gcing, 0)
if time.Since(h.gcAt) < time.Hour {
h.logger.Debugf("skip gc: %v", h.gcAt.String())
return
}
h.gcAt = time.Now()
h.logger.Debugf("gc: %v", h.gcAt.String())
const (
keepUsed = 30 * 24 * time.Hour
keepUnused = 7 * 24 * time.Hour
keepTemp = 5 * time.Minute
)
db, err := h.openDB()
if err != nil {
return
}
defer db.Close()
var caches []*Cache
if err := db.Find(&caches, bolthold.Where("UsedAt").Lt(time.Now().Add(-keepTemp).Unix())); err != nil {
h.logger.Warnf("find caches: %v", err)
} else {
for _, cache := range caches {
if cache.Complete {
continue
}
h.storage.Remove(cache.ID)
if err := db.Delete(cache.ID, cache); err != nil {
h.logger.Warnf("delete cache: %v", err)
continue
}
h.logger.Infof("deleted cache: %+v", cache)
}
}
caches = caches[:0]
if err := db.Find(&caches, bolthold.Where("UsedAt").Lt(time.Now().Add(-keepUnused).Unix())); err != nil {
h.logger.Warnf("find caches: %v", err)
} else {
for _, cache := range caches {
h.storage.Remove(cache.ID)
if err := db.Delete(cache.ID, cache); err != nil {
h.logger.Warnf("delete cache: %v", err)
continue
}
h.logger.Infof("deleted cache: %+v", cache)
}
}
caches = caches[:0]
if err := db.Find(&caches, bolthold.Where("CreatedAt").Lt(time.Now().Add(-keepUsed).Unix())); err != nil {
h.logger.Warnf("find caches: %v", err)
} else {
for _, cache := range caches {
h.storage.Remove(cache.ID)
if err := db.Delete(cache.ID, cache); err != nil {
h.logger.Warnf("delete cache: %v", err)
continue
}
h.logger.Infof("deleted cache: %+v", cache)
}
}
}
func (h *Handler) responseJSON(w http.ResponseWriter, r *http.Request, code int, v ...any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
var data []byte
if len(v) == 0 || v[0] == nil {
data, _ = json.Marshal(struct{}{})
} else if err, ok := v[0].(error); ok {
h.logger.Errorf("%v %v: %v", r.Method, r.RequestURI, err)
data, _ = json.Marshal(map[string]any{
"error": err.Error(),
})
} else {
data, _ = json.Marshal(v[0])
}
w.WriteHeader(code)
_, _ = w.Write(data)
}
func parseContentRange(s string) (int64, int64, error) {
// support the format like "bytes 11-22/*" only
s, _, _ = strings.Cut(strings.TrimPrefix(s, "bytes "), "/")
s1, s2, _ := strings.Cut(s, "-")
start, err := strconv.ParseInt(s1, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("parse %q: %w", s, err)
}
stop, err := strconv.ParseInt(s2, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("parse %q: %w", s, err)
}
return start, stop, nil
}

View File

@@ -0,0 +1,471 @@
package artifactcache
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"net/http"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.etcd.io/bbolt"
)
func TestHandler(t *testing.T) {
dir := filepath.Join(t.TempDir(), "artifactcache")
handler, err := StartHandler(dir, "", 0, nil)
require.NoError(t, err)
base := fmt.Sprintf("%s%s", handler.ExternalURL(), urlBase)
defer func() {
t.Run("inpect db", func(t *testing.T) {
db, err := handler.openDB()
require.NoError(t, err)
defer db.Close()
require.NoError(t, db.Bolt().View(func(tx *bbolt.Tx) error {
return tx.Bucket([]byte("Cache")).ForEach(func(k, v []byte) error {
t.Logf("%s: %s", k, v)
return nil
})
}))
})
t.Run("close", func(t *testing.T) {
require.NoError(t, handler.Close())
assert.Nil(t, handler.server)
assert.Nil(t, handler.listener)
_, err := http.Post(fmt.Sprintf("%s/caches/%d", base, 1), "", nil)
assert.Error(t, err)
})
}()
t.Run("get not exist", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version))
require.NoError(t, err)
require.Equal(t, 204, resp.StatusCode)
})
t.Run("reserve and upload", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
content := make([]byte, 100)
_, err := rand.Read(content)
require.NoError(t, err)
uploadCacheNormally(t, base, key, version, content)
})
t.Run("clean", func(t *testing.T) {
resp, err := http.Post(fmt.Sprintf("%s/clean", base), "", nil)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
})
t.Run("reserve with bad request", func(t *testing.T) {
body := []byte(`invalid json`)
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
})
t.Run("duplicate reserve", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: 100,
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
got := struct {
CacheID uint64 `json:"cacheId"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
}
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: 100,
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
}
})
t.Run("upload with bad id", func(t *testing.T) {
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/invalid_id", base), bytes.NewReader(nil))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
})
t.Run("upload without reserve", func(t *testing.T) {
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, 1000), bytes.NewReader(nil))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
})
t.Run("upload with complete", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
var id uint64
content := make([]byte, 100)
_, err := rand.Read(content)
require.NoError(t, err)
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: 100,
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
got := struct {
CacheID uint64 `json:"cacheId"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
id = got.CacheID
}
{
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
{
resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
{
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
}
})
t.Run("upload with invalid range", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
var id uint64
content := make([]byte, 100)
_, err := rand.Read(content)
require.NoError(t, err)
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: 100,
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
got := struct {
CacheID uint64 `json:"cacheId"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
id = got.CacheID
}
{
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes xx-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
}
})
t.Run("commit with bad id", func(t *testing.T) {
{
resp, err := http.Post(fmt.Sprintf("%s/caches/invalid_id", base), "", nil)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
}
})
t.Run("commit with not exist id", func(t *testing.T) {
{
resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, 100), "", nil)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
}
})
t.Run("duplicate commit", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
var id uint64
content := make([]byte, 100)
_, err := rand.Read(content)
require.NoError(t, err)
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: 100,
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
got := struct {
CacheID uint64 `json:"cacheId"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
id = got.CacheID
}
{
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
{
resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
{
resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil)
require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode)
}
})
t.Run("commit early", func(t *testing.T) {
key := strings.ToLower(t.Name())
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
var id uint64
content := make([]byte, 100)
_, err := rand.Read(content)
require.NoError(t, err)
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: 100,
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
got := struct {
CacheID uint64 `json:"cacheId"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
id = got.CacheID
}
{
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content[:50]))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-59/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
{
resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil)
require.NoError(t, err)
assert.Equal(t, 500, resp.StatusCode)
}
})
t.Run("get with bad id", func(t *testing.T) {
resp, err := http.Get(fmt.Sprintf("%s/artifacts/invalid_id", base))
require.NoError(t, err)
require.Equal(t, 400, resp.StatusCode)
})
t.Run("get with not exist id", func(t *testing.T) {
resp, err := http.Get(fmt.Sprintf("%s/artifacts/%d", base, 100))
require.NoError(t, err)
require.Equal(t, 404, resp.StatusCode)
})
t.Run("get with not exist id", func(t *testing.T) {
resp, err := http.Get(fmt.Sprintf("%s/artifacts/%d", base, 100))
require.NoError(t, err)
require.Equal(t, 404, resp.StatusCode)
})
t.Run("get with multiple keys", func(t *testing.T) {
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
key := strings.ToLower(t.Name())
keys := [3]string{
key + "_a",
key + "_a_b",
key + "_a_b_c",
}
contents := [3][]byte{
make([]byte, 100),
make([]byte, 200),
make([]byte, 300),
}
for i := range contents {
_, err := rand.Read(contents[i])
require.NoError(t, err)
uploadCacheNormally(t, base, keys[i], version, contents[i])
}
reqKeys := strings.Join([]string{
key + "_a_b_x",
key + "_a_b",
key + "_a",
}, ",")
var archiveLocation string
{
resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version))
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
got := struct {
Result string `json:"result"`
ArchiveLocation string `json:"archiveLocation"`
CacheKey string `json:"cacheKey"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
assert.Equal(t, "hit", got.Result)
assert.Equal(t, keys[1], got.CacheKey)
archiveLocation = got.ArchiveLocation
}
{
resp, err := http.Get(archiveLocation) //nolint:gosec
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
got, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, contents[1], got)
}
})
t.Run("case insensitive", func(t *testing.T) {
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
key := strings.ToLower(t.Name())
content := make([]byte, 100)
_, err := rand.Read(content)
require.NoError(t, err)
uploadCacheNormally(t, base, key+"_ABC", version, content)
{
reqKey := key + "_aBc"
resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKey, version))
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
got := struct {
Result string `json:"result"`
ArchiveLocation string `json:"archiveLocation"`
CacheKey string `json:"cacheKey"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
assert.Equal(t, "hit", got.Result)
assert.Equal(t, key+"_abc", got.CacheKey)
}
})
}
func uploadCacheNormally(t *testing.T, base, key, version string, content []byte) {
var id uint64
{
body, err := json.Marshal(&Request{
Key: key,
Version: version,
Size: int64(len(content)),
})
require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body))
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
got := struct {
CacheID uint64 `json:"cacheId"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
id = got.CacheID
}
{
req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
{
resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
}
var archiveLocation string
{
resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version))
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
got := struct {
Result string `json:"result"`
ArchiveLocation string `json:"archiveLocation"`
CacheKey string `json:"cacheKey"`
}{}
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
assert.Equal(t, "hit", got.Result)
assert.Equal(t, strings.ToLower(key), got.CacheKey)
archiveLocation = got.ArchiveLocation
}
{
resp, err := http.Get(archiveLocation) //nolint:gosec
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
got, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, content, got)
}
}

View File

@@ -0,0 +1,44 @@
package artifactcache
import (
"crypto/sha256"
"fmt"
)
type Request struct {
Key string `json:"key" `
Version string `json:"version"`
Size int64 `json:"cacheSize"`
}
func (c *Request) ToCache() *Cache {
if c == nil {
return nil
}
ret := &Cache{
Key: c.Key,
Version: c.Version,
Size: c.Size,
}
if c.Size == 0 {
// So the request comes from old versions of actions, like `actions/cache@v2`.
// It doesn't send cache size. Set it to -1 to indicate that.
ret.Size = -1
}
return ret
}
type Cache struct {
ID uint64 `json:"id" boltholdKey:"ID"`
Key string `json:"key" boltholdIndex:"Key"`
Version string `json:"version" boltholdIndex:"Version"`
KeyVersionHash string `json:"keyVersionHash" boltholdUnique:"KeyVersionHash"`
Size int64 `json:"cacheSize"`
Complete bool `json:"complete"`
UsedAt int64 `json:"usedAt" boltholdIndex:"UsedAt"`
CreatedAt int64 `json:"createdAt" boltholdIndex:"CreatedAt"`
}
func (c *Cache) FillKeyVersionHash() {
c.KeyVersionHash = fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%s:%s", c.Key, c.Version))))
}

View File

@@ -0,0 +1,130 @@
package artifactcache
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)
type Storage struct {
rootDir string
}
func NewStorage(rootDir string) (*Storage, error) {
if err := os.MkdirAll(rootDir, 0o755); err != nil {
return nil, err
}
return &Storage{
rootDir: rootDir,
}, nil
}
func (s *Storage) Exist(id uint64) (bool, error) {
name := s.filename(id)
if _, err := os.Stat(name); os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, err
}
return true, nil
}
func (s *Storage) Write(id uint64, offset int64, reader io.Reader) error {
name := s.tempName(id, offset)
if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil {
return err
}
file, err := os.Create(name)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, reader)
return err
}
func (s *Storage) Commit(id uint64, size int64) (int64, error) {
defer func() {
_ = os.RemoveAll(s.tempDir(id))
}()
name := s.filename(id)
tempNames, err := s.tempNames(id)
if err != nil {
return 0, err
}
if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil {
return 0, err
}
file, err := os.Create(name)
if err != nil {
return 0, err
}
defer file.Close()
var written int64
for _, v := range tempNames {
f, err := os.Open(v)
if err != nil {
return 0, err
}
n, err := io.Copy(file, f)
_ = f.Close()
if err != nil {
return 0, err
}
written += n
}
// If size is less than 0, it means the size is unknown.
// We can't check the size of the file, just skip the check.
// It happens when the request comes from old versions of actions, like `actions/cache@v2`.
if size >= 0 && written != size {
_ = file.Close()
_ = os.Remove(name)
return 0, fmt.Errorf("broken file: %v != %v", written, size)
}
return written, nil
}
func (s *Storage) Serve(w http.ResponseWriter, r *http.Request, id uint64) {
name := s.filename(id)
http.ServeFile(w, r, name)
}
func (s *Storage) Remove(id uint64) {
_ = os.Remove(s.filename(id))
_ = os.RemoveAll(s.tempDir(id))
}
func (s *Storage) filename(id uint64) string {
return filepath.Join(s.rootDir, fmt.Sprintf("%02x", id%0xff), fmt.Sprint(id))
}
func (s *Storage) tempDir(id uint64) string {
return filepath.Join(s.rootDir, "tmp", fmt.Sprint(id))
}
func (s *Storage) tempName(id uint64, offset int64) string {
return filepath.Join(s.tempDir(id), fmt.Sprintf("%016x", offset))
}
func (s *Storage) tempNames(id uint64) ([]string, error) {
dir := s.tempDir(id)
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var names []string
for _, v := range files {
if !v.IsDir() {
names = append(names, filepath.Join(dir, v.Name()))
}
}
return names, nil
}

View File

@@ -0,0 +1,30 @@
# Copied from https://github.com/actions/cache#example-cache-workflow
name: Caching Primes
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: env
- uses: actions/checkout@v3
- name: Cache Primes
id: cache-primes
uses: actions/cache@v3
with:
path: prime-numbers
key: ${{ runner.os }}-primes-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-primes
${{ runner.os }}
- name: Generate Prime Numbers
if: steps.cache-primes.outputs.cache-hit != 'true'
run: cat /proc/sys/kernel/random/uuid > prime-numbers
- name: Use Prime Numbers
run: cat prime-numbers

View File

@@ -9,12 +9,12 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
) )
@@ -46,44 +46,53 @@ type ResponseMessage struct {
Message string `json:"message"` Message string `json:"message"`
} }
type MkdirFS interface { type WritableFile interface {
fs.FS io.WriteCloser
MkdirAll(path string, perm fs.FileMode) error
Open(name string) (fs.File, error)
OpenAtEnd(name string) (fs.File, error)
} }
type MkdirFsImpl struct { type WriteFS interface {
dir string OpenWritable(name string) (WritableFile, error)
fs.FS OpenAppendable(name string) (WritableFile, error)
} }
func (fsys MkdirFsImpl) MkdirAll(path string, perm fs.FileMode) error { type readWriteFSImpl struct {
return os.MkdirAll(fsys.dir+"/"+path, perm)
} }
func (fsys MkdirFsImpl) Open(name string) (fs.File, error) { func (fwfs readWriteFSImpl) Open(name string) (fs.File, error) {
return os.OpenFile(fsys.dir+"/"+name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) return os.Open(name)
} }
func (fsys MkdirFsImpl) OpenAtEnd(name string) (fs.File, error) { func (fwfs readWriteFSImpl) OpenWritable(name string) (WritableFile, error) {
file, err := os.OpenFile(fsys.dir+"/"+name, os.O_CREATE|os.O_RDWR, 0644) if err := os.MkdirAll(filepath.Dir(name), os.ModePerm); err != nil {
return nil, err
}
return os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644)
}
func (fwfs readWriteFSImpl) OpenAppendable(name string) (WritableFile, error) {
if err := os.MkdirAll(filepath.Dir(name), os.ModePerm); err != nil {
return nil, err
}
file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0o644)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = file.Seek(0, os.SEEK_END) _, err = file.Seek(0, io.SeekEnd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return file, nil return file, nil
} }
var gzipExtension = ".gz__" var gzipExtension = ".gz__"
func uploads(router *httprouter.Router, fsys MkdirFS) { func safeResolve(baseDir string, relPath string) string {
return filepath.Join(baseDir, filepath.Clean(filepath.Join(string(os.PathSeparator), relPath)))
}
func uploads(router *httprouter.Router, baseDir string, fsys WriteFS) {
router.POST("/_apis/pipelines/workflows/:runId/artifacts", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { router.POST("/_apis/pipelines/workflows/:runId/artifacts", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
runID := params.ByName("runId") runID := params.ByName("runId")
@@ -108,19 +117,15 @@ func uploads(router *httprouter.Router, fsys MkdirFS) {
itemPath += gzipExtension itemPath += gzipExtension
} }
filePath := fmt.Sprintf("%s/%s", runID, itemPath) safeRunPath := safeResolve(baseDir, runID)
safePath := safeResolve(safeRunPath, itemPath)
err := fsys.MkdirAll(path.Dir(filePath), os.ModePerm) file, err := func() (WritableFile, error) {
if err != nil {
panic(err)
}
file, err := func() (fs.File, error) {
contentRange := req.Header.Get("Content-Range") contentRange := req.Header.Get("Content-Range")
if contentRange != "" && !strings.HasPrefix(contentRange, "bytes 0-") { if contentRange != "" && !strings.HasPrefix(contentRange, "bytes 0-") {
return fsys.OpenAtEnd(filePath) return fsys.OpenAppendable(safePath)
} }
return fsys.Open(filePath) return fsys.OpenWritable(safePath)
}() }()
if err != nil { if err != nil {
@@ -170,11 +175,13 @@ func uploads(router *httprouter.Router, fsys MkdirFS) {
}) })
} }
func downloads(router *httprouter.Router, fsys fs.FS) { func downloads(router *httprouter.Router, baseDir string, fsys fs.FS) {
router.GET("/_apis/pipelines/workflows/:runId/artifacts", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { router.GET("/_apis/pipelines/workflows/:runId/artifacts", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
runID := params.ByName("runId") runID := params.ByName("runId")
entries, err := fs.ReadDir(fsys, runID) safePath := safeResolve(baseDir, runID)
entries, err := fs.ReadDir(fsys, safePath)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -204,21 +211,25 @@ func downloads(router *httprouter.Router, fsys fs.FS) {
router.GET("/download/:container", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { router.GET("/download/:container", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
container := params.ByName("container") container := params.ByName("container")
itemPath := req.URL.Query().Get("itemPath") itemPath := req.URL.Query().Get("itemPath")
dirPath := fmt.Sprintf("%s/%s", container, itemPath) safePath := safeResolve(baseDir, filepath.Join(container, itemPath))
var files []ContainerItem var files []ContainerItem
err := fs.WalkDir(fsys, dirPath, func(path string, entry fs.DirEntry, err error) error { err := fs.WalkDir(fsys, safePath, func(path string, entry fs.DirEntry, err error) error {
if !entry.IsDir() { if !entry.IsDir() {
rel, err := filepath.Rel(dirPath, path) rel, err := filepath.Rel(safePath, path)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// if it was upload as gzip // if it was upload as gzip
rel = strings.TrimSuffix(rel, gzipExtension) rel = strings.TrimSuffix(rel, gzipExtension)
path := filepath.Join(itemPath, rel)
rel = filepath.ToSlash(rel)
path = filepath.ToSlash(path)
files = append(files, ContainerItem{ files = append(files, ContainerItem{
Path: fmt.Sprintf("%s/%s", itemPath, rel), Path: path,
ItemType: "file", ItemType: "file",
ContentLocation: fmt.Sprintf("http://%s/artifact/%s/%s/%s", req.Host, container, itemPath, rel), ContentLocation: fmt.Sprintf("http://%s/artifact/%s/%s/%s", req.Host, container, itemPath, rel),
}) })
@@ -245,10 +256,12 @@ func downloads(router *httprouter.Router, fsys fs.FS) {
router.GET("/artifact/*path", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { router.GET("/artifact/*path", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
path := params.ByName("path")[1:] path := params.ByName("path")[1:]
file, err := fsys.Open(path) safePath := safeResolve(baseDir, path)
file, err := fsys.Open(safePath)
if err != nil { if err != nil {
// try gzip file // try gzip file
file, err = fsys.Open(path + gzipExtension) file, err = fsys.Open(safePath + gzipExtension)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -262,7 +275,7 @@ func downloads(router *httprouter.Router, fsys fs.FS) {
}) })
} }
func Serve(ctx context.Context, artifactPath string, port string) context.CancelFunc { func Serve(ctx context.Context, artifactPath string, addr string, port string) context.CancelFunc {
serverContext, cancel := context.WithCancel(ctx) serverContext, cancel := context.WithCancel(ctx)
logger := common.Logger(serverContext) logger := common.Logger(serverContext)
@@ -273,20 +286,19 @@ func Serve(ctx context.Context, artifactPath string, port string) context.Cancel
router := httprouter.New() router := httprouter.New()
logger.Debugf("Artifacts base path '%s'", artifactPath) logger.Debugf("Artifacts base path '%s'", artifactPath)
fs := os.DirFS(artifactPath) fsys := readWriteFSImpl{}
uploads(router, MkdirFsImpl{artifactPath, fs}) uploads(router, artifactPath, fsys)
downloads(router, fs) downloads(router, artifactPath, fsys)
ip := common.GetOutboundIP().String()
server := &http.Server{ server := &http.Server{
Addr: fmt.Sprintf("%s:%s", ip, port), Addr: fmt.Sprintf("%s:%s", addr, port),
ReadHeaderTimeout: 2 * time.Second, ReadHeaderTimeout: 2 * time.Second,
Handler: router, Handler: router,
} }
// run server // run server
go func() { go func() {
logger.Infof("Start server on http://%s:%s", ip, port) logger.Infof("Start server on http://%s:%s", addr, port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal(err) logger.Fatal(err)
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@@ -15,50 +14,50 @@ import (
"testing/fstest" "testing/fstest"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
) )
type MapFsImpl struct { type writableMapFile struct {
fstest.MapFS fstest.MapFile
} }
func (fsys MapFsImpl) MkdirAll(path string, perm fs.FileMode) error { func (f *writableMapFile) Write(data []byte) (int, error) {
// mocked no-op f.Data = data
return nil
}
type WritableFile struct {
fs.File
fsys fstest.MapFS
path string
}
func (file WritableFile) Write(data []byte) (int, error) {
file.fsys[file.path].Data = data
return len(data), nil return len(data), nil
} }
func (fsys MapFsImpl) Open(path string) (fs.File, error) { func (f *writableMapFile) Close() error {
var file = fstest.MapFile{ return nil
Data: []byte("content2"),
}
fsys.MapFS[path] = &file
result, err := fsys.MapFS.Open(path)
return WritableFile{result, fsys.MapFS, path}, err
} }
func (fsys MapFsImpl) OpenAtEnd(path string) (fs.File, error) { type writeMapFS struct {
var file = fstest.MapFile{ fstest.MapFS
Data: []byte("content2"), }
}
fsys.MapFS[path] = &file
result, err := fsys.MapFS.Open(path) func (fsys writeMapFS) OpenWritable(name string) (WritableFile, error) {
return WritableFile{result, fsys.MapFS, path}, err var file = &writableMapFile{
MapFile: fstest.MapFile{
Data: []byte("content2"),
},
}
fsys.MapFS[name] = &file.MapFile
return file, nil
}
func (fsys writeMapFS) OpenAppendable(name string) (WritableFile, error) {
var file = &writableMapFile{
MapFile: fstest.MapFile{
Data: []byte("content2"),
},
}
fsys.MapFS[name] = &file.MapFile
return file, nil
} }
func TestNewArtifactUploadPrepare(t *testing.T) { func TestNewArtifactUploadPrepare(t *testing.T) {
@@ -67,7 +66,7 @@ func TestNewArtifactUploadPrepare(t *testing.T) {
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, MapFsImpl{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("POST", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil) req, _ := http.NewRequest("POST", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -93,7 +92,7 @@ func TestArtifactUploadBlob(t *testing.T) {
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, MapFsImpl{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content")) req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content"))
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -111,7 +110,7 @@ func TestArtifactUploadBlob(t *testing.T) {
} }
assert.Equal("success", response.Message) assert.Equal("success", response.Message)
assert.Equal("content", string(memfs["1/some/file"].Data)) assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data))
} }
func TestFinalizeArtifactUpload(t *testing.T) { func TestFinalizeArtifactUpload(t *testing.T) {
@@ -120,7 +119,7 @@ func TestFinalizeArtifactUpload(t *testing.T) {
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, MapFsImpl{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("PATCH", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil) req, _ := http.NewRequest("PATCH", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -144,13 +143,13 @@ func TestListArtifacts(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ var memfs = fstest.MapFS(map[string]*fstest.MapFile{
"1/file.txt": { "artifact/server/path/1/file.txt": {
Data: []byte(""), Data: []byte(""),
}, },
}) })
router := httprouter.New() router := httprouter.New()
downloads(router, memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil) req, _ := http.NewRequest("GET", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -176,13 +175,13 @@ func TestListArtifactContainer(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ var memfs = fstest.MapFS(map[string]*fstest.MapFile{
"1/some/file": { "artifact/server/path/1/some/file": {
Data: []byte(""), Data: []byte(""),
}, },
}) })
router := httprouter.New() router := httprouter.New()
downloads(router, memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/download/1?itemPath=some/file", nil) req, _ := http.NewRequest("GET", "http://localhost/download/1?itemPath=some/file", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -200,7 +199,7 @@ func TestListArtifactContainer(t *testing.T) {
} }
assert.Equal(1, len(response.Value)) assert.Equal(1, len(response.Value))
assert.Equal("some/file/.", response.Value[0].Path) assert.Equal("some/file", response.Value[0].Path)
assert.Equal("file", response.Value[0].ItemType) assert.Equal("file", response.Value[0].ItemType)
assert.Equal("http://localhost/artifact/1/some/file/.", response.Value[0].ContentLocation) assert.Equal("http://localhost/artifact/1/some/file/.", response.Value[0].ContentLocation)
} }
@@ -209,13 +208,13 @@ func TestDownloadArtifactFile(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ var memfs = fstest.MapFS(map[string]*fstest.MapFile{
"1/some/file": { "artifact/server/path/1/some/file": {
Data: []byte("content"), Data: []byte("content"),
}, },
}) })
router := httprouter.New() router := httprouter.New()
downloads(router, memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/artifact/1/some/file", nil) req, _ := http.NewRequest("GET", "http://localhost/artifact/1/some/file", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -240,8 +239,11 @@ type TestJobFileInfo struct {
containerArchitecture string containerArchitecture string
} }
var aritfactsPath = path.Join(os.TempDir(), "test-artifacts") var (
var artifactsPort = "12345" artifactsPath = path.Join(os.TempDir(), "test-artifacts")
artifactsAddr = "127.0.0.1"
artifactsPort = "12345"
)
func TestArtifactFlow(t *testing.T) { func TestArtifactFlow(t *testing.T) {
if testing.Short() { if testing.Short() {
@@ -250,15 +252,16 @@ func TestArtifactFlow(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cancel := Serve(ctx, aritfactsPath, artifactsPort) cancel := Serve(ctx, artifactsPath, artifactsAddr, artifactsPort)
defer cancel() defer cancel()
platforms := map[string]string{ platforms := map[string]string{
"ubuntu-latest": "node:16-buster-slim", "ubuntu-latest": "node:16-buster", // Don't use node:16-buster-slim because it doesn't have curl command, which is used in the tests
} }
tables := []TestJobFileInfo{ tables := []TestJobFileInfo{
{"testdata", "upload-and-download", "push", "", platforms, ""}, {"testdata", "upload-and-download", "push", "", platforms, ""},
{"testdata", "GHSL-2023-004", "push", "", platforms, ""},
} }
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
@@ -271,7 +274,7 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
t.Run(tjfi.workflowPath, func(t *testing.T) { t.Run(tjfi.workflowPath, func(t *testing.T) {
fmt.Printf("::group::%s\n", tjfi.workflowPath) fmt.Printf("::group::%s\n", tjfi.workflowPath)
if err := os.RemoveAll(aritfactsPath); err != nil { if err := os.RemoveAll(artifactsPath); err != nil {
panic(err) panic(err)
} }
@@ -286,7 +289,8 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
ReuseContainers: false, ReuseContainers: false,
ContainerArchitecture: tjfi.containerArchitecture, ContainerArchitecture: tjfi.containerArchitecture,
GitHubInstance: "github.com", GitHubInstance: "github.com",
ArtifactServerPath: aritfactsPath, ArtifactServerPath: artifactsPath,
ArtifactServerAddr: artifactsAddr,
ArtifactServerPort: artifactsPort, ArtifactServerPort: artifactsPort,
} }
@@ -296,15 +300,96 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true) planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true)
assert.Nil(t, err, fullWorkflowPath) assert.Nil(t, err, fullWorkflowPath)
plan := planner.PlanEvent(tjfi.eventName) plan, err := planner.PlanEvent(tjfi.eventName)
if err == nil {
err = runner.NewPlanExecutor(plan)(ctx) err = runner.NewPlanExecutor(plan)(ctx)
if tjfi.errorMessage == "" { if tjfi.errorMessage == "" {
assert.Nil(t, err, fullWorkflowPath) assert.Nil(t, err, fullWorkflowPath)
} else {
assert.Error(t, err, tjfi.errorMessage)
}
} else { } else {
assert.Error(t, err, tjfi.errorMessage) assert.Nil(t, plan)
} }
fmt.Println("::endgroup::") fmt.Println("::endgroup::")
}) })
} }
func TestMkdirFsImplSafeResolve(t *testing.T) {
assert := assert.New(t)
baseDir := "/foo/bar"
tests := map[string]struct {
input string
want string
}{
"simple": {input: "baz", want: "/foo/bar/baz"},
"nested": {input: "baz/blue", want: "/foo/bar/baz/blue"},
"dots in middle": {input: "baz/../../blue", want: "/foo/bar/blue"},
"leading dots": {input: "../../parent", want: "/foo/bar/parent"},
"root path": {input: "/root", want: "/foo/bar/root"},
"root": {input: "/", want: "/foo/bar"},
"empty": {input: "", want: "/foo/bar"},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
assert.Equal(tc.want, safeResolve(baseDir, tc.input))
})
}
}
func TestDownloadArtifactFileUnsafePath(t *testing.T) {
assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
"artifact/server/path/some/file": {
Data: []byte("content"),
},
})
router := httprouter.New()
downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/artifact/2/../../some/file", nil)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
}
data := rr.Body.Bytes()
assert.Equal("content", string(data))
}
func TestArtifactUploadBlobUnsafePath(t *testing.T) {
assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New()
uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=../../some/file", strings.NewReader("content"))
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
assert.Fail("Wrong status")
}
response := ResponseMessage{}
err := json.Unmarshal(rr.Body.Bytes(), &response)
if err != nil {
panic(err)
}
assert.Equal("success", response.Message)
assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data))
}

View File

@@ -0,0 +1,39 @@
name: "GHSL-2023-0004"
on: push
jobs:
test-artifacts:
runs-on: ubuntu-latest
steps:
- run: echo "hello world" > test.txt
- name: curl upload
run: curl --silent --show-error --fail ${ACTIONS_RUNTIME_URL}upload/1?itemPath=../../my-artifact/secret.txt --upload-file test.txt
- uses: actions/download-artifact@v2
with:
name: my-artifact
path: test-artifacts
- name: 'Verify Artifact #1'
run: |
file="test-artifacts/secret.txt"
if [ ! -f $file ] ; then
echo "Expected file does not exist"
exit 1
fi
if [ "$(cat $file)" != "hello world" ] ; then
echo "File contents of downloaded artifact are incorrect"
exit 1
fi
- name: Verify download should work by clean extra dots
run: curl --silent --show-error --fail --path-as-is -o out.txt ${ACTIONS_RUNTIME_URL}artifact/1/../../../1/my-artifact/secret.txt
- name: 'Verify download content'
run: |
file="out.txt"
if [ ! -f $file ] ; then
echo "Expected file does not exist"
exit 1
fi
if [ "$(cat $file)" != "hello world" ] ; then
echo "File contents of downloaded artifact are incorrect"
exit 1
fi

View File

@@ -3,6 +3,8 @@ package common
import ( import (
"context" "context"
"fmt" "fmt"
log "github.com/sirupsen/logrus"
) )
// Warning that implements `error` but safe to ignore // Warning that implements `error` but safe to ignore
@@ -94,6 +96,11 @@ func NewParallelExecutor(parallel int, executors ...Executor) Executor {
work := make(chan Executor, len(executors)) work := make(chan Executor, len(executors))
errs := make(chan error, len(executors)) errs := make(chan error, len(executors))
if 1 > parallel {
log.Infof("Parallel tasks (%d) below minimum, setting to 1", parallel)
parallel = 1
}
for i := 0; i < parallel; i++ { for i := 0; i < parallel; i++ {
go func(work <-chan Executor, errs chan<- error) { go func(work <-chan Executor, errs chan<- error) {
for executor := range work { for executor := range work {

View File

@@ -100,6 +100,17 @@ func TestNewParallelExecutor(t *testing.T) {
assert.Equal(3, count, "should run all 3 executors") assert.Equal(3, count, "should run all 3 executors")
assert.Equal(2, maxCount, "should run at most 2 executors in parallel") assert.Equal(2, maxCount, "should run at most 2 executors in parallel")
assert.Nil(err) assert.Nil(err)
// Reset to test running the executor with 0 parallelism
count = 0
activeCount = 0
maxCount = 0
errSingle := NewParallelExecutor(0, emptyWorkflow, emptyWorkflow, emptyWorkflow)(ctx)
assert.Equal(3, count, "should run all 3 executors")
assert.Equal(1, maxCount, "should run at most 1 executors in parallel")
assert.Nil(errSingle)
} }
func TestNewParallelExecutorFailed(t *testing.T) { func TestNewParallelExecutorFailed(t *testing.T) {

View File

@@ -7,20 +7,19 @@ import (
"io" "io"
"os" "os"
"path" "path"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"github.com/nektos/act/pkg/common"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer"
"github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-ini/ini"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common"
) )
var ( var (
@@ -55,41 +54,40 @@ func (e *Error) Commit() string {
// FindGitRevision get the current git revision // FindGitRevision get the current git revision
func FindGitRevision(ctx context.Context, file string) (shortSha string, sha string, err error) { func FindGitRevision(ctx context.Context, file string) (shortSha string, sha string, err error) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
gitDir, err := findGitDirectory(file)
gitDir, err := git.PlainOpenWithOptions(
file,
&git.PlainOpenOptions{
DetectDotGit: true,
EnableDotGitCommonDir: true,
},
)
if err != nil {
logger.WithError(err).Error("path", file, "not located inside a git repository")
return "", "", err
}
head, err := gitDir.Reference(plumbing.HEAD, true)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
bts, err := os.ReadFile(filepath.Join(gitDir, "HEAD")) if head.Hash().IsZero() {
if err != nil { return "", "", fmt.Errorf("HEAD sha1 could not be resolved")
return "", "", err
} }
var ref = strings.TrimSpace(strings.TrimPrefix(string(bts), "ref:")) hash := head.Hash().String()
var refBuf []byte
if strings.HasPrefix(ref, "refs/") {
// load commitid ref
refBuf, err = os.ReadFile(filepath.Join(gitDir, ref))
if err != nil {
return "", "", err
}
} else {
refBuf = []byte(ref)
}
logger.Debugf("Found revision: %s", refBuf) logger.Debugf("Found revision: %s", hash)
return string(refBuf[:7]), strings.TrimSpace(string(refBuf)), nil return hash[:7], strings.TrimSpace(hash), nil
} }
// FindGitRef get the current git ref // FindGitRef get the current git ref
func FindGitRef(ctx context.Context, file string) (string, error) { func FindGitRef(ctx context.Context, file string) (string, error) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
gitDir, err := findGitDirectory(file)
if err != nil {
return "", err
}
logger.Debugf("Loading revision from git directory '%s'", gitDir)
logger.Debugf("Loading revision from git directory")
_, ref, err := FindGitRevision(ctx, file) _, ref, err := FindGitRevision(ctx, file)
if err != nil { if err != nil {
return "", err return "", err
@@ -100,28 +98,58 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
// Prefer the git library to iterate over the references and find a matching tag or branch. // Prefer the git library to iterate over the references and find a matching tag or branch.
var refTag = "" var refTag = ""
var refBranch = "" var refBranch = ""
r, err := git.PlainOpen(filepath.Join(gitDir, "..")) repo, err := git.PlainOpenWithOptions(
if err == nil { file,
iter, err := r.References() &git.PlainOpenOptions{
if err == nil { DetectDotGit: true,
for { EnableDotGitCommonDir: true,
r, err := iter.Next() },
if r == nil || err != nil { )
break
} if err != nil {
// logger.Debugf("Reference: name=%s sha=%s", r.Name().String(), r.Hash().String()) return "", err
if r.Hash().String() == ref {
if r.Name().IsTag() {
refTag = r.Name().String()
}
if r.Name().IsBranch() {
refBranch = r.Name().String()
}
}
}
iter.Close()
}
} }
iter, err := repo.References()
if err != nil {
return "", err
}
// find the reference that matches the revision's has
err = iter.ForEach(func(r *plumbing.Reference) error {
/* tags and branches will have the same hash
* when a user checks out a tag, it is not mentioned explicitly
* in the go-git package, we must identify the revision
* then check if any tag matches that revision,
* if so then we checked out a tag
* else we look for branches and if matches,
* it means we checked out a branch
*
* If a branches matches first we must continue and check all tags (all references)
* in case we match with a tag later in the interation
*/
if r.Hash().String() == ref {
if r.Name().IsTag() {
refTag = r.Name().String()
}
if r.Name().IsBranch() {
refBranch = r.Name().String()
}
}
// we found what we where looking for
if refTag != "" && refBranch != "" {
return storer.ErrStop
}
return nil
})
if err != nil {
return "", err
}
// order matters here see above comment.
if refTag != "" { if refTag != "" {
return refTag, nil return refTag, nil
} }
@@ -129,39 +157,7 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
return refBranch, nil return refBranch, nil
} }
// If the above doesn't work, fall back to the old way return "", fmt.Errorf("failed to identify reference (tag/branch) for the checked-out revision '%s'", ref)
// try tags first
tag, err := findGitPrettyRef(ctx, ref, gitDir, "refs/tags")
if err != nil || tag != "" {
return tag, err
}
// and then branches
return findGitPrettyRef(ctx, ref, gitDir, "refs/heads")
}
func findGitPrettyRef(ctx context.Context, head, root, sub string) (string, error) {
var name string
var err = filepath.Walk(filepath.Join(root, sub), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if name != "" || info.IsDir() {
return nil
}
var bts []byte
if bts, err = os.ReadFile(path); err != nil {
return err
}
var pointsTo = strings.TrimSpace(string(bts))
if head == pointsTo {
// On Windows paths are separated with backslash character so they should be replaced to provide proper git refs format
name = strings.TrimPrefix(strings.ReplaceAll(strings.Replace(path, root, "", 1), `\`, `/`), "/")
common.Logger(ctx).Debugf("HEAD matches %s", name)
}
return nil
})
return name, err
} }
// FindGithubRepo get the repo // FindGithubRepo get the repo
@@ -178,27 +174,28 @@ func FindGithubRepo(ctx context.Context, file, githubInstance, remoteName string
return slug, err return slug, err
} }
func findGitRemoteURL(ctx context.Context, file, remoteName string) (string, error) { func findGitRemoteURL(_ context.Context, file, remoteName string) (string, error) {
gitDir, err := findGitDirectory(file) repo, err := git.PlainOpenWithOptions(
file,
&git.PlainOpenOptions{
DetectDotGit: true,
EnableDotGitCommonDir: true,
},
)
if err != nil { if err != nil {
return "", err return "", err
} }
common.Logger(ctx).Debugf("Loading slug from git directory '%s'", gitDir)
gitconfig, err := ini.InsensitiveLoad(fmt.Sprintf("%s/config", gitDir)) remote, err := repo.Remote(remoteName)
if err != nil { if err != nil {
return "", err return "", err
} }
remote, err := gitconfig.GetSection(fmt.Sprintf(`remote "%s"`, remoteName))
if err != nil { if len(remote.Config().URLs) < 1 {
return "", err return "", fmt.Errorf("remote '%s' exists but has no URL", remoteName)
} }
urlKey, err := remote.GetKey("url")
if err != nil { return remote.Config().URLs[0], nil
return "", err
}
url := urlKey.String()
return url, nil
} }
func findGitSlug(url string, githubInstance string) (string, string, error) { func findGitSlug(url string, githubInstance string) (string, string, error) {
@@ -222,35 +219,6 @@ func findGitSlug(url string, githubInstance string) (string, string, error) {
return "", url, nil return "", url, nil
} }
func findGitDirectory(fromFile string) (string, error) {
absPath, err := filepath.Abs(fromFile)
if err != nil {
return "", err
}
fi, err := os.Stat(absPath)
if err != nil {
return "", err
}
var dir string
if fi.Mode().IsDir() {
dir = absPath
} else {
dir = filepath.Dir(absPath)
}
gitPath := filepath.Join(dir, ".git")
fi, err = os.Stat(gitPath)
if err == nil && fi.Mode().IsDir() {
return gitPath, nil
} else if dir == "/" || dir == "C:\\" || dir == "c:\\" {
return "", &Error{err: ErrNoRepo}
}
return findGitDirectory(filepath.Dir(dir))
}
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor // NewGitCloneExecutorInput the input for the NewGitCloneExecutor
type NewGitCloneExecutorInput struct { type NewGitCloneExecutorInput struct {
URL string URL string
@@ -292,7 +260,7 @@ func CloneIfRequired(ctx context.Context, refName plumbing.ReferenceName, input
return nil, err return nil, err
} }
if err = os.Chmod(input.Dir, 0755); err != nil { if err = os.Chmod(input.Dir, 0o755); err != nil {
return nil, err return nil, err
} }
} }

View File

@@ -82,12 +82,19 @@ func TestFindGitRemoteURL(t *testing.T) {
assert.NoError(err) assert.NoError(err)
remoteURL := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-repo-name" remoteURL := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-repo-name"
err = gitCmd("config", "-f", fmt.Sprintf("%s/.git/config", basedir), "--add", "remote.origin.url", remoteURL) err = gitCmd("-C", basedir, "remote", "add", "origin", remoteURL)
assert.NoError(err) assert.NoError(err)
u, err := findGitRemoteURL(context.Background(), basedir, "origin") u, err := findGitRemoteURL(context.Background(), basedir, "origin")
assert.NoError(err) assert.NoError(err)
assert.Equal(remoteURL, u) assert.Equal(remoteURL, u)
remoteURL = "git@github.com/AwesomeOwner/MyAwesomeRepo.git"
err = gitCmd("-C", basedir, "remote", "add", "upstream", remoteURL)
assert.NoError(err)
u, err = findGitRemoteURL(context.Background(), basedir, "upstream")
assert.NoError(err)
assert.Equal(remoteURL, u)
} }
func TestGitFindRef(t *testing.T) { func TestGitFindRef(t *testing.T) {
@@ -160,7 +167,7 @@ func TestGitFindRef(t *testing.T) {
name := name name := name
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
dir := filepath.Join(basedir, name) dir := filepath.Join(basedir, name)
require.NoError(t, os.MkdirAll(dir, 0755)) require.NoError(t, os.MkdirAll(dir, 0o755))
require.NoError(t, gitCmd("-C", dir, "init", "--initial-branch=master")) require.NoError(t, gitCmd("-C", dir, "init", "--initial-branch=master"))
require.NoError(t, cleanGitHooks(dir)) require.NoError(t, cleanGitHooks(dir))
tt.Prepare(t, dir) tt.Prepare(t, dir)

View File

@@ -25,24 +25,3 @@ func Logger(ctx context.Context) logrus.FieldLogger {
func WithLogger(ctx context.Context, logger logrus.FieldLogger) context.Context { func WithLogger(ctx context.Context, logger logrus.FieldLogger) context.Context {
return context.WithValue(ctx, loggerContextKeyVal, logger) return context.WithValue(ctx, loggerContextKeyVal, logger)
} }
type loggerHookKey string
const loggerHookKeyVal = loggerHookKey("logrus.Hook")
// LoggerHook returns the appropriate logger hook for current context
// the hook affects job logger, not global logger
func LoggerHook(ctx context.Context) logrus.Hook {
val := ctx.Value(loggerHookKeyVal)
if val != nil {
if hook, ok := val.(logrus.Hook); ok {
return hook
}
}
return nil
}
// WithLoggerHook adds a value to the context for the logger hook
func WithLoggerHook(ctx context.Context, hook logrus.Hook) context.Context {
return context.WithValue(ctx, loggerHookKeyVal, hook)
}

View File

@@ -2,20 +2,74 @@ package common
import ( import (
"net" "net"
"sort"
log "github.com/sirupsen/logrus" "strings"
) )
// https://stackoverflow.com/a/37382208 // GetOutboundIP returns an outbound IP address of this machine.
// Get preferred outbound ip of this machine // It tries to access the internet and returns the local IP address of the connection.
// If the machine cannot access the internet, it returns a preferred IP address from network interfaces.
// It returns nil if no IP address is found.
func GetOutboundIP() net.IP { func GetOutboundIP() net.IP {
// See https://stackoverflow.com/a/37382208
conn, err := net.Dial("udp", "8.8.8.8:80") conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil { if err == nil {
log.Fatal(err) defer conn.Close()
return conn.LocalAddr().(*net.UDPAddr).IP
} }
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr) // So the machine cannot access the internet. Pick an IP address from network interfaces.
if ifs, err := net.Interfaces(); err == nil {
type IP struct {
net.IP
net.Interface
}
var ips []IP
for _, i := range ifs {
if addrs, err := i.Addrs(); err == nil {
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip.IsGlobalUnicast() {
ips = append(ips, IP{ip, i})
}
}
}
}
if len(ips) > 1 {
sort.Slice(ips, func(i, j int) bool {
ifi := ips[i].Interface
ifj := ips[j].Interface
return localAddr.IP // ethernet is preferred
if vi, vj := strings.HasPrefix(ifi.Name, "e"), strings.HasPrefix(ifj.Name, "e"); vi != vj {
return vi
}
ipi := ips[i].IP
ipj := ips[j].IP
// IPv4 is preferred
if vi, vj := ipi.To4() != nil, ipj.To4() != nil; vi != vj {
return vi
}
// en0 is preferred to en1
if ifi.Name != ifj.Name {
return ifi.Name < ifj.Name
}
// fallback
return ipi.String() < ipj.String()
})
return ips[0].IP
}
}
return nil
} }

View File

@@ -0,0 +1,75 @@
package container
import (
"context"
"io"
"github.com/docker/go-connections/nat"
"github.com/nektos/act/pkg/common"
)
// NewContainerInput the input for the New function
type NewContainerInput struct {
Image string
Username string
Password string
Entrypoint []string
Cmd []string
WorkingDir string
Env []string
Binds []string
Mounts map[string]string
Name string
Stdout io.Writer
Stderr io.Writer
NetworkMode string
Privileged bool
UsernsMode string
Platform string
Options string
NetworkAliases []string
ExposedPorts nat.PortSet
PortBindings nat.PortMap
}
// FileEntry is a file to copy to a container
type FileEntry struct {
Name string
Mode int64
Body string
}
// Container for managing docker run containers
type Container interface {
Create(capAdd []string, capDrop []string) common.Executor
Copy(destPath string, files ...*FileEntry) common.Executor
CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error
CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor
GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error)
Pull(forcePull bool) common.Executor
Start(attach bool) common.Executor
Exec(command []string, env map[string]string, user, workdir string) common.Executor
UpdateFromEnv(srcPath string, env *map[string]string) common.Executor
UpdateFromImageEnv(env *map[string]string) common.Executor
Remove() common.Executor
Close() common.Executor
ReplaceLogWriter(io.Writer, io.Writer) (io.Writer, io.Writer)
}
// NewDockerBuildExecutorInput the input for the NewDockerBuildExecutor function
type NewDockerBuildExecutorInput struct {
ContextDir string
Dockerfile string
BuildContext io.Reader
ImageTag string
Platform string
}
// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function
type NewDockerPullExecutorInput struct {
Image string
ForcePull bool
Platform string
Username string
Password string
}

View File

@@ -1,3 +1,5 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (
@@ -6,16 +8,16 @@ import (
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/credentials" "github.com/docker/cli/cli/config/credentials"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types/registry"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
) )
func LoadDockerAuthConfig(ctx context.Context, image string) (types.AuthConfig, error) { func LoadDockerAuthConfig(ctx context.Context, image string) (registry.AuthConfig, error) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
config, err := config.Load(config.Dir()) config, err := config.Load(config.Dir())
if err != nil { if err != nil {
logger.Warnf("Could not load docker config: %v", err) logger.Warnf("Could not load docker config: %v", err)
return types.AuthConfig{}, err return registry.AuthConfig{}, err
} }
if !config.ContainsAuth() { if !config.ContainsAuth() {
@@ -31,8 +33,29 @@ func LoadDockerAuthConfig(ctx context.Context, image string) (types.AuthConfig,
authConfig, err := config.GetAuthConfig(hostName) authConfig, err := config.GetAuthConfig(hostName)
if err != nil { if err != nil {
logger.Warnf("Could not get auth config from docker config: %v", err) logger.Warnf("Could not get auth config from docker config: %v", err)
return types.AuthConfig{}, err return registry.AuthConfig{}, err
} }
return types.AuthConfig(authConfig), nil return registry.AuthConfig(authConfig), nil
}
func LoadDockerAuthConfigs(ctx context.Context) map[string]registry.AuthConfig {
logger := common.Logger(ctx)
config, err := config.Load(config.Dir())
if err != nil {
logger.Warnf("Could not load docker config: %v", err)
return nil
}
if !config.ContainsAuth() {
config.CredentialsStore = credentials.DetectDefaultStore(config.CredentialsStore)
}
creds, _ := config.GetAllCredentials()
authConfigs := make(map[string]registry.AuthConfig, len(creds))
for k, v := range creds {
authConfigs[k] = registry.AuthConfig(v)
}
return authConfigs
} }

View File

@@ -1,3 +1,5 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (
@@ -8,22 +10,14 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
// github.com/docker/docker/builder/dockerignore is deprecated // github.com/docker/docker/builder/dockerignore is deprecated
"github.com/moby/buildkit/frontend/dockerfile/dockerignore" "github.com/moby/buildkit/frontend/dockerfile/dockerignore"
"github.com/moby/patternmatcher"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
) )
// NewDockerBuildExecutorInput the input for the NewDockerBuildExecutor function
type NewDockerBuildExecutorInput struct {
ContextDir string
Container Container
ImageTag string
Platform string
}
// NewDockerBuildExecutor function to create a run executor for the container // NewDockerBuildExecutor function to create a run executor for the container
func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor { func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
@@ -47,15 +41,17 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
tags := []string{input.ImageTag} tags := []string{input.ImageTag}
options := types.ImageBuildOptions{ options := types.ImageBuildOptions{
Tags: tags, Tags: tags,
Remove: true, Remove: true,
Platform: input.Platform, Platform: input.Platform,
AuthConfigs: LoadDockerAuthConfigs(ctx),
Dockerfile: input.Dockerfile,
} }
var buildContext io.ReadCloser var buildContext io.ReadCloser
if input.Container != nil { if input.BuildContext != nil {
buildContext, err = input.Container.GetContainerArchive(ctx, input.ContextDir+"/.") buildContext = io.NopCloser(input.BuildContext)
} else { } else {
buildContext, err = createBuildContext(ctx, input.ContextDir, "Dockerfile") buildContext, err = createBuildContext(ctx, input.ContextDir, input.Dockerfile)
} }
if err != nil { if err != nil {
return err return err
@@ -101,8 +97,8 @@ func createBuildContext(ctx context.Context, contextDir string, relDockerfile st
// parses the Dockerfile. Ignore errors here, as they will have been // parses the Dockerfile. Ignore errors here, as they will have been
// caught by validateContextDirectory above. // caught by validateContextDirectory above.
var includes = []string{"."} var includes = []string{"."}
keepThem1, _ := fileutils.Matches(".dockerignore", excludes) keepThem1, _ := patternmatcher.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches(relDockerfile, excludes) keepThem2, _ := patternmatcher.Matches(relDockerfile, excludes)
if keepThem1 || keepThem2 { if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", relDockerfile) includes = append(includes, ".dockerignore", relDockerfile)
} }

View File

@@ -1,3 +1,5 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
// This file is exact copy of https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts.go // This file is exact copy of https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts.go
// appended with license information. // appended with license information.
// //

View File

@@ -663,8 +663,8 @@ func TestRunFlagsParseShmSize(t *testing.T) {
func TestParseRestartPolicy(t *testing.T) { func TestParseRestartPolicy(t *testing.T) {
invalids := map[string]string{ invalids := map[string]string{
"always:2:3": "invalid restart policy format", "always:2:3": "invalid restart policy format: maximum retry count must be an integer",
"on-failure:invalid": "maximum retry count must be an integer", "on-failure:invalid": "invalid restart policy format: maximum retry count must be an integer",
} }
valids := map[string]container.RestartPolicy{ valids := map[string]container.RestartPolicy{
"": {}, "": {},

View File

@@ -1,3 +1,5 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (
@@ -5,7 +7,7 @@ import (
"fmt" "fmt"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/client"
) )
// ImageExistsLocally returns a boolean indicating if an image with the // ImageExistsLocally returns a boolean indicating if an image with the
@@ -17,33 +19,15 @@ func ImageExistsLocally(ctx context.Context, imageName string, platform string)
} }
defer cli.Close() defer cli.Close()
filters := filters.NewArgs() inspectImage, _, err := cli.ImageInspectWithRaw(ctx, imageName)
filters.Add("reference", imageName) if client.IsErrNotFound(err) {
return false, nil
imageListOptions := types.ImageListOptions{ } else if err != nil {
Filters: filters,
}
images, err := cli.ImageList(ctx, imageListOptions)
if err != nil {
return false, err return false, err
} }
if len(images) > 0 { if platform == "" || platform == "any" || fmt.Sprintf("%s/%s", inspectImage.Os, inspectImage.Architecture) == platform {
if platform == "any" || platform == "" { return true, nil
return true, nil
}
for _, v := range images {
inspectImage, _, err := cli.ImageInspectWithRaw(ctx, v.ID)
if err != nil {
return false, err
}
if fmt.Sprintf("%s/%s", inspectImage.Os, inspectImage.Architecture) == platform {
return true, nil
}
}
return false, nil
} }
return false, nil return false, nil
@@ -52,38 +36,25 @@ func ImageExistsLocally(ctx context.Context, imageName string, platform string)
// RemoveImage removes image from local store, the function is used to run different // RemoveImage removes image from local store, the function is used to run different
// container image architectures // container image architectures
func RemoveImage(ctx context.Context, imageName string, force bool, pruneChildren bool) (bool, error) { func RemoveImage(ctx context.Context, imageName string, force bool, pruneChildren bool) (bool, error) {
if exists, err := ImageExistsLocally(ctx, imageName, "any"); !exists {
return false, err
}
cli, err := GetDockerClient(ctx) cli, err := GetDockerClient(ctx)
if err != nil { if err != nil {
return false, err return false, err
} }
defer cli.Close()
filters := filters.NewArgs() inspectImage, _, err := cli.ImageInspectWithRaw(ctx, imageName)
filters.Add("reference", imageName) if client.IsErrNotFound(err) {
return false, nil
imageListOptions := types.ImageListOptions{ } else if err != nil {
Filters: filters,
}
images, err := cli.ImageList(ctx, imageListOptions)
if err != nil {
return false, err return false, err
} }
if len(images) > 0 { if _, err = cli.ImageRemove(ctx, inspectImage.ID, types.ImageRemoveOptions{
for _, v := range images { Force: force,
if _, err = cli.ImageRemove(ctx, v.ID, types.ImageRemoveOptions{ PruneChildren: pruneChildren,
Force: force, }); err != nil {
PruneChildren: pruneChildren, return false, err
}); err != nil {
return false, err
}
}
return true, nil
} }
return false, nil return true, nil
} }

View File

@@ -1,3 +1,5 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (

View File

@@ -0,0 +1,79 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container
import (
"context"
"github.com/docker/docker/api/types"
"github.com/nektos/act/pkg/common"
)
func NewDockerNetworkCreateExecutor(name string) common.Executor {
return func(ctx context.Context) error {
cli, err := GetDockerClient(ctx)
if err != nil {
return err
}
defer cli.Close()
// Only create the network if it doesn't exist
networks, err := cli.NetworkList(ctx, types.NetworkListOptions{})
if err != nil {
return err
}
common.Logger(ctx).Debugf("%v", networks)
for _, network := range networks {
if network.Name == name {
common.Logger(ctx).Debugf("Network %v exists", name)
return nil
}
}
_, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
Driver: "bridge",
Scope: "local",
})
if err != nil {
return err
}
return nil
}
}
func NewDockerNetworkRemoveExecutor(name string) common.Executor {
return func(ctx context.Context) error {
cli, err := GetDockerClient(ctx)
if err != nil {
return err
}
defer cli.Close()
// Make shure that all network of the specified name are removed
// cli.NetworkRemove refuses to remove a network if there are duplicates
networks, err := cli.NetworkList(ctx, types.NetworkListOptions{})
if err != nil {
return err
}
common.Logger(ctx).Debugf("%v", networks)
for _, network := range networks {
if network.Name == name {
result, err := cli.NetworkInspect(ctx, network.ID, types.NetworkInspectOptions{})
if err != nil {
return err
}
if len(result.Containers) == 0 {
if err = cli.NetworkRemove(ctx, network.ID); err != nil {
common.Logger(ctx).Debugf("%v", err)
}
} else {
common.Logger(ctx).Debugf("Refusing to remove network %v because it still has active endpoints", name)
}
}
}
return err
}
}

View File

@@ -1,3 +1,5 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (
@@ -5,22 +7,15 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
) )
// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function
type NewDockerPullExecutorInput struct {
Image string
ForcePull bool
Platform string
Username string
Password string
}
// NewDockerPullExecutor function to create a run executor for the container // NewDockerPullExecutor function to create a run executor for the container
func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor { func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
@@ -66,6 +61,13 @@ func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
_ = logDockerResponse(logger, reader, err != nil) _ = logDockerResponse(logger, reader, err != nil)
if err != nil { if err != nil {
if imagePullOptions.RegistryAuth != "" && strings.Contains(err.Error(), "unauthorized") {
logger.Errorf("pulling image '%v' (%s) failed with credentials %s retrying without them, please check for stale docker config files", imageRef, input.Platform, err.Error())
imagePullOptions.RegistryAuth = ""
reader, err = cli.ImagePull(ctx, imageRef, imagePullOptions)
_ = logDockerResponse(logger, reader, err != nil)
}
return err return err
} }
return nil return nil
@@ -76,12 +78,12 @@ func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput)
imagePullOptions := types.ImagePullOptions{ imagePullOptions := types.ImagePullOptions{
Platform: input.Platform, Platform: input.Platform,
} }
logger := common.Logger(ctx)
if input.Username != "" && input.Password != "" { if input.Username != "" && input.Password != "" {
logger := common.Logger(ctx)
logger.Debugf("using authentication for docker pull") logger.Debugf("using authentication for docker pull")
authConfig := types.AuthConfig{ authConfig := registry.AuthConfig{
Username: input.Username, Username: input.Username,
Password: input.Password, Password: input.Password,
} }
@@ -100,6 +102,7 @@ func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput)
if authConfig.Username == "" && authConfig.Password == "" { if authConfig.Username == "" && authConfig.Password == "" {
return imagePullOptions, nil return imagePullOptions, nil
} }
logger.Info("using DockerAuthConfig authentication for docker pull")
encodedJSON, err := json.Marshal(authConfig) encodedJSON, err := json.Marshal(authConfig)
if err != nil { if err != nil {

View File

@@ -1,8 +1,9 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (
"archive/tar" "archive/tar"
"bufio"
"bytes" "bytes"
"context" "context"
"errors" "errors"
@@ -15,76 +16,27 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/go-git/go-billy/v5/helper/polyfill" "github.com/Masterminds/semver"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/joho/godotenv"
"github.com/imdario/mergo"
"github.com/kballard/go-shellquote"
"github.com/spf13/pflag"
"github.com/docker/cli/cli/connhelper" "github.com/docker/cli/cli/connhelper"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/go-git/go-billy/v5/helper/polyfill"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/imdario/mergo"
"github.com/joho/godotenv"
"github.com/kballard/go-shellquote"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/pflag"
"github.com/Masterminds/semver"
"golang.org/x/term" "golang.org/x/term"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
) )
// NewContainerInput the input for the New function
type NewContainerInput struct {
Image string
Username string
Password string
Entrypoint []string
Cmd []string
WorkingDir string
Env []string
Binds []string
Mounts map[string]string
Name string
Stdout io.Writer
Stderr io.Writer
NetworkMode string
Privileged bool
UsernsMode string
Platform string
Options string
AutoRemove bool
}
// FileEntry is a file to copy to a container
type FileEntry struct {
Name string
Mode int64
Body string
}
// Container for managing docker run containers
type Container interface {
Create(capAdd []string, capDrop []string) common.Executor
Copy(destPath string, files ...*FileEntry) common.Executor
CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor
GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error)
Pull(forcePull bool) common.Executor
Start(attach bool) common.Executor
Exec(command []string, env map[string]string, user, workdir string) common.Executor
UpdateFromEnv(srcPath string, env *map[string]string) common.Executor
UpdateFromImageEnv(env *map[string]string) common.Executor
UpdateFromPath(env *map[string]string) common.Executor
Remove() common.Executor
Close() common.Executor
ReplaceLogWriter(io.Writer, io.Writer) (io.Writer, io.Writer)
}
// NewContainer creates a reference to a container // NewContainer creates a reference to a container
func NewContainer(input *NewContainerInput) ExecutionsEnvironment { func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
cr := new(containerReference) cr := new(containerReference)
@@ -112,7 +64,7 @@ func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) b
func (cr *containerReference) Create(capAdd []string, capDrop []string) common.Executor { func (cr *containerReference) Create(capAdd []string, capDrop []string) common.Executor {
return common. return common.
NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd). NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
Then( Then(
common.NewPipelineExecutor( common.NewPipelineExecutor(
cr.connect(), cr.connect(),
@@ -124,7 +76,7 @@ func (cr *containerReference) Create(capAdd []string, capDrop []string) common.E
func (cr *containerReference) Start(attach bool) common.Executor { func (cr *containerReference) Start(attach bool) common.Executor {
return common. return common.
NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd). NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
Then( Then(
common.NewPipelineExecutor( common.NewPipelineExecutor(
cr.connect(), cr.connect(),
@@ -190,17 +142,13 @@ func (cr *containerReference) GetContainerArchive(ctx context.Context, srcPath s
} }
func (cr *containerReference) UpdateFromEnv(srcPath string, env *map[string]string) common.Executor { func (cr *containerReference) UpdateFromEnv(srcPath string, env *map[string]string) common.Executor {
return cr.extractEnv(srcPath, env).IfNot(common.Dryrun) return parseEnvFile(cr, srcPath, env).IfNot(common.Dryrun)
} }
func (cr *containerReference) UpdateFromImageEnv(env *map[string]string) common.Executor { func (cr *containerReference) UpdateFromImageEnv(env *map[string]string) common.Executor {
return cr.extractFromImageEnv(env).IfNot(common.Dryrun) return cr.extractFromImageEnv(env).IfNot(common.Dryrun)
} }
func (cr *containerReference) UpdateFromPath(env *map[string]string) common.Executor {
return cr.extractPath(env).IfNot(common.Dryrun)
}
func (cr *containerReference) Exec(command []string, env map[string]string, user, workdir string) common.Executor { func (cr *containerReference) Exec(command []string, env map[string]string, user, workdir string) common.Executor {
return common.NewPipelineExecutor( return common.NewPipelineExecutor(
common.NewInfoExecutor("%sdocker exec cmd=[%s] user=%s workdir=%s", logPrefix, strings.Join(command, " "), user, workdir), common.NewInfoExecutor("%sdocker exec cmd=[%s] user=%s workdir=%s", logPrefix, strings.Join(command, " "), user, workdir),
@@ -239,9 +187,6 @@ type containerReference struct {
} }
func GetDockerClient(ctx context.Context) (cli client.APIClient, err error) { func GetDockerClient(ctx context.Context) (cli client.APIClient, err error) {
// TODO: this should maybe need to be a global option, not hidden in here?
// though i'm not sure how that works out when there's another Executor :D
// I really would like something that works on OSX native for eg
dockerHost := os.Getenv("DOCKER_HOST") dockerHost := os.Getenv("DOCKER_HOST")
if strings.HasPrefix(dockerHost, "ssh://") { if strings.HasPrefix(dockerHost, "ssh://") {
@@ -293,8 +238,8 @@ func RunnerArch(ctx context.Context) string {
archMapper := map[string]string{ archMapper := map[string]string{
"x86_64": "X64", "x86_64": "X64",
"386": "x86", "386": "X86",
"aarch64": "arm64", "aarch64": "ARM64",
} }
if arch, ok := archMapper[info.Architecture]; ok { if arch, ok := archMapper[info.Architecture]; ok {
return arch return arch
@@ -398,7 +343,13 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
return nil, nil, fmt.Errorf("Cannot parse container options: '%s': '%w'", input.Options, err) return nil, nil, fmt.Errorf("Cannot parse container options: '%s': '%w'", input.Options, err)
} }
containerConfig, err := parse(flags, copts, "") if len(copts.netMode.Value()) == 0 {
if err = copts.netMode.Set(cr.input.NetworkMode); err != nil {
return nil, nil, fmt.Errorf("Cannot parse networkmode=%s. This is an internal error and should not happen: '%w'", cr.input.NetworkMode, err)
}
}
containerConfig, err := parse(flags, copts, runtime.GOOS)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Cannot process container options: '%s': '%w'", input.Options, err) return nil, nil, fmt.Errorf("Cannot process container options: '%s': '%w'", input.Options, err)
} }
@@ -413,10 +364,16 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
logger.Debugf("Custom container.HostConfig from options ==> %+v", containerConfig.HostConfig) logger.Debugf("Custom container.HostConfig from options ==> %+v", containerConfig.HostConfig)
hostConfig.Binds = append(hostConfig.Binds, containerConfig.HostConfig.Binds...)
hostConfig.Mounts = append(hostConfig.Mounts, containerConfig.HostConfig.Mounts...)
binds := hostConfig.Binds
mounts := hostConfig.Mounts
err = mergo.Merge(hostConfig, containerConfig.HostConfig, mergo.WithOverride) err = mergo.Merge(hostConfig, containerConfig.HostConfig, mergo.WithOverride)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Cannot merge container.HostConfig options: '%s': '%w'", input.Options, err) return nil, nil, fmt.Errorf("Cannot merge container.HostConfig options: '%s': '%w'", input.Options, err)
} }
hostConfig.Binds = binds
hostConfig.Mounts = mounts
logger.Debugf("Merged container.HostConfig ==> %+v", hostConfig) logger.Debugf("Merged container.HostConfig ==> %+v", hostConfig)
return config, hostConfig, nil return config, hostConfig, nil
@@ -432,10 +389,11 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
input := cr.input input := cr.input
config := &container.Config{ config := &container.Config{
Image: input.Image, Image: input.Image,
WorkingDir: input.WorkingDir, WorkingDir: input.WorkingDir,
Env: input.Env, Env: input.Env,
Tty: isTerminal, ExposedPorts: input.ExposedPorts,
Tty: isTerminal,
} }
logger.Debugf("Common container.Config ==> %+v", config) logger.Debugf("Common container.Config ==> %+v", config)
@@ -471,14 +429,14 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
} }
hostConfig := &container.HostConfig{ hostConfig := &container.HostConfig{
CapAdd: capAdd, CapAdd: capAdd,
CapDrop: capDrop, CapDrop: capDrop,
Binds: input.Binds, Binds: input.Binds,
Mounts: mounts, Mounts: mounts,
NetworkMode: container.NetworkMode(input.NetworkMode), NetworkMode: container.NetworkMode(input.NetworkMode),
Privileged: input.Privileged, Privileged: input.Privileged,
UsernsMode: container.UsernsMode(input.UsernsMode), UsernsMode: container.UsernsMode(input.UsernsMode),
AutoRemove: input.AutoRemove, PortBindings: input.PortBindings,
} }
logger.Debugf("Common container.HostConfig ==> %+v", hostConfig) logger.Debugf("Common container.HostConfig ==> %+v", hostConfig)
@@ -487,7 +445,22 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
return err return err
} }
resp, err := cr.cli.ContainerCreate(ctx, config, hostConfig, nil, platSpecs, input.Name) var networkingConfig *network.NetworkingConfig
logger.Debugf("input.NetworkAliases ==> %v", input.NetworkAliases)
n := hostConfig.NetworkMode
// IsUserDefined and IsHost are broken on windows
if n.IsUserDefined() && n != "host" && len(input.NetworkAliases) > 0 {
endpointConfig := &network.EndpointSettings{
Aliases: input.NetworkAliases,
}
networkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
input.NetworkMode: endpointConfig,
},
}
}
resp, err := cr.cli.ContainerCreate(ctx, config, hostConfig, networkingConfig, platSpecs, input.Name)
if err != nil { if err != nil {
return fmt.Errorf("failed to create container: '%w'", err) return fmt.Errorf("failed to create container: '%w'", err)
} }
@@ -500,59 +473,6 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
} }
} }
var singleLineEnvPattern, multiLineEnvPattern *regexp.Regexp
func (cr *containerReference) extractEnv(srcPath string, env *map[string]string) common.Executor {
if singleLineEnvPattern == nil {
// Single line pattern matches:
// SOME_VAR=data=moredata
// SOME_VAR=datamoredata
singleLineEnvPattern = regexp.MustCompile(`^([^=]*)\=(.*)$`)
multiLineEnvPattern = regexp.MustCompile(`^([^<]+)<<([\w-]+)$`)
}
localEnv := *env
return func(ctx context.Context) error {
envTar, _, err := cr.cli.CopyFromContainer(ctx, cr.id, srcPath)
if err != nil {
return nil
}
defer envTar.Close()
reader := tar.NewReader(envTar)
_, err = reader.Next()
if err != nil && err != io.EOF {
return fmt.Errorf("failed to read tar archive: %w", err)
}
s := bufio.NewScanner(reader)
multiLineEnvKey := ""
multiLineEnvDelimiter := ""
multiLineEnvContent := ""
for s.Scan() {
line := s.Text()
if singleLineEnv := singleLineEnvPattern.FindStringSubmatch(line); singleLineEnv != nil {
localEnv[singleLineEnv[1]] = singleLineEnv[2]
}
if line == multiLineEnvDelimiter {
localEnv[multiLineEnvKey] = multiLineEnvContent
multiLineEnvKey, multiLineEnvDelimiter, multiLineEnvContent = "", "", ""
}
if multiLineEnvKey != "" && multiLineEnvDelimiter != "" {
if multiLineEnvContent != "" {
multiLineEnvContent += "\n"
}
multiLineEnvContent += line
}
if multiLineEnvStart := multiLineEnvPattern.FindStringSubmatch(line); multiLineEnvStart != nil {
multiLineEnvKey = multiLineEnvStart[1]
multiLineEnvDelimiter = multiLineEnvStart[2]
}
}
env = &localEnv
return nil
}
}
func (cr *containerReference) extractFromImageEnv(env *map[string]string) common.Executor { func (cr *containerReference) extractFromImageEnv(env *map[string]string) common.Executor {
envMap := *env envMap := *env
return func(ctx context.Context) error { return func(ctx context.Context) error {
@@ -561,11 +481,17 @@ func (cr *containerReference) extractFromImageEnv(env *map[string]string) common
inspect, _, err := cr.cli.ImageInspectWithRaw(ctx, cr.input.Image) inspect, _, err := cr.cli.ImageInspectWithRaw(ctx, cr.input.Image)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return fmt.Errorf("inspect image: %w", err)
}
if inspect.Config == nil {
return nil
} }
imageEnv, err := godotenv.Unmarshal(strings.Join(inspect.Config.Env, "\n")) imageEnv, err := godotenv.Unmarshal(strings.Join(inspect.Config.Env, "\n"))
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return fmt.Errorf("unmarshal image env: %w", err)
} }
for k, v := range imageEnv { for k, v := range imageEnv {
@@ -585,31 +511,6 @@ func (cr *containerReference) extractFromImageEnv(env *map[string]string) common
} }
} }
func (cr *containerReference) extractPath(env *map[string]string) common.Executor {
localEnv := *env
return func(ctx context.Context) error {
pathTar, _, err := cr.cli.CopyFromContainer(ctx, cr.id, localEnv["GITHUB_PATH"])
if err != nil {
return fmt.Errorf("failed to copy from container: %w", err)
}
defer pathTar.Close()
reader := tar.NewReader(pathTar)
_, err = reader.Next()
if err != nil && err != io.EOF {
return fmt.Errorf("failed to read tar archive: %w", err)
}
s := bufio.NewScanner(reader)
for s.Scan() {
line := s.Text()
localEnv["PATH"] = fmt.Sprintf("%s:%s", line, localEnv["PATH"])
}
env = &localEnv
return nil
}
}
func (cr *containerReference) exec(cmd []string, env map[string]string, user, workdir string) common.Executor { func (cr *containerReference) exec(cmd []string, env map[string]string, user, workdir string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
@@ -706,7 +607,7 @@ func (cr *containerReference) tryReadID(opt string, cbk func(id int)) common.Exe
} }
exp := regexp.MustCompile(`\d+\n`) exp := regexp.MustCompile(`\d+\n`)
found := exp.FindString(sid) found := exp.FindString(sid)
id, err := strconv.ParseInt(found[:len(found)-1], 10, 32) id, err := strconv.ParseInt(strings.TrimSpace(found), 10, 32)
if err != nil { if err != nil {
return nil return nil
} }
@@ -724,7 +625,7 @@ func (cr *containerReference) tryReadGID() common.Executor {
return cr.tryReadID("-g", func(id int) { cr.GID = id }) return cr.tryReadID("-g", func(id int) { cr.GID = id })
} }
func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, idResp types.IDResponse, user string, workdir string) error { func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, _ types.IDResponse, _ string, _ string) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
cmdResponse := make(chan error) cmdResponse := make(chan error)
@@ -769,6 +670,14 @@ func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal boo
} }
} }
func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
err := cr.cli.CopyToContainer(ctx, cr.id, destPath, tarStream, types.CopyToContainerOptions{})
if err != nil {
return fmt.Errorf("failed to copy content to container: %w", err)
}
return nil
}
func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgnore bool) common.Executor { func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgnore bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)

View File

@@ -19,6 +19,7 @@ func TestDocker(t *testing.T) {
ctx := context.Background() ctx := context.Background()
client, err := GetDockerClient(ctx) client, err := GetDockerClient(ctx)
assert.NoError(t, err) assert.NoError(t, err)
defer client.Close()
dockerBuild := NewDockerBuildExecutor(NewDockerBuildExecutorInput{ dockerBuild := NewDockerBuildExecutor(NewDockerBuildExecutorInput{
ContextDir: "testdata", ContextDir: "testdata",
@@ -78,7 +79,7 @@ type endlessReader struct {
io.Reader io.Reader
} }
func (r endlessReader) Read(p []byte) (n int, err error) { func (r endlessReader) Read(_ []byte) (n int, err error) {
return 1, nil return 1, nil
} }

View File

@@ -0,0 +1,69 @@
//go:build WITHOUT_DOCKER || !(linux || darwin || windows)
package container
import (
"context"
"runtime"
"github.com/docker/docker/api/types"
"github.com/nektos/act/pkg/common"
"github.com/pkg/errors"
)
// ImageExistsLocally returns a boolean indicating if an image with the
// requested name, tag and architecture exists in the local docker image store
func ImageExistsLocally(ctx context.Context, imageName string, platform string) (bool, error) {
return false, errors.New("Unsupported Operation")
}
// RemoveImage removes image from local store, the function is used to run different
// container image architectures
func RemoveImage(ctx context.Context, imageName string, force bool, pruneChildren bool) (bool, error) {
return false, errors.New("Unsupported Operation")
}
// NewDockerBuildExecutor function to create a run executor for the container
func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
return func(ctx context.Context) error {
return errors.New("Unsupported Operation")
}
}
// NewDockerPullExecutor function to create a run executor for the container
func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
return func(ctx context.Context) error {
return errors.New("Unsupported Operation")
}
}
// NewContainer creates a reference to a container
func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
return nil
}
func RunnerArch(ctx context.Context) string {
return runtime.GOOS
}
func GetHostInfo(ctx context.Context) (info types.Info, err error) {
return types.Info{}, nil
}
func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor {
return func(ctx context.Context) error {
return nil
}
}
func NewDockerNetworkCreateExecutor(name string) common.Executor {
return func(ctx context.Context) error {
return nil
}
}
func NewDockerNetworkRemoveExecutor(name string) common.Executor {
return func(ctx context.Context) error {
return nil
}
}

View File

@@ -1,13 +1,16 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))
package container package container
import ( import (
"context" "context"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
) )
func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor { func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
cli, err := GetDockerClient(ctx) cli, err := GetDockerClient(ctx)
if err != nil { if err != nil {
@@ -15,14 +18,14 @@ func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor {
} }
defer cli.Close() defer cli.Close()
list, err := cli.VolumeList(ctx, filters.NewArgs()) list, err := cli.VolumeList(ctx, volume.ListOptions{Filters: filters.NewArgs()})
if err != nil { if err != nil {
return err return err
} }
for _, vol := range list.Volumes { for _, vol := range list.Volumes {
if vol.Name == volume { if vol.Name == volumeName {
return removeExecutor(volume, force)(ctx) return removeExecutor(volumeName, force)(ctx)
} }
} }

View File

@@ -10,4 +10,6 @@ type ExecutionsEnvironment interface {
DefaultPathVariable() string DefaultPathVariable() string
JoinPathVariable(...string) string JoinPathVariable(...string) string
GetRunnerContext(ctx context.Context) map[string]interface{} GetRunnerContext(ctx context.Context) map[string]interface{}
// On windows PATH and Path are the same key
IsEnvironmentCaseInsensitive() bool
} }

View File

@@ -65,7 +65,7 @@ type copyCollector struct {
func (cc *copyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error { func (cc *copyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string, f io.Reader) error {
fdestpath := filepath.Join(cc.DstDir, fpath) fdestpath := filepath.Join(cc.DstDir, fpath)
if err := os.MkdirAll(filepath.Dir(fdestpath), 0777); err != nil { if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
return err return err
} }
if f == nil { if f == nil {

View File

@@ -76,7 +76,7 @@ func (mfs *memoryFs) Readlink(path string) (string, error) {
func TestIgnoredTrackedfile(t *testing.T) { func TestIgnoredTrackedfile(t *testing.T) {
fs := memfs.New() fs := memfs.New()
_ = fs.MkdirAll("mygitrepo/.git", 0777) _ = fs.MkdirAll("mygitrepo/.git", 0o777)
dotgit, _ := fs.Chroot("mygitrepo/.git") dotgit, _ := fs.Chroot("mygitrepo/.git")
worktree, _ := fs.Chroot("mygitrepo") worktree, _ := fs.Chroot("mygitrepo")
repo, _ := git.Init(filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()), worktree) repo, _ := git.Init(filesystem.NewStorage(dotgit, cache.NewObjectLRUDefault()), worktree)

View File

@@ -2,9 +2,9 @@ package container
import ( import (
"archive/tar" "archive/tar"
"bufio"
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -15,14 +15,13 @@ import (
"strings" "strings"
"time" "time"
"errors"
"github.com/go-git/go-billy/v5/helper/polyfill" "github.com/go-git/go-billy/v5/helper/polyfill"
"github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/go-git/go-git/v5/plumbing/format/gitignore"
"golang.org/x/term"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/lookpath" "github.com/nektos/act/pkg/lookpath"
"golang.org/x/term"
) )
type HostEnvironment struct { type HostEnvironment struct {
@@ -35,7 +34,7 @@ type HostEnvironment struct {
StdOut io.Writer StdOut io.Writer
} }
func (e *HostEnvironment) Create(capAdd []string, capDrop []string) common.Executor { func (e *HostEnvironment) Create(_ []string, _ []string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return nil return nil
} }
@@ -50,7 +49,7 @@ func (e *HostEnvironment) Close() common.Executor {
func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Executor { func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
for _, f := range files { for _, f := range files {
if err := os.MkdirAll(filepath.Dir(filepath.Join(destPath, f.Name)), 0777); err != nil { if err := os.MkdirAll(filepath.Dir(filepath.Join(destPath, f.Name)), 0o777); err != nil {
return err return err
} }
if err := os.WriteFile(filepath.Join(destPath, f.Name), []byte(f.Body), fs.FileMode(f.Mode)); err != nil { if err := os.WriteFile(filepath.Join(destPath, f.Name), []byte(f.Body), fs.FileMode(f.Mode)); err != nil {
@@ -61,6 +60,33 @@ func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Exec
} }
} }
func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
if err := os.RemoveAll(destPath); err != nil {
return err
}
tr := tar.NewReader(tarStream)
cp := &copyCollector{
DstDir: destPath,
}
for {
ti, err := tr.Next()
if errors.Is(err, io.EOF) {
return nil
} else if err != nil {
return err
}
if ti.FileInfo().IsDir() {
continue
}
if ctx.Err() != nil {
return fmt.Errorf("CopyTarStream has been cancelled")
}
if err := cp.WriteFile(ti.Name, ti.FileInfo(), ti.Linkname, tr); err != nil {
return err
}
}
}
func (e *HostEnvironment) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor { func (e *HostEnvironment) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
@@ -141,13 +167,13 @@ func (e *HostEnvironment) GetContainerArchive(ctx context.Context, srcPath strin
return io.NopCloser(buf), nil return io.NopCloser(buf), nil
} }
func (e *HostEnvironment) Pull(forcePull bool) common.Executor { func (e *HostEnvironment) Pull(_ bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return nil return nil
} }
} }
func (e *HostEnvironment) Start(attach bool) common.Executor { func (e *HostEnvironment) Start(_ bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return nil return nil
} }
@@ -241,7 +267,7 @@ func copyPtyOutput(writer io.Writer, ppty io.Reader, finishLog context.CancelFun
} }
} }
func (e *HostEnvironment) UpdateFromImageEnv(env *map[string]string) common.Executor { func (e *HostEnvironment) UpdateFromImageEnv(_ *map[string]string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return nil return nil
} }
@@ -255,7 +281,7 @@ func getEnvListFromMap(env map[string]string) []string {
return envList return envList
} }
func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline string, env map[string]string, user, workdir string) error { func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline string, env map[string]string, _, workdir string) error {
envList := getEnvListFromMap(env) envList := getEnvListFromMap(env)
var wd string var wd string
if workdir != "" { if workdir != "" {
@@ -327,8 +353,12 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
} }
func (e *HostEnvironment) Exec(command []string /*cmdline string, */, env map[string]string, user, workdir string) common.Executor { func (e *HostEnvironment) Exec(command []string /*cmdline string, */, env map[string]string, user, workdir string) common.Executor {
return e.ExecWithCmdLine(command, "", env, user, workdir)
}
func (e *HostEnvironment) ExecWithCmdLine(command []string, cmdline string, env map[string]string, user, workdir string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
if err := e.exec(ctx, command, "" /*cmdline*/, env, user, workdir); err != nil { if err := e.exec(ctx, command, cmdline, env, user, workdir); err != nil {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return fmt.Errorf("this step has been cancelled: %w", err) return fmt.Errorf("this step has been cancelled: %w", err)
@@ -341,77 +371,7 @@ func (e *HostEnvironment) Exec(command []string /*cmdline string, */, env map[st
} }
func (e *HostEnvironment) UpdateFromEnv(srcPath string, env *map[string]string) common.Executor { func (e *HostEnvironment) UpdateFromEnv(srcPath string, env *map[string]string) common.Executor {
localEnv := *env return parseEnvFile(e, srcPath, env)
return func(ctx context.Context) error {
envTar, err := e.GetContainerArchive(ctx, srcPath)
if err != nil {
return nil
}
defer envTar.Close()
reader := tar.NewReader(envTar)
_, err = reader.Next()
if err != nil && err != io.EOF {
return err
}
s := bufio.NewScanner(reader)
for s.Scan() {
line := s.Text()
singleLineEnv := strings.Index(line, "=")
multiLineEnv := strings.Index(line, "<<")
if singleLineEnv != -1 && (multiLineEnv == -1 || singleLineEnv < multiLineEnv) {
localEnv[line[:singleLineEnv]] = line[singleLineEnv+1:]
} else if multiLineEnv != -1 {
multiLineEnvContent := ""
multiLineEnvDelimiter := line[multiLineEnv+2:]
delimiterFound := false
for s.Scan() {
content := s.Text()
if content == multiLineEnvDelimiter {
delimiterFound = true
break
}
if multiLineEnvContent != "" {
multiLineEnvContent += "\n"
}
multiLineEnvContent += content
}
if !delimiterFound {
return fmt.Errorf("invalid format delimiter '%v' not found before end of file", multiLineEnvDelimiter)
}
localEnv[line[:multiLineEnv]] = multiLineEnvContent
} else {
return fmt.Errorf("invalid format '%v', expected a line with '=' or '<<'", line)
}
}
env = &localEnv
return nil
}
}
func (e *HostEnvironment) UpdateFromPath(env *map[string]string) common.Executor {
localEnv := *env
return func(ctx context.Context) error {
pathTar, err := e.GetContainerArchive(ctx, localEnv["GITHUB_PATH"])
if err != nil {
return err
}
defer pathTar.Close()
reader := tar.NewReader(pathTar)
_, err = reader.Next()
if err != nil && err != io.EOF {
return err
}
s := bufio.NewScanner(reader)
for s.Scan() {
line := s.Text()
pathSep := string(filepath.ListSeparator)
localEnv[e.GetPathVariableName()] = fmt.Sprintf("%s%s%s", line, pathSep, localEnv[e.GetPathVariableName()])
}
env = &localEnv
return nil
}
} }
func (e *HostEnvironment) Remove() common.Executor { func (e *HostEnvironment) Remove() common.Executor {
@@ -433,7 +393,11 @@ func (e *HostEnvironment) ToContainerPath(path string) string {
} }
func (e *HostEnvironment) GetActPath() string { func (e *HostEnvironment) GetActPath() string {
return e.ActPath actPath := e.ActPath
if runtime.GOOS == "windows" {
actPath = strings.ReplaceAll(actPath, "\\", "/")
}
return actPath
} }
func (*HostEnvironment) GetPathVariableName() string { func (*HostEnvironment) GetPathVariableName() string {
@@ -454,17 +418,45 @@ func (*HostEnvironment) JoinPathVariable(paths ...string) string {
return strings.Join(paths, string(filepath.ListSeparator)) return strings.Join(paths, string(filepath.ListSeparator))
} }
func (e *HostEnvironment) GetRunnerContext(ctx context.Context) map[string]interface{} { // Reference for Arch values for runner.arch
// https://docs.github.com/en/actions/learn-github-actions/contexts#runner-context
func goArchToActionArch(arch string) string {
archMapper := map[string]string{
"x86_64": "X64",
"386": "X86",
"aarch64": "ARM64",
}
if arch, ok := archMapper[arch]; ok {
return arch
}
return arch
}
func goOsToActionOs(os string) string {
osMapper := map[string]string{
"darwin": "macOS",
}
if os, ok := osMapper[os]; ok {
return os
}
return os
}
func (e *HostEnvironment) GetRunnerContext(_ context.Context) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"os": runtime.GOOS, "os": goOsToActionOs(runtime.GOOS),
"arch": runtime.GOARCH, "arch": goArchToActionArch(runtime.GOARCH),
"temp": e.TmpDir, "temp": e.TmpDir,
"tool_cache": e.ToolCache, "tool_cache": e.ToolCache,
} }
} }
func (e *HostEnvironment) ReplaceLogWriter(stdout io.Writer, stderr io.Writer) (io.Writer, io.Writer) { func (e *HostEnvironment) ReplaceLogWriter(stdout io.Writer, _ io.Writer) (io.Writer, io.Writer) {
org := e.StdOut org := e.StdOut
e.StdOut = stdout e.StdOut = stdout
return org, org return org, org
} }
func (*HostEnvironment) IsEnvironmentCaseInsensitive() bool {
return runtime.GOOS == "windows"
}

View File

@@ -71,3 +71,7 @@ func (*LinuxContainerEnvironmentExtensions) GetRunnerContext(ctx context.Context
"tool_cache": "/opt/hostedtoolcache", "tool_cache": "/opt/hostedtoolcache",
} }
} }
func (*LinuxContainerEnvironmentExtensions) IsEnvironmentCaseInsensitive() bool {
return false
}

View File

@@ -0,0 +1,60 @@
package container
import (
"archive/tar"
"bufio"
"context"
"fmt"
"io"
"strings"
"github.com/nektos/act/pkg/common"
)
func parseEnvFile(e Container, srcPath string, env *map[string]string) common.Executor {
localEnv := *env
return func(ctx context.Context) error {
envTar, err := e.GetContainerArchive(ctx, srcPath)
if err != nil {
return nil
}
defer envTar.Close()
reader := tar.NewReader(envTar)
_, err = reader.Next()
if err != nil && err != io.EOF {
return err
}
s := bufio.NewScanner(reader)
for s.Scan() {
line := s.Text()
singleLineEnv := strings.Index(line, "=")
multiLineEnv := strings.Index(line, "<<")
if singleLineEnv != -1 && (multiLineEnv == -1 || singleLineEnv < multiLineEnv) {
localEnv[line[:singleLineEnv]] = line[singleLineEnv+1:]
} else if multiLineEnv != -1 {
multiLineEnvContent := ""
multiLineEnvDelimiter := line[multiLineEnv+2:]
delimiterFound := false
for s.Scan() {
content := s.Text()
if content == multiLineEnvDelimiter {
delimiterFound = true
break
}
if multiLineEnvContent != "" {
multiLineEnvContent += "\n"
}
multiLineEnvContent += content
}
if !delimiterFound {
return fmt.Errorf("invalid format delimiter '%v' not found before end of file", multiLineEnvDelimiter)
}
localEnv[line[:multiLineEnv]] = multiLineEnvContent
} else {
return fmt.Errorf("invalid format '%v', expected a line with '=' or '<<'", line)
}
}
env = &localEnv
return nil
}
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/creack/pty" "github.com/creack/pty"
) )
func getSysProcAttr(cmdLine string, tty bool) *syscall.SysProcAttr { func getSysProcAttr(_ string, tty bool) *syscall.SysProcAttr {
if tty { if tty {
return &syscall.SysProcAttr{ return &syscall.SysProcAttr{
Setsid: true, Setsid: true,

View File

@@ -14,6 +14,7 @@ import (
"strings" "strings"
"github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/rhysd/actionlint" "github.com/rhysd/actionlint"
) )
@@ -202,6 +203,9 @@ func (impl *interperterImpl) hashFiles(paths ...reflect.Value) (string, error) {
var files []string var files []string
if err := filepath.Walk(impl.config.WorkingDir, func(path string, fi fs.FileInfo, err error) error { if err := filepath.Walk(impl.config.WorkingDir, func(path string, fi fs.FileInfo, err error) error {
if err != nil {
return err
}
sansPrefix := strings.TrimPrefix(path, impl.config.WorkingDir+string(filepath.Separator)) sansPrefix := strings.TrimPrefix(path, impl.config.WorkingDir+string(filepath.Separator))
parts := strings.Split(sansPrefix, string(filepath.Separator)) parts := strings.Split(sansPrefix, string(filepath.Separator))
if fi.IsDir() || !matcher.Match(parts, fi.IsDir()) { if fi.IsDir() || !matcher.Match(parts, fi.IsDir()) {

View File

@@ -230,6 +230,7 @@ func TestFunctionFormat(t *testing.T) {
{"format('{0', '{1}', 'World')", nil, "Unclosed brackets. The following format string is invalid: '{0'", "format-invalid-format-string"}, {"format('{0', '{1}', 'World')", nil, "Unclosed brackets. The following format string is invalid: '{0'", "format-invalid-format-string"},
{"format('{2}', '{1}', 'World')", "", "The following format string references more arguments than were supplied: '{2}'", "format-invalid-replacement-reference"}, {"format('{2}', '{1}', 'World')", "", "The following format string references more arguments than were supplied: '{2}'", "format-invalid-replacement-reference"},
{"format('{2147483648}')", "", "The following format string is invalid: '{2147483648}'", "format-invalid-replacement-reference"}, {"format('{2147483648}')", "", "The following format string is invalid: '{2147483648}'", "format-invalid-replacement-reference"},
{"format('{0} {1} {2} {3}', 1.0, 1.1, 1234567890.0, 12345678901234567890.0)", "1 1.1 1234567890 1.23456789012346E+19", nil, "format-floats"},
} }
env := &EvaluationEnvironment{ env := &EvaluationEnvironment{

View File

@@ -12,16 +12,24 @@ import (
) )
type EvaluationEnvironment struct { type EvaluationEnvironment struct {
Github *model.GithubContext Github *model.GithubContext
Env map[string]string Env map[string]string
Job *model.JobContext Job *model.JobContext
Steps map[string]*model.StepResult Jobs *map[string]*model.WorkflowCallResult
Runner map[string]interface{} Steps map[string]*model.StepResult
Secrets map[string]string Runner map[string]interface{}
Strategy map[string]interface{} Secrets map[string]string
Matrix map[string]interface{} Vars map[string]string
Needs map[string]map[string]map[string]string Strategy map[string]interface{}
Inputs map[string]interface{} Matrix map[string]interface{}
Needs map[string]Needs
Inputs map[string]interface{}
HashFiles func([]reflect.Value) (interface{}, error)
}
type Needs struct {
Outputs map[string]string `json:"outputs"`
Result string `json:"result"`
} }
type Config struct { type Config struct {
@@ -142,6 +150,7 @@ func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (interfa
} }
} }
//nolint:gocyclo
func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) { func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) {
switch strings.ToLower(variableNode.Name) { switch strings.ToLower(variableNode.Name) {
case "github": case "github":
@@ -150,12 +159,19 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
return impl.env.Env, nil return impl.env.Env, nil
case "job": case "job":
return impl.env.Job, nil return impl.env.Job, nil
case "jobs":
if impl.env.Jobs == nil {
return nil, fmt.Errorf("Unavailable context: jobs")
}
return impl.env.Jobs, nil
case "steps": case "steps":
return impl.env.Steps, nil return impl.env.Steps, nil
case "runner": case "runner":
return impl.env.Runner, nil return impl.env.Runner, nil
case "secrets": case "secrets":
return impl.env.Secrets, nil return impl.env.Secrets, nil
case "vars":
return impl.env.Vars, nil
case "strategy": case "strategy":
return impl.env.Strategy, nil return impl.env.Strategy, nil
case "matrix": case "matrix":
@@ -361,8 +377,16 @@ func (impl *interperterImpl) compareValues(leftValue reflect.Value, rightValue r
return impl.compareNumber(leftValue.Float(), rightValue.Float(), kind) return impl.compareNumber(leftValue.Float(), rightValue.Float(), kind)
case reflect.Invalid:
if rightValue.Kind() == reflect.Invalid {
return true, nil
}
// not possible situation - params are converted to the same type in code above
return nil, fmt.Errorf("Compare params of Invalid type: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
default: default:
return nil, fmt.Errorf("TODO: evaluateCompare not implemented! left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind()) return nil, fmt.Errorf("Compare not implemented for types: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
} }
} }
@@ -423,7 +447,7 @@ func (impl *interperterImpl) coerceToString(value reflect.Value) reflect.Value {
} else if math.IsInf(value.Float(), -1) { } else if math.IsInf(value.Float(), -1) {
return reflect.ValueOf("-Infinity") return reflect.ValueOf("-Infinity")
} }
return reflect.ValueOf(fmt.Sprint(value)) return reflect.ValueOf(fmt.Sprintf("%.15G", value.Float()))
case reflect.Slice: case reflect.Slice:
return reflect.ValueOf("Array") return reflect.ValueOf("Array")
@@ -531,6 +555,10 @@ func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.Logi
leftValue := reflect.ValueOf(left) leftValue := reflect.ValueOf(left)
if IsTruthy(left) == (compareNode.Kind == actionlint.LogicalOpNodeKindOr) {
return impl.getSafeValue(leftValue), nil
}
right, err := impl.evaluateNode(compareNode.Right) right, err := impl.evaluateNode(compareNode.Right)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -540,17 +568,8 @@ func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.Logi
switch compareNode.Kind { switch compareNode.Kind {
case actionlint.LogicalOpNodeKindAnd: case actionlint.LogicalOpNodeKindAnd:
if IsTruthy(left) { return impl.getSafeValue(rightValue), nil
return impl.getSafeValue(rightValue), nil
}
return impl.getSafeValue(leftValue), nil
case actionlint.LogicalOpNodeKindOr: case actionlint.LogicalOpNodeKindOr:
if IsTruthy(left) {
return impl.getSafeValue(leftValue), nil
}
return impl.getSafeValue(rightValue), nil return impl.getSafeValue(rightValue), nil
} }
@@ -589,6 +608,9 @@ func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallN
case "fromjson": case "fromjson":
return impl.fromJSON(args[0]) return impl.fromJSON(args[0])
case "hashfiles": case "hashfiles":
if impl.env.HashFiles != nil {
return impl.env.HashFiles(args)
}
return impl.hashFiles(args...) return impl.hashFiles(args...)
case "always": case "always":
return impl.always() return impl.always()

View File

@@ -69,6 +69,11 @@ func TestOperators(t *testing.T) {
{`true || false`, true, "or", ""}, {`true || false`, true, "or", ""},
{`fromJSON('{}') && true`, true, "and-boolean-object", ""}, {`fromJSON('{}') && true`, true, "and-boolean-object", ""},
{`fromJSON('{}') || false`, make(map[string]interface{}), "or-boolean-object", ""}, {`fromJSON('{}') || false`, make(map[string]interface{}), "or-boolean-object", ""},
{"github.event.commits[0].author.username != github.event.commits[1].author.username", true, "property-comparison1", ""},
{"github.event.commits[0].author.username1 != github.event.commits[1].author.username", true, "property-comparison2", ""},
{"github.event.commits[0].author.username != github.event.commits[1].author.username1", true, "property-comparison3", ""},
{"github.event.commits[0].author.username1 != github.event.commits[1].author.username2", true, "property-comparison4", ""},
{"secrets != env", nil, "property-comparison5", "Compare not implemented for types: left: map, right: map"},
} }
env := &EvaluationEnvironment{ env := &EvaluationEnvironment{
@@ -552,9 +557,11 @@ func TestContexts(t *testing.T) {
// {"contains(steps.*.outputs.name, 'value')", true, "steps-context-array-outputs"}, // {"contains(steps.*.outputs.name, 'value')", true, "steps-context-array-outputs"},
{"runner.os", "Linux", "runner-context"}, {"runner.os", "Linux", "runner-context"},
{"secrets.name", "value", "secrets-context"}, {"secrets.name", "value", "secrets-context"},
{"vars.name", "value", "vars-context"},
{"strategy.fail-fast", true, "strategy-context"}, {"strategy.fail-fast", true, "strategy-context"},
{"matrix.os", "Linux", "matrix-context"}, {"matrix.os", "Linux", "matrix-context"},
{"needs.job-id.outputs.output-name", "value", "needs-context"}, {"needs.job-id.outputs.output-name", "value", "needs-context"},
{"needs.job-id.result", "success", "needs-context"},
{"inputs.name", "value", "inputs-context"}, {"inputs.name", "value", "inputs-context"},
} }
@@ -587,17 +594,21 @@ func TestContexts(t *testing.T) {
Secrets: map[string]string{ Secrets: map[string]string{
"name": "value", "name": "value",
}, },
Vars: map[string]string{
"name": "value",
},
Strategy: map[string]interface{}{ Strategy: map[string]interface{}{
"fail-fast": true, "fail-fast": true,
}, },
Matrix: map[string]interface{}{ Matrix: map[string]interface{}{
"os": "Linux", "os": "Linux",
}, },
Needs: map[string]map[string]map[string]string{ Needs: map[string]Needs{
"job-id": { "job-id": {
"outputs": { Outputs: map[string]string{
"output-name": "value", "output-name": "value",
}, },
Result: "success",
}, },
}, },
Inputs: map[string]interface{}{ Inputs: map[string]interface{}{

View File

@@ -1,185 +0,0 @@
package jobparser
import (
"fmt"
"regexp"
"strings"
"github.com/nektos/act/pkg/exprparser"
"gopkg.in/yaml.v3"
)
// ExpressionEvaluator is copied from runner.expressionEvaluator,
// to avoid unnecessary dependencies
type ExpressionEvaluator struct {
interpreter exprparser.Interpreter
}
func NewExpressionEvaluator(interpreter exprparser.Interpreter) *ExpressionEvaluator {
return &ExpressionEvaluator{interpreter: interpreter}
}
func (ee ExpressionEvaluator) evaluate(in string, defaultStatusCheck exprparser.DefaultStatusCheck) (interface{}, error) {
evaluated, err := ee.interpreter.Evaluate(in, defaultStatusCheck)
return evaluated, err
}
func (ee ExpressionEvaluator) evaluateScalarYamlNode(node *yaml.Node) error {
var in string
if err := node.Decode(&in); err != nil {
return err
}
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return nil
}
expr, _ := rewriteSubExpression(in, false)
res, err := ee.evaluate(expr, exprparser.DefaultStatusCheckNone)
if err != nil {
return err
}
return node.Encode(res)
}
func (ee ExpressionEvaluator) evaluateMappingYamlNode(node *yaml.Node) error {
// GitHub has this undocumented feature to merge maps, called insert directive
insertDirective := regexp.MustCompile(`\${{\s*insert\s*}}`)
for i := 0; i < len(node.Content)/2; {
k := node.Content[i*2]
v := node.Content[i*2+1]
if err := ee.EvaluateYamlNode(v); err != nil {
return err
}
var sk string
// Merge the nested map of the insert directive
if k.Decode(&sk) == nil && insertDirective.MatchString(sk) {
node.Content = append(append(node.Content[:i*2], v.Content...), node.Content[(i+1)*2:]...)
i += len(v.Content) / 2
} else {
if err := ee.EvaluateYamlNode(k); err != nil {
return err
}
i++
}
}
return nil
}
func (ee ExpressionEvaluator) evaluateSequenceYamlNode(node *yaml.Node) error {
for i := 0; i < len(node.Content); {
v := node.Content[i]
// Preserve nested sequences
wasseq := v.Kind == yaml.SequenceNode
if err := ee.EvaluateYamlNode(v); err != nil {
return err
}
// GitHub has this undocumented feature to merge sequences / arrays
// We have a nested sequence via evaluation, merge the arrays
if v.Kind == yaml.SequenceNode && !wasseq {
node.Content = append(append(node.Content[:i], v.Content...), node.Content[i+1:]...)
i += len(v.Content)
} else {
i++
}
}
return nil
}
func (ee ExpressionEvaluator) EvaluateYamlNode(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
return ee.evaluateScalarYamlNode(node)
case yaml.MappingNode:
return ee.evaluateMappingYamlNode(node)
case yaml.SequenceNode:
return ee.evaluateSequenceYamlNode(node)
default:
return nil
}
}
func (ee ExpressionEvaluator) Interpolate(in string) string {
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return in
}
expr, _ := rewriteSubExpression(in, true)
evaluated, err := ee.evaluate(expr, exprparser.DefaultStatusCheckNone)
if err != nil {
return ""
}
value, ok := evaluated.(string)
if !ok {
panic(fmt.Sprintf("Expression %s did not evaluate to a string", expr))
}
return value
}
func escapeFormatString(in string) string {
return strings.ReplaceAll(strings.ReplaceAll(in, "{", "{{"), "}", "}}")
}
func rewriteSubExpression(in string, forceFormat bool) (string, error) {
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return in, nil
}
strPattern := regexp.MustCompile("(?:''|[^'])*'")
pos := 0
exprStart := -1
strStart := -1
var results []string
formatOut := ""
for pos < len(in) {
if strStart > -1 {
matches := strPattern.FindStringIndex(in[pos:])
if matches == nil {
panic("unclosed string.")
}
strStart = -1
pos += matches[1]
} else if exprStart > -1 {
exprEnd := strings.Index(in[pos:], "}}")
strStart = strings.Index(in[pos:], "'")
if exprEnd > -1 && strStart > -1 {
if exprEnd < strStart {
strStart = -1
} else {
exprEnd = -1
}
}
if exprEnd > -1 {
formatOut += fmt.Sprintf("{%d}", len(results))
results = append(results, strings.TrimSpace(in[exprStart:pos+exprEnd]))
pos += exprEnd + 2
exprStart = -1
} else if strStart > -1 {
pos += strStart + 1
} else {
panic("unclosed expression.")
}
} else {
exprStart = strings.Index(in[pos:], "${{")
if exprStart != -1 {
formatOut += escapeFormatString(in[pos : pos+exprStart])
exprStart = pos + exprStart + 3
pos = exprStart
} else {
formatOut += escapeFormatString(in[pos:])
pos = len(in)
}
}
}
if len(results) == 1 && formatOut == "{0}" && !forceFormat {
return in, nil
}
out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", "))
return out, nil
}

View File

@@ -1,80 +0,0 @@
package jobparser
import (
"github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model"
"gopkg.in/yaml.v3"
)
// NewInterpeter returns an interpeter used in the server,
// need github, needs, strategy, matrix, inputs context only,
// see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
func NewInterpeter(
jobID string,
job *model.Job,
matrix map[string]interface{},
gitCtx *model.GithubContext,
results map[string]*JobResult,
) exprparser.Interpreter {
strategy := make(map[string]interface{})
if job.Strategy != nil {
strategy["fail-fast"] = job.Strategy.FailFast
strategy["max-parallel"] = job.Strategy.MaxParallel
}
run := &model.Run{
Workflow: &model.Workflow{
Jobs: map[string]*model.Job{},
},
JobID: jobID,
}
for id, result := range results {
need := yaml.Node{}
_ = need.Encode(result.Needs)
run.Workflow.Jobs[id] = &model.Job{
RawNeeds: need,
Result: result.Result,
Outputs: result.Outputs,
}
}
jobs := run.Workflow.Jobs
jobNeeds := run.Job().Needs()
using := map[string]map[string]map[string]string{}
for _, need := range jobNeeds {
if v, ok := jobs[need]; ok {
using[need] = map[string]map[string]string{
"outputs": v.Outputs,
}
}
}
ee := &exprparser.EvaluationEnvironment{
Github: gitCtx,
Env: nil, // no need
Job: nil, // no need
Steps: nil, // no need
Runner: nil, // no need
Secrets: nil, // no need
Strategy: strategy,
Matrix: matrix,
Needs: using,
Inputs: nil, // not supported yet
}
config := exprparser.Config{
Run: run,
WorkingDir: "", // WorkingDir is used for the function hashFiles, but it's not needed in the server
Context: "job",
}
return exprparser.NewInterpeter(ee, config)
}
// JobResult is the minimum requirement of job results for Interpeter
type JobResult struct {
Needs []string
Result string
Outputs map[string]string
}

View File

@@ -1,153 +0,0 @@
package jobparser
import (
"bytes"
"fmt"
"sort"
"strings"
"gopkg.in/yaml.v3"
"github.com/nektos/act/pkg/model"
)
func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
origin, err := model.ReadWorkflow(bytes.NewReader(content))
if err != nil {
return nil, fmt.Errorf("model.ReadWorkflow: %w", err)
}
workflow := &SingleWorkflow{}
if err := yaml.Unmarshal(content, workflow); err != nil {
return nil, fmt.Errorf("yaml.Unmarshal: %w", err)
}
pc := &parseContext{}
for _, o := range options {
o(pc)
}
results := map[string]*JobResult{}
for id, job := range origin.Jobs {
results[id] = &JobResult{
Needs: job.Needs(),
Result: pc.jobResults[id],
Outputs: nil, // not supported yet
}
}
var ret []*SingleWorkflow
for id, job := range workflow.Jobs {
for _, matrix := range getMatrixes(origin.GetJob(id)) {
job := job.Clone()
if job.Name == "" {
job.Name = id
}
job.Name = nameWithMatrix(job.Name, matrix)
job.Strategy.RawMatrix = encodeMatrix(matrix)
evaluator := NewExpressionEvaluator(NewInterpeter(id, origin.GetJob(id), matrix, pc.gitContext, results))
runsOn := origin.GetJob(id).RunsOn()
for i, v := range runsOn {
runsOn[i] = evaluator.Interpolate(v)
}
job.RawRunsOn = encodeRunsOn(runsOn)
job.EraseNeeds() // there will be only one job in SingleWorkflow, it cannot have needs
ret = append(ret, &SingleWorkflow{
Name: workflow.Name,
RawOn: workflow.RawOn,
Env: workflow.Env,
Jobs: map[string]*Job{id: job},
Defaults: workflow.Defaults,
})
}
}
sortWorkflows(ret)
return ret, nil
}
func WithJobResults(results map[string]string) ParseOption {
return func(c *parseContext) {
c.jobResults = results
}
}
func WithGitContext(context *model.GithubContext) ParseOption {
return func(c *parseContext) {
c.gitContext = context
}
}
type parseContext struct {
jobResults map[string]string
gitContext *model.GithubContext
}
type ParseOption func(c *parseContext)
func getMatrixes(job *model.Job) []map[string]interface{} {
ret := job.GetMatrixes()
sort.Slice(ret, func(i, j int) bool {
return matrixName(ret[i]) < matrixName(ret[j])
})
return ret
}
func encodeMatrix(matrix map[string]interface{}) yaml.Node {
if len(matrix) == 0 {
return yaml.Node{}
}
value := map[string][]interface{}{}
for k, v := range matrix {
value[k] = []interface{}{v}
}
node := yaml.Node{}
_ = node.Encode(value)
return node
}
func encodeRunsOn(runsOn []string) yaml.Node {
node := yaml.Node{}
if len(runsOn) == 1 {
_ = node.Encode(runsOn[0])
} else {
_ = node.Encode(runsOn)
}
return node
}
func nameWithMatrix(name string, m map[string]interface{}) string {
if len(m) == 0 {
return name
}
return name + " " + matrixName(m)
}
func matrixName(m map[string]interface{}) string {
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
vs := make([]string, 0, len(m))
for _, v := range ks {
vs = append(vs, fmt.Sprint(m[v]))
}
return fmt.Sprintf("(%s)", strings.Join(vs, ", "))
}
func sortWorkflows(wfs []*SingleWorkflow) {
sort.Slice(wfs, func(i, j int) bool {
ki := ""
for k := range wfs[i].Jobs {
ki = k
break
}
kj := ""
for k := range wfs[j].Jobs {
kj = k
break
}
return ki < kj
})
}

View File

@@ -1,65 +0,0 @@
package jobparser
import (
"embed"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
//go:embed testdata
var f embed.FS
func TestParse(t *testing.T) {
tests := []struct {
name string
options []ParseOption
wantErr bool
}{
{
name: "multiple_jobs",
options: nil,
wantErr: false,
},
{
name: "multiple_matrix",
options: nil,
wantErr: false,
},
{
name: "has_needs",
options: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
content, err := f.ReadFile(filepath.Join("testdata", tt.name+".in.yaml"))
require.NoError(t, err)
want, err := f.ReadFile(filepath.Join("testdata", tt.name+".out.yaml"))
require.NoError(t, err)
got, err := Parse(content, tt.options...)
if tt.wantErr {
require.Error(t, err)
}
require.NoError(t, err)
builder := &strings.Builder{}
for _, v := range got {
if builder.Len() > 0 {
builder.WriteString("---\n")
}
encoder := yaml.NewEncoder(builder)
encoder.SetIndent(2)
_ = encoder.Encode(v)
}
assert.Equal(t, string(want), builder.String())
})
}
}

View File

@@ -1,207 +0,0 @@
package jobparser
import (
"fmt"
"github.com/nektos/act/pkg/model"
"gopkg.in/yaml.v3"
)
// SingleWorkflow is a workflow with single job and single matrix
type SingleWorkflow struct {
Name string `yaml:"name,omitempty"`
RawOn yaml.Node `yaml:"on,omitempty"`
Env map[string]string `yaml:"env,omitempty"`
Jobs map[string]*Job `yaml:"jobs,omitempty"`
Defaults Defaults `yaml:"defaults,omitempty"`
}
func (w *SingleWorkflow) Job() (string, *Job) {
for k, v := range w.Jobs {
return k, v
}
return "", nil
}
func (w *SingleWorkflow) Marshal() ([]byte, error) {
return yaml.Marshal(w)
}
type Job struct {
Name string `yaml:"name,omitempty"`
RawNeeds yaml.Node `yaml:"needs,omitempty"`
RawRunsOn yaml.Node `yaml:"runs-on,omitempty"`
Env yaml.Node `yaml:"env,omitempty"`
If yaml.Node `yaml:"if,omitempty"`
Steps []*Step `yaml:"steps,omitempty"`
TimeoutMinutes string `yaml:"timeout-minutes,omitempty"`
Services map[string]*ContainerSpec `yaml:"services,omitempty"`
Strategy Strategy `yaml:"strategy,omitempty"`
RawContainer yaml.Node `yaml:"container,omitempty"`
Defaults Defaults `yaml:"defaults,omitempty"`
Outputs map[string]string `yaml:"outputs,omitempty"`
Uses string `yaml:"uses,omitempty"`
}
func (j *Job) Clone() *Job {
if j == nil {
return nil
}
return &Job{
Name: j.Name,
RawNeeds: j.RawNeeds,
RawRunsOn: j.RawRunsOn,
Env: j.Env,
If: j.If,
Steps: j.Steps,
TimeoutMinutes: j.TimeoutMinutes,
Services: j.Services,
Strategy: j.Strategy,
RawContainer: j.RawContainer,
Defaults: j.Defaults,
Outputs: j.Outputs,
Uses: j.Uses,
}
}
func (j *Job) Needs() []string {
return (&model.Job{RawNeeds: j.RawNeeds}).Needs()
}
func (j *Job) EraseNeeds() {
j.RawNeeds = yaml.Node{}
}
func (j *Job) RunsOn() []string {
return (&model.Job{RawRunsOn: j.RawRunsOn}).RunsOn()
}
type Step struct {
ID string `yaml:"id,omitempty"`
If yaml.Node `yaml:"if,omitempty"`
Name string `yaml:"name,omitempty"`
Uses string `yaml:"uses,omitempty"`
Run string `yaml:"run,omitempty"`
WorkingDirectory string `yaml:"working-directory,omitempty"`
Shell string `yaml:"shell,omitempty"`
Env yaml.Node `yaml:"env,omitempty"`
With map[string]string `yaml:"with,omitempty"`
ContinueOnError bool `yaml:"continue-on-error,omitempty"`
TimeoutMinutes string `yaml:"timeout-minutes,omitempty"`
}
// String gets the name of step
func (s *Step) String() string {
return (&model.Step{
ID: s.ID,
Name: s.Name,
Uses: s.Uses,
Run: s.Run,
}).String()
}
type ContainerSpec struct {
Image string `yaml:"image,omitempty"`
Env map[string]string `yaml:"env,omitempty"`
Ports []string `yaml:"ports,omitempty"`
Volumes []string `yaml:"volumes,omitempty"`
Options string `yaml:"options,omitempty"`
Credentials map[string]string `yaml:"credentials,omitempty"`
}
type Strategy struct {
FailFastString string `yaml:"fail-fast,omitempty"`
MaxParallelString string `yaml:"max-parallel,omitempty"`
RawMatrix yaml.Node `yaml:"matrix,omitempty"`
}
type Defaults struct {
Run RunDefaults `yaml:"run,omitempty"`
}
type RunDefaults struct {
Shell string `yaml:"shell,omitempty"`
WorkingDirectory string `yaml:"working-directory,omitempty"`
}
type Event struct {
Name string
Acts map[string][]string
}
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
switch rawOn.Kind {
case yaml.ScalarNode:
var val string
err := rawOn.Decode(&val)
if err != nil {
return nil, err
}
return []*Event{
{Name: val},
}, nil
case yaml.SequenceNode:
var val []interface{}
err := rawOn.Decode(&val)
if err != nil {
return nil, err
}
res := make([]*Event, 0, len(val))
for _, v := range val {
switch t := v.(type) {
case string:
res = append(res, &Event{Name: t})
default:
return nil, fmt.Errorf("invalid type %T", t)
}
}
return res, nil
case yaml.MappingNode:
var val map[string]interface{}
err := rawOn.Decode(&val)
if err != nil {
return nil, err
}
res := make([]*Event, 0, len(val))
for k, v := range val {
switch t := v.(type) {
case string:
res = append(res, &Event{
Name: k,
Acts: map[string][]string{},
})
case []string:
res = append(res, &Event{
Name: k,
Acts: map[string][]string{},
})
case map[string]interface{}:
acts := make(map[string][]string, len(t))
for act, branches := range t {
switch b := branches.(type) {
case string:
acts[act] = []string{b}
case []string:
acts[act] = b
case []interface{}:
acts[act] = make([]string, len(b))
for i, v := range b {
acts[act][i] = v.(string)
}
default:
return nil, fmt.Errorf("unknown on type: %#v", branches)
}
}
res = append(res, &Event{
Name: k,
Acts: acts,
})
default:
return nil, fmt.Errorf("unknown on type: %#v", v)
}
}
return res, nil
default:
return nil, fmt.Errorf("unknown on type: %v", rawOn.Kind)
}
}

View File

@@ -1,184 +0,0 @@
package jobparser
import (
"fmt"
"strings"
"testing"
"github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert"
)
func TestParseRawOn(t *testing.T) {
kases := []struct {
input string
result []*Event
}{
{
input: "on: issue_comment",
result: []*Event{
{
Name: "issue_comment",
},
},
},
{
input: "on:\n push",
result: []*Event{
{
Name: "push",
},
},
},
{
input: "on:\n - push\n - pull_request",
result: []*Event{
{
Name: "push",
},
{
Name: "pull_request",
},
},
},
{
input: "on:\n push:\n branches:\n - master",
result: []*Event{
{
Name: "push",
Acts: map[string][]string{
"branches": {
"master",
},
},
},
},
},
{
input: "on:\n branch_protection_rule:\n types: [created, deleted]",
result: []*Event{
{
Name: "branch_protection_rule",
Acts: map[string][]string{
"types": {
"created",
"deleted",
},
},
},
},
},
{
input: "on:\n project:\n types: [created, deleted]\n milestone:\n types: [opened, deleted]",
result: []*Event{
{
Name: "project",
Acts: map[string][]string{
"types": {
"created",
"deleted",
},
},
},
{
Name: "milestone",
Acts: map[string][]string{
"types": {
"opened",
"deleted",
},
},
},
},
},
{
input: "on:\n pull_request:\n types:\n - opened\n branches:\n - 'releases/**'",
result: []*Event{
{
Name: "pull_request",
Acts: map[string][]string{
"types": {
"opened",
},
"branches": {
"releases/**",
},
},
},
},
},
{
input: "on:\n push:\n branches:\n - main\n pull_request:\n types:\n - opened\n branches:\n - '**'",
result: []*Event{
{
Name: "push",
Acts: map[string][]string{
"branches": {
"main",
},
},
},
{
Name: "pull_request",
Acts: map[string][]string{
"types": {
"opened",
},
"branches": {
"**",
},
},
},
},
},
{
input: "on:\n push:\n branches:\n - 'main'\n - 'releases/**'",
result: []*Event{
{
Name: "push",
Acts: map[string][]string{
"branches": {
"main",
"releases/**",
},
},
},
},
},
{
input: "on:\n push:\n tags:\n - v1.**",
result: []*Event{
{
Name: "push",
Acts: map[string][]string{
"tags": {
"v1.**",
},
},
},
},
},
{
input: "on: [pull_request, workflow_dispatch]",
result: []*Event{
{
Name: "pull_request",
},
{
Name: "workflow_dispatch",
},
},
},
}
for _, kase := range kases {
t.Run(kase.input, func(t *testing.T) {
origin, err := model.ReadWorkflow(strings.NewReader(kase.input))
assert.NoError(t, err)
events, err := ParseRawOn(&origin.RawOn)
assert.NoError(t, err)
assert.EqualValues(t, kase.result, events, fmt.Sprintf("%#v", events))
})
}
}

View File

@@ -1,16 +0,0 @@
name: test
jobs:
job1:
runs-on: linux
steps:
- run: uname -a
job2:
runs-on: linux
steps:
- run: uname -a
needs: job1
job3:
runs-on: linux
steps:
- run: uname -a
needs: [job1, job2]

View File

@@ -1,23 +0,0 @@
name: test
jobs:
job1:
name: job1
runs-on: linux
steps:
- run: uname -a
---
name: test
jobs:
job2:
name: job2
runs-on: linux
steps:
- run: uname -a
---
name: test
jobs:
job3:
name: job3
runs-on: linux
steps:
- run: uname -a

View File

@@ -1,14 +0,0 @@
name: test
jobs:
job1:
runs-on: linux
steps:
- run: uname -a && go version
job2:
runs-on: linux
steps:
- run: uname -a && go version
job3:
runs-on: linux
steps:
- run: uname -a && go version

View File

@@ -1,23 +0,0 @@
name: test
jobs:
job1:
name: job1
runs-on: linux
steps:
- run: uname -a && go version
---
name: test
jobs:
job2:
name: job2
runs-on: linux
steps:
- run: uname -a && go version
---
name: test
jobs:
job3:
name: job3
runs-on: linux
steps:
- run: uname -a && go version

View File

@@ -1,13 +0,0 @@
name: test
jobs:
job1:
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-20.04]
version: [1.17, 1.18, 1.19]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version

View File

@@ -1,101 +0,0 @@
name: test
jobs:
job1:
name: job1 (ubuntu-20.04, 1.17)
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version
strategy:
matrix:
os:
- ubuntu-20.04
version:
- 1.17
---
name: test
jobs:
job1:
name: job1 (ubuntu-20.04, 1.18)
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version
strategy:
matrix:
os:
- ubuntu-20.04
version:
- 1.18
---
name: test
jobs:
job1:
name: job1 (ubuntu-20.04, 1.19)
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version
strategy:
matrix:
os:
- ubuntu-20.04
version:
- 1.19
---
name: test
jobs:
job1:
name: job1 (ubuntu-22.04, 1.17)
runs-on: ubuntu-22.04
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version
strategy:
matrix:
os:
- ubuntu-22.04
version:
- 1.17
---
name: test
jobs:
job1:
name: job1 (ubuntu-22.04, 1.18)
runs-on: ubuntu-22.04
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version
strategy:
matrix:
os:
- ubuntu-22.04
version:
- 1.18
---
name: test
jobs:
job1:
name: job1 (ubuntu-22.04, 1.19)
runs-on: ubuntu-22.04
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
- run: uname -a && go version
strategy:
matrix:
os:
- ubuntu-22.04
version:
- 1.19

View File

@@ -20,7 +20,7 @@ func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(interface{}) error) error
// Force input to lowercase for case insensitive comparison // Force input to lowercase for case insensitive comparison
format := ActionRunsUsing(strings.ToLower(using)) format := ActionRunsUsing(strings.ToLower(using))
switch format { switch format {
case ActionRunsUsingNode16, ActionRunsUsingNode12, ActionRunsUsingDocker, ActionRunsUsingComposite: case ActionRunsUsingNode20, ActionRunsUsingNode16, ActionRunsUsingNode12, ActionRunsUsingDocker, ActionRunsUsingComposite:
*a = format *a = format
default: default:
return fmt.Errorf(fmt.Sprintf("The runs.using key in action.yml must be one of: %v, got %s", []string{ return fmt.Errorf(fmt.Sprintf("The runs.using key in action.yml must be one of: %v, got %s", []string{
@@ -28,6 +28,7 @@ func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(interface{}) error) error
ActionRunsUsingDocker, ActionRunsUsingDocker,
ActionRunsUsingNode12, ActionRunsUsingNode12,
ActionRunsUsingNode16, ActionRunsUsingNode16,
ActionRunsUsingNode20,
}, format)) }, format))
} }
return nil return nil
@@ -36,14 +37,14 @@ func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(interface{}) error) error
const ( const (
// ActionRunsUsingNode12 for running with node12 // ActionRunsUsingNode12 for running with node12
ActionRunsUsingNode12 = "node12" ActionRunsUsingNode12 = "node12"
// ActionRunsUsingNode12 for running with node16 // ActionRunsUsingNode16 for running with node16
ActionRunsUsingNode16 = "node16" ActionRunsUsingNode16 = "node16"
// ActionRunsUsingNode20 for running with node20
ActionRunsUsingNode20 = "node20"
// ActionRunsUsingDocker for running with docker // ActionRunsUsingDocker for running with docker
ActionRunsUsingDocker = "docker" ActionRunsUsingDocker = "docker"
// ActionRunsUsingComposite for running composite // ActionRunsUsingComposite for running composite
ActionRunsUsingComposite = "composite" ActionRunsUsingComposite = "composite"
// ActionRunsUsingGo for running with go
ActionRunsUsingGo = "go"
) )
// ActionRuns are a field in Action // ActionRuns are a field in Action

View File

@@ -3,6 +3,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/common/git" "github.com/nektos/act/pkg/common/git"
@@ -35,6 +36,9 @@ type GithubContext struct {
RetentionDays string `json:"retention_days"` RetentionDays string `json:"retention_days"`
RunnerPerflog string `json:"runner_perflog"` RunnerPerflog string `json:"runner_perflog"`
RunnerTrackingID string `json:"runner_tracking_id"` RunnerTrackingID string `json:"runner_tracking_id"`
ServerURL string `json:"server_url"`
APIURL string `json:"api_url"`
GraphQLURL string `json:"graphql_url"`
} }
func asString(v interface{}) string { func asString(v interface{}) string {
@@ -89,26 +93,22 @@ func withDefaultBranch(ctx context.Context, b string, event map[string]interface
var findGitRef = git.FindGitRef var findGitRef = git.FindGitRef
var findGitRevision = git.FindGitRevision var findGitRevision = git.FindGitRevision
func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string, repoPath string) { func (ghc *GithubContext) SetRef(ctx context.Context, defaultBranch string, repoPath string) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows // https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
switch ghc.EventName { switch ghc.EventName {
case "pull_request_target": case "pull_request_target":
ghc.Ref = fmt.Sprintf("refs/heads/%s", ghc.BaseRef) ghc.Ref = fmt.Sprintf("refs/heads/%s", ghc.BaseRef)
ghc.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
case "pull_request", "pull_request_review", "pull_request_review_comment": case "pull_request", "pull_request_review", "pull_request_review_comment":
ghc.Ref = fmt.Sprintf("refs/pull/%.0f/merge", ghc.Event["number"]) ghc.Ref = fmt.Sprintf("refs/pull/%.0f/merge", ghc.Event["number"])
case "deployment", "deployment_status": case "deployment", "deployment_status":
ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref")) ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref"))
ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha"))
case "release": case "release":
ghc.Ref = asString(nestedMapLookup(ghc.Event, "release", "tag_name")) ghc.Ref = fmt.Sprintf("refs/tags/%s", asString(nestedMapLookup(ghc.Event, "release", "tag_name")))
case "push", "create", "workflow_dispatch": case "push", "create", "workflow_dispatch":
ghc.Ref = asString(ghc.Event["ref"]) ghc.Ref = asString(ghc.Event["ref"])
if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
ghc.Sha = asString(ghc.Event["after"])
}
default: default:
defaultBranch := asString(nestedMapLookup(ghc.Event, "repository", "default_branch")) defaultBranch := asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
if defaultBranch != "" { if defaultBranch != "" {
@@ -136,6 +136,23 @@ func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string
ghc.Ref = fmt.Sprintf("refs/heads/%s", asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))) ghc.Ref = fmt.Sprintf("refs/heads/%s", asString(nestedMapLookup(ghc.Event, "repository", "default_branch")))
} }
} }
}
func (ghc *GithubContext) SetSha(ctx context.Context, repoPath string) {
logger := common.Logger(ctx)
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
switch ghc.EventName {
case "pull_request_target":
ghc.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
case "deployment", "deployment_status":
ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha"))
case "push", "create", "workflow_dispatch":
if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
ghc.Sha = asString(ghc.Event["after"])
}
}
if ghc.Sha == "" { if ghc.Sha == "" {
_, sha, err := findGitRevision(ctx, repoPath) _, sha, err := findGitRevision(ctx, repoPath)
@@ -146,3 +163,51 @@ func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string
} }
} }
} }
func (ghc *GithubContext) SetRepositoryAndOwner(ctx context.Context, githubInstance string, remoteName string, repoPath string) {
if ghc.Repository == "" {
repo, err := git.FindGithubRepo(ctx, repoPath, githubInstance, remoteName)
if err != nil {
common.Logger(ctx).Warningf("unable to get git repo: %v", err)
return
}
ghc.Repository = repo
}
ghc.RepositoryOwner = strings.Split(ghc.Repository, "/")[0]
}
func (ghc *GithubContext) SetRefTypeAndName() {
var refType, refName string
// https://docs.github.com/en/actions/learn-github-actions/environment-variables
if strings.HasPrefix(ghc.Ref, "refs/tags/") {
refType = "tag"
refName = ghc.Ref[len("refs/tags/"):]
} else if strings.HasPrefix(ghc.Ref, "refs/heads/") {
refType = "branch"
refName = ghc.Ref[len("refs/heads/"):]
} else if strings.HasPrefix(ghc.Ref, "refs/pull/") {
refType = ""
refName = ghc.Ref[len("refs/pull/"):]
}
if ghc.RefType == "" {
ghc.RefType = refType
}
if ghc.RefName == "" {
ghc.RefName = refName
}
}
func (ghc *GithubContext) SetBaseAndHeadRef() {
if ghc.EventName == "pull_request" || ghc.EventName == "pull_request_target" {
if ghc.BaseRef == "" {
ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref"))
}
if ghc.HeadRef == "" {
ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref"))
}
}
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSetRefAndSha(t *testing.T) { func TestSetRef(t *testing.T) {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
oldFindGitRef := findGitRef oldFindGitRef := findGitRef
@@ -29,38 +29,31 @@ func TestSetRefAndSha(t *testing.T) {
eventName string eventName string
event map[string]interface{} event map[string]interface{}
ref string ref string
sha string refName string
}{ }{
{ {
eventName: "pull_request_target", eventName: "pull_request_target",
event: map[string]interface{}{ event: map[string]interface{}{},
"pull_request": map[string]interface{}{ ref: "refs/heads/master",
"base": map[string]interface{}{ refName: "master",
"sha": "pr-base-sha",
},
},
},
ref: "refs/heads/master",
sha: "pr-base-sha",
}, },
{ {
eventName: "pull_request", eventName: "pull_request",
event: map[string]interface{}{ event: map[string]interface{}{
"number": 1234., "number": 1234.,
}, },
ref: "refs/pull/1234/merge", ref: "refs/pull/1234/merge",
sha: "1234fakesha", refName: "1234/merge",
}, },
{ {
eventName: "deployment", eventName: "deployment",
event: map[string]interface{}{ event: map[string]interface{}{
"deployment": map[string]interface{}{ "deployment": map[string]interface{}{
"ref": "refs/heads/somebranch", "ref": "refs/heads/somebranch",
"sha": "deployment-sha",
}, },
}, },
ref: "refs/heads/somebranch", ref: "refs/heads/somebranch",
sha: "deployment-sha", refName: "somebranch",
}, },
{ {
eventName: "release", eventName: "release",
@@ -69,18 +62,16 @@ func TestSetRefAndSha(t *testing.T) {
"tag_name": "v1.0.0", "tag_name": "v1.0.0",
}, },
}, },
ref: "v1.0.0", ref: "refs/tags/v1.0.0",
sha: "1234fakesha", refName: "v1.0.0",
}, },
{ {
eventName: "push", eventName: "push",
event: map[string]interface{}{ event: map[string]interface{}{
"ref": "refs/heads/somebranch", "ref": "refs/heads/somebranch",
"after": "push-sha",
"deleted": false,
}, },
ref: "refs/heads/somebranch", ref: "refs/heads/somebranch",
sha: "push-sha", refName: "somebranch",
}, },
{ {
eventName: "unknown", eventName: "unknown",
@@ -89,14 +80,14 @@ func TestSetRefAndSha(t *testing.T) {
"default_branch": "main", "default_branch": "main",
}, },
}, },
ref: "refs/heads/main", ref: "refs/heads/main",
sha: "1234fakesha", refName: "main",
}, },
{ {
eventName: "no-event", eventName: "no-event",
event: map[string]interface{}{}, event: map[string]interface{}{},
ref: "refs/heads/master", ref: "refs/heads/master",
sha: "1234fakesha", refName: "master",
}, },
} }
@@ -108,10 +99,11 @@ func TestSetRefAndSha(t *testing.T) {
Event: table.event, Event: table.event,
} }
ghc.SetRefAndSha(context.Background(), "main", "/some/dir") ghc.SetRef(context.Background(), "main", "/some/dir")
ghc.SetRefTypeAndName()
assert.Equal(t, table.ref, ghc.Ref) assert.Equal(t, table.ref, ghc.Ref)
assert.Equal(t, table.sha, ghc.Sha) assert.Equal(t, table.refName, ghc.RefName)
}) })
} }
@@ -125,9 +117,96 @@ func TestSetRefAndSha(t *testing.T) {
Event: map[string]interface{}{}, Event: map[string]interface{}{},
} }
ghc.SetRefAndSha(context.Background(), "", "/some/dir") ghc.SetRef(context.Background(), "", "/some/dir")
assert.Equal(t, "refs/heads/master", ghc.Ref) assert.Equal(t, "refs/heads/master", ghc.Ref)
assert.Equal(t, "1234fakesha", ghc.Sha)
}) })
} }
func TestSetSha(t *testing.T) {
log.SetLevel(log.DebugLevel)
oldFindGitRef := findGitRef
oldFindGitRevision := findGitRevision
defer func() { findGitRef = oldFindGitRef }()
defer func() { findGitRevision = oldFindGitRevision }()
findGitRef = func(ctx context.Context, file string) (string, error) {
return "refs/heads/master", nil
}
findGitRevision = func(ctx context.Context, file string) (string, string, error) {
return "", "1234fakesha", nil
}
tables := []struct {
eventName string
event map[string]interface{}
sha string
}{
{
eventName: "pull_request_target",
event: map[string]interface{}{
"pull_request": map[string]interface{}{
"base": map[string]interface{}{
"sha": "pr-base-sha",
},
},
},
sha: "pr-base-sha",
},
{
eventName: "pull_request",
event: map[string]interface{}{
"number": 1234.,
},
sha: "1234fakesha",
},
{
eventName: "deployment",
event: map[string]interface{}{
"deployment": map[string]interface{}{
"sha": "deployment-sha",
},
},
sha: "deployment-sha",
},
{
eventName: "release",
event: map[string]interface{}{},
sha: "1234fakesha",
},
{
eventName: "push",
event: map[string]interface{}{
"after": "push-sha",
"deleted": false,
},
sha: "push-sha",
},
{
eventName: "unknown",
event: map[string]interface{}{},
sha: "1234fakesha",
},
{
eventName: "no-event",
event: map[string]interface{}{},
sha: "1234fakesha",
},
}
for _, table := range tables {
t.Run(table.eventName, func(t *testing.T) {
ghc := &GithubContext{
EventName: table.eventName,
BaseRef: "master",
Event: table.event,
}
ghc.SetSha(context.Background(), "/some/dir")
assert.Equal(t, table.sha, ghc.Sha)
})
}
}

View File

@@ -15,9 +15,9 @@ import (
// WorkflowPlanner contains methods for creating plans // WorkflowPlanner contains methods for creating plans
type WorkflowPlanner interface { type WorkflowPlanner interface {
PlanEvent(eventName string) *Plan PlanEvent(eventName string) (*Plan, error)
PlanJob(jobName string) *Plan PlanJob(jobName string) (*Plan, error)
PlanAll() *Plan PlanAll() (*Plan, error)
GetEvents() []string GetEvents() []string
} }
@@ -164,59 +164,81 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e
return wp, nil return wp, nil
} }
// CombineWorkflowPlanner combines workflows to a WorkflowPlanner
func CombineWorkflowPlanner(workflows ...*Workflow) WorkflowPlanner {
return &workflowPlanner{
workflows: workflows,
}
}
type workflowPlanner struct { type workflowPlanner struct {
workflows []*Workflow workflows []*Workflow
} }
// PlanEvent builds a new list of runs to execute in parallel for an event name // PlanEvent builds a new list of runs to execute in parallel for an event name
func (wp *workflowPlanner) PlanEvent(eventName string) *Plan { func (wp *workflowPlanner) PlanEvent(eventName string) (*Plan, error) {
plan := new(Plan) plan := new(Plan)
if len(wp.workflows) == 0 { if len(wp.workflows) == 0 {
log.Debugf("no events found for workflow: %s", eventName) log.Debug("no workflows found by planner")
return plan, nil
} }
var lastErr error
for _, w := range wp.workflows { for _, w := range wp.workflows {
for _, e := range w.On() { events := w.On()
if len(events) == 0 {
log.Debugf("no events found for workflow: %s", w.File)
continue
}
for _, e := range events {
if e == eventName { if e == eventName {
plan.mergeStages(createStages(w, w.GetJobIDs()...)) stages, err := createStages(w, w.GetJobIDs()...)
if err != nil {
log.Warn(err)
lastErr = err
} else {
plan.mergeStages(stages)
}
} }
} }
} }
return plan return plan, lastErr
} }
// PlanJob builds a new run to execute in parallel for a job name // PlanJob builds a new run to execute in parallel for a job name
func (wp *workflowPlanner) PlanJob(jobName string) *Plan { func (wp *workflowPlanner) PlanJob(jobName string) (*Plan, error) {
plan := new(Plan) plan := new(Plan)
if len(wp.workflows) == 0 { if len(wp.workflows) == 0 {
log.Debugf("no jobs found for workflow: %s", jobName) log.Debugf("no jobs found for workflow: %s", jobName)
} }
var lastErr error
for _, w := range wp.workflows { for _, w := range wp.workflows {
plan.mergeStages(createStages(w, jobName)) stages, err := createStages(w, jobName)
if err != nil {
log.Warn(err)
lastErr = err
} else {
plan.mergeStages(stages)
}
} }
return plan return plan, lastErr
} }
// PlanAll builds a new run to execute in parallel all // PlanAll builds a new run to execute in parallel all
func (wp *workflowPlanner) PlanAll() *Plan { func (wp *workflowPlanner) PlanAll() (*Plan, error) {
plan := new(Plan) plan := new(Plan)
if len(wp.workflows) == 0 { if len(wp.workflows) == 0 {
log.Debugf("no jobs found for loaded workflows") log.Debug("no workflows found by planner")
return plan, nil
} }
var lastErr error
for _, w := range wp.workflows { for _, w := range wp.workflows {
plan.mergeStages(createStages(w, w.GetJobIDs()...)) stages, err := createStages(w, w.GetJobIDs()...)
if err != nil {
log.Warn(err)
lastErr = err
} else {
plan.mergeStages(stages)
}
} }
return plan return plan, lastErr
} }
// GetEvents gets all the events in the workflows file // GetEvents gets all the events in the workflows file
@@ -289,7 +311,7 @@ func (p *Plan) mergeStages(stages []*Stage) {
p.Stages = newStages p.Stages = newStages
} }
func createStages(w *Workflow, jobIDs ...string) []*Stage { func createStages(w *Workflow, jobIDs ...string) ([]*Stage, error) {
// first, build a list of all the necessary jobs to run, and their dependencies // first, build a list of all the necessary jobs to run, and their dependencies
jobDependencies := make(map[string][]string) jobDependencies := make(map[string][]string)
for len(jobIDs) > 0 { for len(jobIDs) > 0 {
@@ -321,12 +343,16 @@ func createStages(w *Workflow, jobIDs ...string) []*Stage {
} }
} }
if len(stage.Runs) == 0 { if len(stage.Runs) == 0 {
log.Fatalf("Unable to build dependency graph!") return nil, fmt.Errorf("unable to build dependency graph for %s (%s)", w.Name, w.File)
} }
stages = append(stages, stage) stages = append(stages, stage)
} }
return stages if len(stages) == 0 {
return nil, fmt.Errorf("Could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name")
}
return stages, nil
} }
// return true iff all strings in srcList exist in at least one of the stages // return true iff all strings in srcList exist in at least one of the stages

View File

@@ -39,3 +39,25 @@ func TestPlanner(t *testing.T) {
} }
} }
} }
func TestWorkflow(t *testing.T) {
log.SetLevel(log.DebugLevel)
workflow := Workflow{
Jobs: map[string]*Job{
"valid_job": {
Name: "valid_job",
},
},
}
// Check that an invalid job id returns error
result, err := createStages(&workflow, "invalid_job_id")
assert.NotNil(t, err)
assert.Nil(t, result)
// Check that an valid job id returns non-error
result, err = createStages(&workflow, "valid_job")
assert.Nil(t, err)
assert.NotNil(t, result)
}

View File

@@ -42,5 +42,4 @@ type StepResult struct {
Outputs map[string]string `json:"outputs"` Outputs map[string]string `json:"outputs"`
Conclusion stepStatus `json:"conclusion"` Conclusion stepStatus `json:"conclusion"`
Outcome stepStatus `json:"outcome"` Outcome stepStatus `json:"outcome"`
State map[string]string
} }

View File

@@ -58,39 +58,14 @@ func (w *Workflow) On() []string {
func (w *Workflow) OnEvent(event string) interface{} { func (w *Workflow) OnEvent(event string) interface{} {
if w.RawOn.Kind == yaml.MappingNode { if w.RawOn.Kind == yaml.MappingNode {
var val map[string]interface{} var val map[string]interface{}
err := w.RawOn.Decode(&val) if !decodeNode(w.RawOn, &val) {
if err != nil { return nil
log.Fatal(err)
} }
return val[event] return val[event]
} }
return nil return nil
} }
func (w *Workflow) OnSchedule() []string {
schedules := w.OnEvent("schedule")
if schedules == nil {
return []string{}
}
switch val := schedules.(type) {
case []interface{}:
allSchedules := []string{}
for _, v := range val {
for k, cron := range v.(map[string]interface{}) {
if k != "cron" {
continue
}
allSchedules = append(allSchedules, cron.(string))
}
}
return allSchedules
default:
}
return []string{}
}
type WorkflowDispatchInput struct { type WorkflowDispatchInput struct {
Description string `yaml:"description"` Description string `yaml:"description"`
Required bool `yaml:"required"` Required bool `yaml:"required"`
@@ -109,16 +84,55 @@ func (w *Workflow) WorkflowDispatchConfig() *WorkflowDispatch {
} }
var val map[string]yaml.Node var val map[string]yaml.Node
err := w.RawOn.Decode(&val) if !decodeNode(w.RawOn, &val) {
if err != nil { return nil
log.Fatal(err)
} }
var config WorkflowDispatch var config WorkflowDispatch
node := val["workflow_dispatch"] node := val["workflow_dispatch"]
err = node.Decode(&config) if !decodeNode(node, &config) {
if err != nil { return nil
log.Fatal(err) }
return &config
}
type WorkflowCallInput struct {
Description string `yaml:"description"`
Required bool `yaml:"required"`
Default string `yaml:"default"`
Type string `yaml:"type"`
}
type WorkflowCallOutput struct {
Description string `yaml:"description"`
Value string `yaml:"value"`
}
type WorkflowCall struct {
Inputs map[string]WorkflowCallInput `yaml:"inputs"`
Outputs map[string]WorkflowCallOutput `yaml:"outputs"`
}
type WorkflowCallResult struct {
Outputs map[string]string
}
func (w *Workflow) WorkflowCallConfig() *WorkflowCall {
if w.RawOn.Kind != yaml.MappingNode {
// The callers expect for "on: workflow_call" and "on: [ workflow_call ]" a non nil return value
return &WorkflowCall{}
}
var val map[string]yaml.Node
if !decodeNode(w.RawOn, &val) {
return &WorkflowCall{}
}
var config WorkflowCall
node := val["workflow_call"]
if !decodeNode(node, &config) {
return &WorkflowCall{}
} }
return &config return &config
@@ -139,6 +153,8 @@ type Job struct {
Defaults Defaults `yaml:"defaults"` Defaults Defaults `yaml:"defaults"`
Outputs map[string]string `yaml:"outputs"` Outputs map[string]string `yaml:"outputs"`
Uses string `yaml:"uses"` Uses string `yaml:"uses"`
With map[string]interface{} `yaml:"with"`
RawSecrets yaml.Node `yaml:"secrets"`
Result string Result string
} }
@@ -193,21 +209,45 @@ func (s Strategy) GetFailFast() bool {
return failFast return failFast
} }
func (j *Job) InheritSecrets() bool {
if j.RawSecrets.Kind != yaml.ScalarNode {
return false
}
var val string
if !decodeNode(j.RawSecrets, &val) {
return false
}
return val == "inherit"
}
func (j *Job) Secrets() map[string]string {
if j.RawSecrets.Kind != yaml.MappingNode {
return nil
}
var val map[string]string
if !decodeNode(j.RawSecrets, &val) {
return nil
}
return val
}
// Container details for the job // Container details for the job
func (j *Job) Container() *ContainerSpec { func (j *Job) Container() *ContainerSpec {
var val *ContainerSpec var val *ContainerSpec
switch j.RawContainer.Kind { switch j.RawContainer.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
val = new(ContainerSpec) val = new(ContainerSpec)
err := j.RawContainer.Decode(&val.Image) if !decodeNode(j.RawContainer, &val.Image) {
if err != nil { return nil
log.Fatal(err)
} }
case yaml.MappingNode: case yaml.MappingNode:
val = new(ContainerSpec) val = new(ContainerSpec)
err := j.RawContainer.Decode(val) if !decodeNode(j.RawContainer, val) {
if err != nil { return nil
log.Fatal(err)
} }
} }
return val return val
@@ -218,16 +258,14 @@ func (j *Job) Needs() []string {
switch j.RawNeeds.Kind { switch j.RawNeeds.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
var val string var val string
err := j.RawNeeds.Decode(&val) if !decodeNode(j.RawNeeds, &val) {
if err != nil { return nil
log.Fatal(err)
} }
return []string{val} return []string{val}
case yaml.SequenceNode: case yaml.SequenceNode:
var val []string var val []string
err := j.RawNeeds.Decode(&val) if !decodeNode(j.RawNeeds, &val) {
if err != nil { return nil
log.Fatal(err)
} }
return val return val
} }
@@ -237,18 +275,40 @@ func (j *Job) Needs() []string {
// RunsOn list for Job // RunsOn list for Job
func (j *Job) RunsOn() []string { func (j *Job) RunsOn() []string {
switch j.RawRunsOn.Kind { switch j.RawRunsOn.Kind {
case yaml.MappingNode:
var val struct {
Group string
Labels yaml.Node
}
if !decodeNode(j.RawRunsOn, &val) {
return nil
}
labels := nodeAsStringSlice(val.Labels)
if val.Group != "" {
labels = append(labels, val.Group)
}
return labels
default:
return nodeAsStringSlice(j.RawRunsOn)
}
}
func nodeAsStringSlice(node yaml.Node) []string {
switch node.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
var val string var val string
err := j.RawRunsOn.Decode(&val) if !decodeNode(node, &val) {
if err != nil { return nil
log.Fatal(err)
} }
return []string{val} return []string{val}
case yaml.SequenceNode: case yaml.SequenceNode:
var val []string var val []string
err := j.RawRunsOn.Decode(&val) if !decodeNode(node, &val) {
if err != nil { return nil
log.Fatal(err)
} }
return val return val
} }
@@ -258,8 +318,8 @@ func (j *Job) RunsOn() []string {
func environment(yml yaml.Node) map[string]string { func environment(yml yaml.Node) map[string]string {
env := make(map[string]string) env := make(map[string]string)
if yml.Kind == yaml.MappingNode { if yml.Kind == yaml.MappingNode {
if err := yml.Decode(&env); err != nil { if !decodeNode(yml, &env) {
log.Fatal(err) return nil
} }
} }
return env return env
@@ -274,8 +334,8 @@ func (j *Job) Environment() map[string]string {
func (j *Job) Matrix() map[string][]interface{} { func (j *Job) Matrix() map[string][]interface{} {
if j.Strategy.RawMatrix.Kind == yaml.MappingNode { if j.Strategy.RawMatrix.Kind == yaml.MappingNode {
var val map[string][]interface{} var val map[string][]interface{}
if err := j.Strategy.RawMatrix.Decode(&val); err != nil { if !decodeNode(j.Strategy.RawMatrix, &val) {
log.Fatal(err) return nil
} }
return val return val
} }
@@ -286,7 +346,7 @@ func (j *Job) Matrix() map[string][]interface{} {
// It skips includes and hard fails excludes for non-existing keys // It skips includes and hard fails excludes for non-existing keys
// //
//nolint:gocyclo //nolint:gocyclo
func (j *Job) GetMatrixes() []map[string]interface{} { func (j *Job) GetMatrixes() ([]map[string]interface{}, error) {
matrixes := make([]map[string]interface{}, 0) matrixes := make([]map[string]interface{}, 0)
if j.Strategy != nil { if j.Strategy != nil {
j.Strategy.FailFast = j.Strategy.GetFailFast() j.Strategy.FailFast = j.Strategy.GetFailFast()
@@ -337,7 +397,7 @@ func (j *Job) GetMatrixes() []map[string]interface{} {
excludes = append(excludes, e) excludes = append(excludes, e)
} else { } else {
// We fail completely here because that's what GitHub does for non-existing matrix keys, fail on exclude, silent skip on include // We fail completely here because that's what GitHub does for non-existing matrix keys, fail on exclude, silent skip on include
log.Fatalf("The workflow is not valid. Matrix exclude key '%s' does not match any key within the matrix", k) return nil, fmt.Errorf("the workflow is not valid. Matrix exclude key %q does not match any key within the matrix", k)
} }
} }
} }
@@ -381,8 +441,9 @@ func (j *Job) GetMatrixes() []map[string]interface{} {
} }
} else { } else {
matrixes = append(matrixes, make(map[string]interface{})) matrixes = append(matrixes, make(map[string]interface{}))
log.Debugf("Empty Strategy, matrixes=%v", matrixes)
} }
return matrixes return matrixes, nil
} }
func commonKeysMatch(a map[string]interface{}, b map[string]interface{}) bool { func commonKeysMatch(a map[string]interface{}, b map[string]interface{}) bool {
@@ -408,14 +469,17 @@ func commonKeysMatch2(a map[string]interface{}, b map[string]interface{}, m map[
type JobType int type JobType int
const ( const (
// StepTypeRun is all steps that have a `run` attribute // JobTypeDefault is all jobs that have a `run` attribute
JobTypeDefault JobType = iota JobTypeDefault JobType = iota
// StepTypeReusableWorkflowLocal is all steps that have a `uses` that is a local workflow in the .github/workflows directory // JobTypeReusableWorkflowLocal is all jobs that have a `uses` that is a local workflow in the .github/workflows directory
JobTypeReusableWorkflowLocal JobTypeReusableWorkflowLocal
// JobTypeReusableWorkflowRemote is all steps that have a `uses` that references a workflow file in a github repo // JobTypeReusableWorkflowRemote is all jobs that have a `uses` that references a workflow file in a github repo
JobTypeReusableWorkflowRemote JobTypeReusableWorkflowRemote
// JobTypeInvalid represents a job which is not configured correctly
JobTypeInvalid
) )
func (j JobType) String() string { func (j JobType) String() string {
@@ -431,13 +495,28 @@ func (j JobType) String() string {
} }
// Type returns the type of the job // Type returns the type of the job
func (j *Job) Type() JobType { func (j *Job) Type() (JobType, error) {
if strings.HasPrefix(j.Uses, "./.github/workflows") && (strings.HasSuffix(j.Uses, ".yml") || strings.HasSuffix(j.Uses, ".yaml")) { isReusable := j.Uses != ""
return JobTypeReusableWorkflowLocal
} else if !strings.HasPrefix(j.Uses, "./") && strings.Contains(j.Uses, ".github/workflows") && (strings.Contains(j.Uses, ".yml@") || strings.Contains(j.Uses, ".yaml@")) { if isReusable {
return JobTypeReusableWorkflowRemote isYaml, _ := regexp.MatchString(`\.(ya?ml)(?:$|@)`, j.Uses)
if isYaml {
isLocalPath := strings.HasPrefix(j.Uses, "./")
isRemotePath, _ := regexp.MatchString(`^[^.](.+?/){2,}.+\.ya?ml@`, j.Uses)
hasVersion, _ := regexp.MatchString(`\.ya?ml@`, j.Uses)
if isLocalPath {
return JobTypeReusableWorkflowLocal, nil
} else if isRemotePath && hasVersion {
return JobTypeReusableWorkflowRemote, nil
}
}
return JobTypeInvalid, fmt.Errorf("`uses` key references invalid workflow path '%s'. Must start with './' if it's a local workflow, or must start with '<org>/<repo>/' and include an '@' if it's a remote workflow", j.Uses)
} }
return JobTypeDefault
return JobTypeDefault, nil
} }
// ContainerSpec is the specification of the container to use for the job // ContainerSpec is the specification of the container to use for the job
@@ -456,7 +535,6 @@ type ContainerSpec struct {
// Step is the structure of one step in a job // Step is the structure of one step in a job
type Step struct { type Step struct {
Number int `yaml:"-"`
ID string `yaml:"id"` ID string `yaml:"id"`
If yaml.Node `yaml:"if"` If yaml.Node `yaml:"if"`
Name string `yaml:"name"` Name string `yaml:"name"`
@@ -483,16 +561,8 @@ func (s *Step) String() string {
} }
// Environments returns string-based key=value map for a step // Environments returns string-based key=value map for a step
// Note: all keys are uppercase
func (s *Step) Environment() map[string]string { func (s *Step) Environment() map[string]string {
env := environment(s.Env) return environment(s.Env)
for k, v := range env {
delete(env, k)
env[strings.ToUpper(k)] = v
}
return env
} }
// GetEnv gets the env for a step // GetEnv gets the env for a step
@@ -511,7 +581,7 @@ func (s *Step) GetEnv() map[string]string {
func (s *Step) ShellCommand() string { func (s *Step) ShellCommand() string {
shellCommand := "" shellCommand := ""
// Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L9-L17 //Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L9-L17
switch s.Shell { switch s.Shell {
case "", "bash": case "", "bash":
shellCommand = "bash --noprofile --norc -e -o pipefail {0}" shellCommand = "bash --noprofile --norc -e -o pipefail {0}"
@@ -522,7 +592,7 @@ func (s *Step) ShellCommand() string {
case "sh": case "sh":
shellCommand = "sh -e {0}" shellCommand = "sh -e {0}"
case "cmd": case "cmd":
shellCommand = "%ComSpec% /D /E:ON /V:OFF /S /C \"CALL \"{0}\"\"" shellCommand = "cmd /D /E:ON /V:OFF /S /C \"CALL \"{0}\"\""
case "powershell": case "powershell":
shellCommand = "powershell -command . '{0}'" shellCommand = "powershell -command . '{0}'"
default: default:
@@ -631,3 +701,17 @@ func (w *Workflow) GetJobIDs() []string {
} }
return ids return ids
} }
var OnDecodeNodeError = func(node yaml.Node, out interface{}, err error) {
log.Fatalf("Failed to decode node %v into %T: %v", node, out, err)
}
func decodeNode(node yaml.Node, out interface{}) bool {
if err := node.Decode(out); err != nil {
if OnDecodeNodeError != nil {
OnDecodeNodeError(node, out, err)
}
return false
}
return true
}

View File

@@ -7,88 +7,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestReadWorkflow_ScheduleEvent(t *testing.T) {
yaml := `
name: local-action-docker-url
on:
schedule:
- cron: '30 5 * * 1,3'
- cron: '30 5 * * 2,4'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-url
`
workflow, err := ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
schedules := workflow.OnEvent("schedule")
assert.Len(t, schedules, 2)
newSchedules := workflow.OnSchedule()
assert.Len(t, newSchedules, 2)
assert.Equal(t, "30 5 * * 1,3", newSchedules[0])
assert.Equal(t, "30 5 * * 2,4", newSchedules[1])
yaml = `
name: local-action-docker-url
on:
schedule:
test: '30 5 * * 1,3'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-url
`
workflow, err = ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
newSchedules = workflow.OnSchedule()
assert.Len(t, newSchedules, 0)
yaml = `
name: local-action-docker-url
on:
schedule:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-url
`
workflow, err = ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
newSchedules = workflow.OnSchedule()
assert.Len(t, newSchedules, 0)
yaml = `
name: local-action-docker-url
on: [push, tag]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: ./actions/docker-url
`
workflow, err = ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
newSchedules = workflow.OnSchedule()
assert.Len(t, newSchedules, 0)
}
func TestReadWorkflow_StringEvent(t *testing.T) { func TestReadWorkflow_StringEvent(t *testing.T) {
yaml := ` yaml := `
name: local-action-docker-url name: local-action-docker-url
@@ -153,6 +71,41 @@ jobs:
assert.Contains(t, workflow.On(), "pull_request") assert.Contains(t, workflow.On(), "pull_request")
} }
func TestReadWorkflow_RunsOnLabels(t *testing.T) {
yaml := `
name: local-action-docker-url
jobs:
test:
container: nginx:latest
runs-on:
labels: ubuntu-latest
steps:
- uses: ./actions/docker-url`
workflow, err := ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
assert.Equal(t, workflow.Jobs["test"].RunsOn(), []string{"ubuntu-latest"})
}
func TestReadWorkflow_RunsOnLabelsWithGroup(t *testing.T) {
yaml := `
name: local-action-docker-url
jobs:
test:
container: nginx:latest
runs-on:
labels: [ubuntu-latest]
group: linux
steps:
- uses: ./actions/docker-url`
workflow, err := ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
assert.Equal(t, workflow.Jobs["test"].RunsOn(), []string{"ubuntu-latest", "linux"})
}
func TestReadWorkflow_StringContainer(t *testing.T) { func TestReadWorkflow_StringContainer(t *testing.T) {
yaml := ` yaml := `
name: local-action-docker-url name: local-action-docker-url
@@ -229,20 +182,81 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo - run: echo
remote-reusable-workflow: remote-reusable-workflow-yml:
runs-on: ubuntu-latest uses: remote/repo/some/path/to/workflow.yml@main
uses: remote/repo/.github/workflows/workflow.yml@main remote-reusable-workflow-yaml:
local-reusable-workflow: uses: remote/repo/some/path/to/workflow.yaml@main
runs-on: ubuntu-latest remote-reusable-workflow-custom-path:
uses: ./.github/workflows/workflow.yml uses: remote/repo/path/to/workflow.yml@main
local-reusable-workflow-yml:
uses: ./some/path/to/workflow.yml
local-reusable-workflow-yaml:
uses: ./some/path/to/workflow.yaml
` `
workflow, err := ReadWorkflow(strings.NewReader(yaml)) workflow, err := ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed") assert.NoError(t, err, "read workflow should succeed")
assert.Len(t, workflow.Jobs, 3) assert.Len(t, workflow.Jobs, 6)
assert.Equal(t, workflow.Jobs["default-job"].Type(), JobTypeDefault)
assert.Equal(t, workflow.Jobs["remote-reusable-workflow"].Type(), JobTypeReusableWorkflowRemote) jobType, err := workflow.Jobs["default-job"].Type()
assert.Equal(t, workflow.Jobs["local-reusable-workflow"].Type(), JobTypeReusableWorkflowLocal) assert.Equal(t, nil, err)
assert.Equal(t, JobTypeDefault, jobType)
jobType, err = workflow.Jobs["remote-reusable-workflow-yml"].Type()
assert.Equal(t, nil, err)
assert.Equal(t, JobTypeReusableWorkflowRemote, jobType)
jobType, err = workflow.Jobs["remote-reusable-workflow-yaml"].Type()
assert.Equal(t, nil, err)
assert.Equal(t, JobTypeReusableWorkflowRemote, jobType)
jobType, err = workflow.Jobs["remote-reusable-workflow-custom-path"].Type()
assert.Equal(t, nil, err)
assert.Equal(t, JobTypeReusableWorkflowRemote, jobType)
jobType, err = workflow.Jobs["local-reusable-workflow-yml"].Type()
assert.Equal(t, nil, err)
assert.Equal(t, JobTypeReusableWorkflowLocal, jobType)
jobType, err = workflow.Jobs["local-reusable-workflow-yaml"].Type()
assert.Equal(t, nil, err)
assert.Equal(t, JobTypeReusableWorkflowLocal, jobType)
}
func TestReadWorkflow_JobTypes_InvalidPath(t *testing.T) {
yaml := `
name: invalid job definition
jobs:
remote-reusable-workflow-missing-version:
uses: remote/repo/some/path/to/workflow.yml
remote-reusable-workflow-bad-extension:
uses: remote/repo/some/path/to/workflow.json
local-reusable-workflow-bad-extension:
uses: ./some/path/to/workflow.json
local-reusable-workflow-bad-path:
uses: some/path/to/workflow.yaml
`
workflow, err := ReadWorkflow(strings.NewReader(yaml))
assert.NoError(t, err, "read workflow should succeed")
assert.Len(t, workflow.Jobs, 4)
jobType, err := workflow.Jobs["remote-reusable-workflow-missing-version"].Type()
assert.Equal(t, JobTypeInvalid, jobType)
assert.NotEqual(t, nil, err)
jobType, err = workflow.Jobs["remote-reusable-workflow-bad-extension"].Type()
assert.Equal(t, JobTypeInvalid, jobType)
assert.NotEqual(t, nil, err)
jobType, err = workflow.Jobs["local-reusable-workflow-bad-extension"].Type()
assert.Equal(t, JobTypeInvalid, jobType)
assert.NotEqual(t, nil, err)
jobType, err = workflow.Jobs["local-reusable-workflow-bad-path"].Type()
assert.Equal(t, JobTypeInvalid, jobType)
assert.NotEqual(t, nil, err)
} }
func TestReadWorkflow_StepsTypes(t *testing.T) { func TestReadWorkflow_StepsTypes(t *testing.T) {
@@ -323,7 +337,8 @@ func TestReadWorkflow_Strategy(t *testing.T) {
w, err := NewWorkflowPlanner("testdata/strategy/push.yml", true) w, err := NewWorkflowPlanner("testdata/strategy/push.yml", true)
assert.NoError(t, err) assert.NoError(t, err)
p := w.PlanJob("strategy-only-max-parallel") p, err := w.PlanJob("strategy-only-max-parallel")
assert.NoError(t, err)
assert.Equal(t, len(p.Stages), 1) assert.Equal(t, len(p.Stages), 1)
assert.Equal(t, len(p.Stages[0].Runs), 1) assert.Equal(t, len(p.Stages[0].Runs), 1)
@@ -331,25 +346,33 @@ func TestReadWorkflow_Strategy(t *testing.T) {
wf := p.Stages[0].Runs[0].Workflow wf := p.Stages[0].Runs[0].Workflow
job := wf.Jobs["strategy-only-max-parallel"] job := wf.Jobs["strategy-only-max-parallel"]
assert.Equal(t, job.GetMatrixes(), []map[string]interface{}{{}}) matrixes, err := job.GetMatrixes()
assert.NoError(t, err)
assert.Equal(t, matrixes, []map[string]interface{}{{}})
assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) assert.Equal(t, job.Matrix(), map[string][]interface{}(nil))
assert.Equal(t, job.Strategy.MaxParallel, 2) assert.Equal(t, job.Strategy.MaxParallel, 2)
assert.Equal(t, job.Strategy.FailFast, true) assert.Equal(t, job.Strategy.FailFast, true)
job = wf.Jobs["strategy-only-fail-fast"] job = wf.Jobs["strategy-only-fail-fast"]
assert.Equal(t, job.GetMatrixes(), []map[string]interface{}{{}}) matrixes, err = job.GetMatrixes()
assert.NoError(t, err)
assert.Equal(t, matrixes, []map[string]interface{}{{}})
assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) assert.Equal(t, job.Matrix(), map[string][]interface{}(nil))
assert.Equal(t, job.Strategy.MaxParallel, 4) assert.Equal(t, job.Strategy.MaxParallel, 4)
assert.Equal(t, job.Strategy.FailFast, false) assert.Equal(t, job.Strategy.FailFast, false)
job = wf.Jobs["strategy-no-matrix"] job = wf.Jobs["strategy-no-matrix"]
assert.Equal(t, job.GetMatrixes(), []map[string]interface{}{{}}) matrixes, err = job.GetMatrixes()
assert.NoError(t, err)
assert.Equal(t, matrixes, []map[string]interface{}{{}})
assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) assert.Equal(t, job.Matrix(), map[string][]interface{}(nil))
assert.Equal(t, job.Strategy.MaxParallel, 2) assert.Equal(t, job.Strategy.MaxParallel, 2)
assert.Equal(t, job.Strategy.FailFast, false) assert.Equal(t, job.Strategy.FailFast, false)
job = wf.Jobs["strategy-all"] job = wf.Jobs["strategy-all"]
assert.Equal(t, job.GetMatrixes(), matrixes, err = job.GetMatrixes()
assert.NoError(t, err)
assert.Equal(t, matrixes,
[]map[string]interface{}{ []map[string]interface{}{
{"datacenter": "site-c", "node-version": "14.x", "site": "staging"}, {"datacenter": "site-c", "node-version": "14.x", "site": "staging"},
{"datacenter": "site-c", "node-version": "16.x", "site": "staging"}, {"datacenter": "site-c", "node-version": "16.x", "site": "staging"},

View File

@@ -14,6 +14,7 @@ import (
"strings" "strings"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
@@ -29,10 +30,9 @@ type actionStep interface {
type readAction func(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) type readAction func(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error)
type ( type actionYamlReader func(filename string) (io.Reader, io.Closer, error)
actionYamlReader func(filename string) (io.Reader, io.Closer, error)
fileWriter func(filename string, data []byte, perm fs.FileMode) error type fileWriter func(filename string, data []byte, perm fs.FileMode) error
)
type runAction func(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor type runAction func(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor
@@ -149,13 +149,15 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
logger.Debugf("type=%v actionDir=%s actionPath=%s workdir=%s actionCacheDir=%s actionName=%s containerActionDir=%s", stepModel.Type(), actionDir, actionPath, rc.Config.Workdir, rc.ActionCacheDir(), actionName, containerActionDir) logger.Debugf("type=%v actionDir=%s actionPath=%s workdir=%s actionCacheDir=%s actionName=%s containerActionDir=%s", stepModel.Type(), actionDir, actionPath, rc.Config.Workdir, rc.ActionCacheDir(), actionName, containerActionDir)
switch action.Runs.Using { switch action.Runs.Using {
case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16: case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20:
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil { if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
return err return err
} }
containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Main)} containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Main)}
logger.Debugf("executing remote job container: %s", containerArgs) logger.Debugf("executing remote job container: %s", containerArgs)
rc.ApplyExtraPath(ctx, step.getEnv())
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
case model.ActionRunsUsingDocker: case model.ActionRunsUsingDocker:
location := actionLocation location := actionLocation
@@ -169,25 +171,19 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
} }
return execAsComposite(step)(ctx) return execAsComposite(step)(ctx)
case model.ActionRunsUsingGo:
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
return err
}
containerArgs := []string{"go", "run", path.Join(containerActionDir, action.Runs.Main)}
logger.Debugf("executing remote job container: %s", containerArgs)
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
default: default:
return fmt.Errorf(fmt.Sprintf("The runs.using key must be one of: %v, got %s", []string{ return fmt.Errorf(fmt.Sprintf("The runs.using key must be one of: %v, got %s", []string{
model.ActionRunsUsingDocker, model.ActionRunsUsingDocker,
model.ActionRunsUsingNode12, model.ActionRunsUsingNode12,
model.ActionRunsUsingNode16, model.ActionRunsUsingNode16,
model.ActionRunsUsingNode20,
model.ActionRunsUsingComposite, model.ActionRunsUsingComposite,
}, action.Runs.Using)) }, action.Runs.Using))
} }
} }
} }
func setupActionEnv(ctx context.Context, step actionStep, remoteAction *remoteAction) error { func setupActionEnv(ctx context.Context, step actionStep, _ *remoteAction) error {
rc := step.getRunContext() rc := step.getRunContext()
// A few fields in the environment (e.g. GITHUB_ACTION_REPOSITORY) // A few fields in the environment (e.g. GITHUB_ACTION_REPOSITORY)
@@ -228,14 +224,17 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
var prepImage common.Executor var prepImage common.Executor
var image string var image string
forcePull := false
if strings.HasPrefix(action.Runs.Image, "docker://") { if strings.HasPrefix(action.Runs.Image, "docker://") {
image = strings.TrimPrefix(action.Runs.Image, "docker://") image = strings.TrimPrefix(action.Runs.Image, "docker://")
// Apply forcePull only for prebuild docker images
forcePull = rc.Config.ForcePull
} else { } else {
// "-dockeraction" enshures that "./", "./test " won't get converted to "act-:latest", "act-test-:latest" which are invalid docker image names // "-dockeraction" enshures that "./", "./test " won't get converted to "act-:latest", "act-test-:latest" which are invalid docker image names
image = fmt.Sprintf("%s-dockeraction:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest") image = fmt.Sprintf("%s-dockeraction:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-")) image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
image = strings.ToLower(image) image = strings.ToLower(image)
contextDir := filepath.Join(basedir, action.Runs.Main) contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image))
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any") anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
if err != nil { if err != nil {
@@ -259,15 +258,20 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
if !correctArchExists || rc.Config.ForceRebuild { if !correctArchExists || rc.Config.ForceRebuild {
logger.Debugf("image '%s' for architecture '%s' will be built from context '%s", image, rc.Config.ContainerArchitecture, contextDir) logger.Debugf("image '%s' for architecture '%s' will be built from context '%s", image, rc.Config.ContainerArchitecture, contextDir)
var actionContainer container.Container var buildContext io.ReadCloser
if localAction { if localAction {
actionContainer = rc.JobContainer buildContext, err = rc.JobContainer.GetContainerArchive(ctx, contextDir+"/.")
if err != nil {
return err
}
defer buildContext.Close()
} }
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{ prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
ContextDir: contextDir, ContextDir: contextDir,
ImageTag: image, Dockerfile: fileName,
Container: actionContainer, ImageTag: image,
Platform: rc.Config.ContainerArchitecture, BuildContext: buildContext,
Platform: rc.Config.ContainerArchitecture,
}) })
} else { } else {
logger.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture) logger.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
@@ -296,7 +300,7 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
stepContainer := newStepContainer(ctx, step, image, cmd, entrypoint) stepContainer := newStepContainer(ctx, step, image, cmd, entrypoint)
return common.NewPipelineExecutor( return common.NewPipelineExecutor(
prepImage, prepImage,
stepContainer.Pull(rc.Config.ForcePull), stepContainer.Pull(forcePull),
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers), stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
stepContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop), stepContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
stepContainer.Start(true), stepContainer.Start(true),
@@ -320,13 +324,13 @@ func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[
inputs[k] = eval.Interpolate(ctx, v) inputs[k] = eval.Interpolate(ctx, v)
} }
} }
mergeIntoMap(step.getEnv(), inputs) mergeIntoMap(step, step.getEnv(), inputs)
stepEE := rc.NewStepExpressionEvaluator(ctx, step) stepEE := rc.NewStepExpressionEvaluator(ctx, step)
for i, v := range *cmd { for i, v := range *cmd {
(*cmd)[i] = stepEE.Interpolate(ctx, v) (*cmd)[i] = stepEE.Interpolate(ctx, v)
} }
mergeIntoMap(step.getEnv(), action.Runs.Env) mergeIntoMap(step, step.getEnv(), action.Runs.Env)
ee := rc.NewStepExpressionEvaluator(ctx, step) ee := rc.NewStepExpressionEvaluator(ctx, step)
for k, v := range *step.getEnv() { for k, v := range *step.getEnv() {
@@ -357,7 +361,10 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp")) envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp"))
binds, mounts := rc.GetBindsAndMounts() binds, mounts := rc.GetBindsAndMounts()
networkMode := fmt.Sprintf("container:%s", rc.jobContainerName())
if rc.IsHostEnv(ctx) {
networkMode = "default"
}
stepContainer := container.NewContainer(&container.NewContainerInput{ stepContainer := container.NewContainer(&container.NewContainerInput{
Cmd: cmd, Cmd: cmd,
Entrypoint: entrypoint, Entrypoint: entrypoint,
@@ -365,25 +372,25 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
Image: image, Image: image,
Username: rc.Config.Secrets["DOCKER_USERNAME"], Username: rc.Config.Secrets["DOCKER_USERNAME"],
Password: rc.Config.Secrets["DOCKER_PASSWORD"], Password: rc.Config.Secrets["DOCKER_PASSWORD"],
Name: createSimpleContainerName(rc.jobContainerName(), "STEP-"+stepModel.ID), Name: createContainerName(rc.jobContainerName(), stepModel.ID),
Env: envList, Env: envList,
Mounts: mounts, Mounts: mounts,
NetworkMode: fmt.Sprintf("container:%s", rc.jobContainerName()), NetworkMode: networkMode,
Binds: binds, Binds: binds,
Stdout: logWriter, Stdout: logWriter,
Stderr: logWriter, Stderr: logWriter,
Privileged: rc.Config.Privileged, Privileged: rc.Config.Privileged,
UsernsMode: rc.Config.UsernsMode, UsernsMode: rc.Config.UsernsMode,
Platform: rc.Config.ContainerArchitecture, Platform: rc.Config.ContainerArchitecture,
AutoRemove: rc.Config.AutoRemove, Options: rc.Config.ContainerOptions,
}) })
return stepContainer return stepContainer
} }
func populateEnvsFromSavedState(env *map[string]string, step actionStep, rc *RunContext) { func populateEnvsFromSavedState(env *map[string]string, step actionStep, rc *RunContext) {
stepResult := rc.StepResults[step.getStepModel().ID] state, ok := rc.IntraActionState[step.getStepModel().ID]
if stepResult != nil { if ok {
for name, value := range stepResult.State { for name, value := range state {
envName := fmt.Sprintf("STATE_%s", name) envName := fmt.Sprintf("STATE_%s", name)
(*env)[envName] = value (*env)[envName] = value
} }
@@ -450,7 +457,8 @@ func hasPreStep(step actionStep) common.Conditional {
action := step.getActionModel() action := step.getActionModel()
return action.Runs.Using == model.ActionRunsUsingComposite || return action.Runs.Using == model.ActionRunsUsingComposite ||
((action.Runs.Using == model.ActionRunsUsingNode12 || ((action.Runs.Using == model.ActionRunsUsingNode12 ||
action.Runs.Using == model.ActionRunsUsingNode16) && action.Runs.Using == model.ActionRunsUsingNode16 ||
action.Runs.Using == model.ActionRunsUsingNode20) &&
action.Runs.Pre != "") action.Runs.Pre != "")
} }
} }
@@ -465,7 +473,7 @@ func runPreStep(step actionStep) common.Executor {
action := step.getActionModel() action := step.getActionModel()
switch action.Runs.Using { switch action.Runs.Using {
case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16: case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20:
// defaults in pre steps were missing, however provided inputs are available // defaults in pre steps were missing, however provided inputs are available
populateEnvsFromInput(ctx, step.getEnv(), action, rc) populateEnvsFromInput(ctx, step.getEnv(), action, rc)
// todo: refactor into step // todo: refactor into step
@@ -473,7 +481,7 @@ func runPreStep(step actionStep) common.Executor {
var actionPath string var actionPath string
if _, ok := step.(*stepActionRemote); ok { if _, ok := step.(*stepActionRemote); ok {
actionPath = newRemoteAction(stepModel.Uses).Path actionPath = newRemoteAction(stepModel.Uses).Path
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), strings.ReplaceAll(stepModel.Uses, "/", "-")) actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(stepModel.Uses))
} else { } else {
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses) actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
actionPath = "" actionPath = ""
@@ -495,6 +503,8 @@ func runPreStep(step actionStep) common.Executor {
containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Pre)} containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Pre)}
logger.Debugf("executing remote job container: %s", containerArgs) logger.Debugf("executing remote job container: %s", containerArgs)
rc.ApplyExtraPath(ctx, step.getEnv())
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
case model.ActionRunsUsingComposite: case model.ActionRunsUsingComposite:
@@ -502,7 +512,10 @@ func runPreStep(step actionStep) common.Executor {
step.getCompositeRunContext(ctx) step.getCompositeRunContext(ctx)
} }
return step.getCompositeSteps().pre(ctx) if steps := step.getCompositeSteps(); steps != nil && steps.pre != nil {
return steps.pre(ctx)
}
return fmt.Errorf("missing steps in composite action")
default: default:
return nil return nil
@@ -540,7 +553,8 @@ func hasPostStep(step actionStep) common.Conditional {
action := step.getActionModel() action := step.getActionModel()
return action.Runs.Using == model.ActionRunsUsingComposite || return action.Runs.Using == model.ActionRunsUsingComposite ||
((action.Runs.Using == model.ActionRunsUsingNode12 || ((action.Runs.Using == model.ActionRunsUsingNode12 ||
action.Runs.Using == model.ActionRunsUsingNode16) && action.Runs.Using == model.ActionRunsUsingNode16 ||
action.Runs.Using == model.ActionRunsUsingNode20) &&
action.Runs.Post != "") action.Runs.Post != "")
} }
} }
@@ -559,7 +573,7 @@ func runPostStep(step actionStep) common.Executor {
var actionPath string var actionPath string
if _, ok := step.(*stepActionRemote); ok { if _, ok := step.(*stepActionRemote); ok {
actionPath = newRemoteAction(stepModel.Uses).Path actionPath = newRemoteAction(stepModel.Uses).Path
actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), strings.ReplaceAll(stepModel.Uses, "/", "-")) actionDir = fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(stepModel.Uses))
} else { } else {
actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses) actionDir = filepath.Join(rc.Config.Workdir, stepModel.Uses)
actionPath = "" actionPath = ""
@@ -575,13 +589,15 @@ func runPostStep(step actionStep) common.Executor {
_, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc) _, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc)
switch action.Runs.Using { switch action.Runs.Using {
case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16: case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20:
populateEnvsFromSavedState(step.getEnv(), step, rc) populateEnvsFromSavedState(step.getEnv(), step, rc)
containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Post)} containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Post)}
logger.Debugf("executing remote job container: %s", containerArgs) logger.Debugf("executing remote job container: %s", containerArgs)
rc.ApplyExtraPath(ctx, step.getEnv())
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
case model.ActionRunsUsingComposite: case model.ActionRunsUsingComposite:
@@ -589,7 +605,10 @@ func runPostStep(step actionStep) common.Executor {
return err return err
} }
return step.getCompositeSteps().post(ctx) if steps := step.getCompositeSteps(); steps != nil && steps.post != nil {
return steps.post(ctx)
}
return fmt.Errorf("missing steps in composite action")
default: default:
return nil return nil

177
pkg/runner/action_cache.go Normal file
View File

@@ -0,0 +1,177 @@
package runner
import (
"archive/tar"
"context"
"crypto/rand"
"encoding/hex"
"errors"
"io"
"io/fs"
"path"
"strings"
git "github.com/go-git/go-git/v5"
config "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
type ActionCache interface {
Fetch(ctx context.Context, cacheDir, url, ref, token string) (string, error)
GetTarArchive(ctx context.Context, cacheDir, sha, includePrefix string) (io.ReadCloser, error)
}
type GoGitActionCache struct {
Path string
}
func (c GoGitActionCache) Fetch(ctx context.Context, cacheDir, url, ref, token string) (string, error) {
gitPath := path.Join(c.Path, safeFilename(cacheDir)+".git")
gogitrepo, err := git.PlainInit(gitPath, true)
if errors.Is(err, git.ErrRepositoryAlreadyExists) {
gogitrepo, err = git.PlainOpen(gitPath)
}
if err != nil {
return "", err
}
tmpBranch := make([]byte, 12)
if _, err := rand.Read(tmpBranch); err != nil {
return "", err
}
branchName := hex.EncodeToString(tmpBranch)
var refSpec config.RefSpec
spec := config.RefSpec(ref + ":" + branchName)
tagOrSha := false
if spec.IsExactSHA1() {
refSpec = spec
} else if strings.HasPrefix(ref, "refs/") {
refSpec = config.RefSpec(ref + ":refs/heads/" + branchName)
} else {
tagOrSha = true
refSpec = config.RefSpec("refs/*/" + ref + ":refs/heads/*/" + branchName)
}
var auth transport.AuthMethod
if token != "" {
auth = &http.BasicAuth{
Username: "token",
Password: token,
}
}
remote, err := gogitrepo.CreateRemoteAnonymous(&config.RemoteConfig{
Name: "anonymous",
URLs: []string{
url,
},
})
if err != nil {
return "", err
}
defer func() {
if refs, err := gogitrepo.References(); err == nil {
_ = refs.ForEach(func(r *plumbing.Reference) error {
if strings.Contains(r.Name().String(), branchName) {
return gogitrepo.DeleteBranch(r.Name().String())
}
return nil
})
}
}()
if err := remote.FetchContext(ctx, &git.FetchOptions{
RefSpecs: []config.RefSpec{
refSpec,
},
Auth: auth,
Force: true,
}); err != nil {
return "", err
}
if tagOrSha {
for _, prefix := range []string{"refs/heads/tags/", "refs/heads/heads/"} {
hash, err := gogitrepo.ResolveRevision(plumbing.Revision(prefix + branchName))
if err == nil {
return hash.String(), nil
}
}
}
hash, err := gogitrepo.ResolveRevision(plumbing.Revision(branchName))
if err != nil {
return "", err
}
return hash.String(), nil
}
func (c GoGitActionCache) GetTarArchive(ctx context.Context, cacheDir, sha, includePrefix string) (io.ReadCloser, error) {
gitPath := path.Join(c.Path, safeFilename(cacheDir)+".git")
gogitrepo, err := git.PlainOpen(gitPath)
if err != nil {
return nil, err
}
commit, err := gogitrepo.CommitObject(plumbing.NewHash(sha))
if err != nil {
return nil, err
}
files, err := commit.Files()
if err != nil {
return nil, err
}
rpipe, wpipe := io.Pipe()
// Interrupt io.Copy using ctx
ch := make(chan int, 1)
go func() {
select {
case <-ctx.Done():
wpipe.CloseWithError(ctx.Err())
case <-ch:
}
}()
go func() {
defer wpipe.Close()
defer close(ch)
tw := tar.NewWriter(wpipe)
cleanIncludePrefix := path.Clean(includePrefix)
wpipe.CloseWithError(files.ForEach(func(f *object.File) error {
if err := ctx.Err(); err != nil {
return err
}
name := f.Name
if strings.HasPrefix(name, cleanIncludePrefix+"/") {
name = name[len(cleanIncludePrefix)+1:]
} else if cleanIncludePrefix != "." && name != cleanIncludePrefix {
return nil
}
fmode, err := f.Mode.ToOSFileMode()
if err != nil {
return err
}
if fmode&fs.ModeSymlink == fs.ModeSymlink {
content, err := f.Contents()
if err != nil {
return err
}
return tw.WriteHeader(&tar.Header{
Name: name,
Mode: int64(fmode),
Linkname: content,
})
}
err = tw.WriteHeader(&tar.Header{
Name: name,
Mode: int64(fmode),
Size: f.Size,
})
if err != nil {
return err
}
reader, err := f.Reader()
if err != nil {
return err
}
_, err = io.Copy(tw, reader)
return err
}))
}()
return rpipe, err
}

View File

@@ -0,0 +1,37 @@
package runner
import (
"archive/tar"
"bytes"
"context"
"io"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
//nolint:gosec
func TestActionCache(t *testing.T) {
a := assert.New(t)
cache := &GoGitActionCache{
Path: os.TempDir(),
}
ctx := context.Background()
sha, err := cache.Fetch(ctx, "christopherhx/script", "https://github.com/christopherhx/script", "main", "")
a.NoError(err)
a.NotEmpty(sha)
atar, err := cache.GetTarArchive(ctx, "christopherhx/script", sha, "node_modules")
a.NoError(err)
a.NotEmpty(atar)
mytar := tar.NewReader(atar)
th, err := mytar.Next()
a.NoError(err)
a.NotEqual(0, th.Size)
buf := &bytes.Buffer{}
// G110: Potential DoS vulnerability via decompression bomb (gosec)
_, err = io.Copy(buf, mytar)
a.NoError(err)
str := buf.String()
a.NotEmpty(str)
}

View File

@@ -37,6 +37,9 @@ func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step
env[envKey] = ee.Interpolate(ctx, input.Default) env[envKey] = ee.Interpolate(ctx, input.Default)
} }
} }
gh := step.getGithubContext(ctx)
env["GITHUB_ACTION_REPOSITORY"] = gh.ActionRepository
env["GITHUB_ACTION_REF"] = gh.ActionRef
return env return env
} }
@@ -53,11 +56,11 @@ func newCompositeRunContext(ctx context.Context, parent *RunContext, step action
Name: parent.Name, Name: parent.Name,
JobName: parent.JobName, JobName: parent.JobName,
Run: &model.Run{ Run: &model.Run{
JobID: "composite-job", JobID: parent.Run.JobID,
Workflow: &model.Workflow{ Workflow: &model.Workflow{
Name: parent.Run.Workflow.Name, Name: parent.Run.Workflow.Name,
Jobs: map[string]*model.Job{ Jobs: map[string]*model.Job{
"composite-job": {}, parent.Run.JobID: {},
}, },
}, },
}, },
@@ -66,6 +69,7 @@ func newCompositeRunContext(ctx context.Context, parent *RunContext, step action
JobContainer: parent.JobContainer, JobContainer: parent.JobContainer,
ActionPath: actionPath, ActionPath: actionPath,
Env: env, Env: env,
GlobalEnv: parent.GlobalEnv,
Masks: parent.Masks, Masks: parent.Masks,
ExtraPath: parent.ExtraPath, ExtraPath: parent.ExtraPath,
Parent: parent, Parent: parent,
@@ -85,6 +89,10 @@ func execAsComposite(step actionStep) common.Executor {
steps := step.getCompositeSteps() steps := step.getCompositeSteps()
if steps == nil || steps.main == nil {
return fmt.Errorf("missing steps in composite action")
}
ctx = WithCompositeLogger(ctx, &compositeRC.Masks) ctx = WithCompositeLogger(ctx, &compositeRC.Masks)
err := steps.main(ctx) err := steps.main(ctx)
@@ -99,6 +107,16 @@ func execAsComposite(step actionStep) common.Executor {
rc.Masks = append(rc.Masks, compositeRC.Masks...) rc.Masks = append(rc.Masks, compositeRC.Masks...)
rc.ExtraPath = compositeRC.ExtraPath rc.ExtraPath = compositeRC.ExtraPath
// compositeRC.Env is dirty, contains INPUT_ and merged step env, only rely on compositeRC.GlobalEnv
mergeIntoMap := mergeIntoMapCaseSensitive
if rc.JobContainer.IsEnvironmentCaseInsensitive() {
mergeIntoMap = mergeIntoMapCaseInsensitive
}
if rc.GlobalEnv == nil {
rc.GlobalEnv = map[string]string{}
}
mergeIntoMap(rc.GlobalEnv, compositeRC.GlobalEnv)
mergeIntoMap(rc.Env, compositeRC.GlobalEnv)
return err return err
} }
@@ -122,7 +140,6 @@ func (rc *RunContext) compositeExecutor(action *model.Action) *compositeSteps {
if step.ID == "" { if step.ID == "" {
step.ID = fmt.Sprintf("%d", i) step.ID = fmt.Sprintf("%d", i)
} }
step.Number = i
// create a copy of the step, since this composite action could // create a copy of the step, since this composite action could
// run multiple times and we might modify the instance // run multiple times and we might modify the instance

View File

@@ -201,10 +201,11 @@ func TestActionRunner(t *testing.T) {
}, },
CurrentStep: "post-step", CurrentStep: "post-step",
StepResults: map[string]*model.StepResult{ StepResults: map[string]*model.StepResult{
"step": {},
},
IntraActionState: map[string]map[string]string{
"step": { "step": {
State: map[string]string{ "name": "state value",
"name": "state value",
},
}, },
}, },
}, },

74
pkg/runner/command.go Executable file → Normal file
View File

@@ -16,22 +16,27 @@ func init() {
commandPatternADO = regexp.MustCompile("^##\\[([^ ]+)( (.+))?]([^\r\n]*)[\r\n]+$") commandPatternADO = regexp.MustCompile("^##\\[([^ ]+)( (.+))?]([^\r\n]*)[\r\n]+$")
} }
func tryParseRawActionCommand(line string) (command string, kvPairs map[string]string, arg string, ok bool) {
if m := commandPatternGA.FindStringSubmatch(line); m != nil {
command = m[1]
kvPairs = parseKeyValuePairs(m[3], ",")
arg = m[4]
ok = true
} else if m := commandPatternADO.FindStringSubmatch(line); m != nil {
command = m[1]
kvPairs = parseKeyValuePairs(m[3], ";")
arg = m[4]
ok = true
}
return
}
func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler { func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
logger := common.Logger(ctx) logger := common.Logger(ctx)
resumeCommand := "" resumeCommand := ""
return func(line string) bool { return func(line string) bool {
var command string command, kvPairs, arg, ok := tryParseRawActionCommand(line)
var kvPairs map[string]string if !ok {
var arg string
if m := commandPatternGA.FindStringSubmatch(line); m != nil {
command = m[1]
kvPairs = parseKeyValuePairs(m[3], ",")
arg = m[4]
} else if m := commandPatternADO.FindStringSubmatch(line); m != nil {
command = m[1]
kvPairs = parseKeyValuePairs(m[3], ";")
arg = m[4]
} else {
return true return true
} }
@@ -66,6 +71,8 @@ func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
case "save-state": case "save-state":
logger.Infof(" \U0001f4be %s", line) logger.Infof(" \U0001f4be %s", line)
rc.saveState(ctx, kvPairs, arg) rc.saveState(ctx, kvPairs, arg)
case "add-matcher":
logger.Infof(" \U00002753 add-matcher %s", arg)
default: default:
logger.Infof(" \U00002753 %s", line) logger.Infof(" \U00002753 %s", line)
} }
@@ -75,11 +82,23 @@ func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
} }
func (rc *RunContext) setEnv(ctx context.Context, kvPairs map[string]string, arg string) { func (rc *RunContext) setEnv(ctx context.Context, kvPairs map[string]string, arg string) {
common.Logger(ctx).Infof(" \U00002699 ::set-env:: %s=%s", kvPairs["name"], arg) name := kvPairs["name"]
common.Logger(ctx).Infof(" \U00002699 ::set-env:: %s=%s", name, arg)
if rc.Env == nil { if rc.Env == nil {
rc.Env = make(map[string]string) rc.Env = make(map[string]string)
} }
rc.Env[kvPairs["name"]] = arg if rc.GlobalEnv == nil {
rc.GlobalEnv = map[string]string{}
}
newenv := map[string]string{
name: arg,
}
mergeIntoMap := mergeIntoMapCaseSensitive
if rc.JobContainer != nil && rc.JobContainer.IsEnvironmentCaseInsensitive() {
mergeIntoMap = mergeIntoMapCaseInsensitive
}
mergeIntoMap(rc.Env, newenv)
mergeIntoMap(rc.GlobalEnv, newenv)
} }
func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string, arg string) { func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string, arg string) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
@@ -101,7 +120,13 @@ func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string,
} }
func (rc *RunContext) addPath(ctx context.Context, arg string) { func (rc *RunContext) addPath(ctx context.Context, arg string) {
common.Logger(ctx).Infof(" \U00002699 ::add-path:: %s", arg) common.Logger(ctx).Infof(" \U00002699 ::add-path:: %s", arg)
rc.ExtraPath = append(rc.ExtraPath, arg) extraPath := []string{arg}
for _, v := range rc.ExtraPath {
if v != arg {
extraPath = append(extraPath, v)
}
}
rc.ExtraPath = extraPath
} }
func parseKeyValuePairs(kvPairs string, separator string) map[string]string { func parseKeyValuePairs(kvPairs string, separator string) map[string]string {
@@ -146,14 +171,17 @@ func unescapeKvPairs(kvPairs map[string]string) map[string]string {
return kvPairs return kvPairs
} }
func (rc *RunContext) saveState(ctx context.Context, kvPairs map[string]string, arg string) { func (rc *RunContext) saveState(_ context.Context, kvPairs map[string]string, arg string) {
if rc.CurrentStep != "" { stepID := rc.CurrentStep
stepResult := rc.StepResults[rc.CurrentStep] if stepID != "" {
if stepResult != nil { if rc.IntraActionState == nil {
if stepResult.State == nil { rc.IntraActionState = map[string]map[string]string{}
stepResult.State = map[string]string{}
}
stepResult.State[kvPairs["name"]] = arg
} }
state, ok := rc.IntraActionState[stepID]
if !ok {
state = map[string]string{}
rc.IntraActionState[stepID] = state
}
state[kvPairs["name"]] = arg
} }
} }

View File

@@ -64,7 +64,7 @@ func TestAddpath(t *testing.T) {
a.Equal("/zoo", rc.ExtraPath[0]) a.Equal("/zoo", rc.ExtraPath[0])
handler("::add-path::/boo\n") handler("::add-path::/boo\n")
a.Equal("/boo", rc.ExtraPath[1]) a.Equal("/boo", rc.ExtraPath[0])
} }
func TestStopCommands(t *testing.T) { func TestStopCommands(t *testing.T) {
@@ -102,7 +102,7 @@ func TestAddpathADO(t *testing.T) {
a.Equal("/zoo", rc.ExtraPath[0]) a.Equal("/zoo", rc.ExtraPath[0])
handler("##[add-path]/boo\n") handler("##[add-path]/boo\n")
a.Equal("/boo", rc.ExtraPath[1]) a.Equal("/boo", rc.ExtraPath[0])
} }
func TestAddmask(t *testing.T) { func TestAddmask(t *testing.T) {
@@ -177,11 +177,7 @@ func TestAddmaskUsemask(t *testing.T) {
func TestSaveState(t *testing.T) { func TestSaveState(t *testing.T) {
rc := &RunContext{ rc := &RunContext{
CurrentStep: "step", CurrentStep: "step",
StepResults: map[string]*model.StepResult{ StepResults: map[string]*model.StepResult{},
"step": {
State: map[string]string{},
},
},
} }
ctx := context.Background() ctx := context.Background()
@@ -189,5 +185,5 @@ func TestSaveState(t *testing.T) {
handler := rc.commandHandler(ctx) handler := rc.commandHandler(ctx)
handler("::save-state name=state-name::state-value\n") handler("::save-state name=state-name::state-value\n")
assert.Equal(t, "state-value", rc.StepResults["step"].State["state-name"]) assert.Equal(t, "state-value", rc.IntraActionState["step"]["state-name"])
} }

View File

@@ -2,6 +2,7 @@ package runner
import ( import (
"context" "context"
"io"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
@@ -49,11 +50,6 @@ func (cm *containerMock) UpdateFromImageEnv(env *map[string]string) common.Execu
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }
func (cm *containerMock) UpdateFromPath(env *map[string]string) common.Executor {
args := cm.Called(env)
return args.Get(0).(func(context.Context) error)
}
func (cm *containerMock) Copy(destPath string, files ...*container.FileEntry) common.Executor { func (cm *containerMock) Copy(destPath string, files ...*container.FileEntry) common.Executor {
args := cm.Called(destPath, files) args := cm.Called(destPath, files)
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
@@ -63,7 +59,17 @@ func (cm *containerMock) CopyDir(destPath string, srcPath string, useGitIgnore b
args := cm.Called(destPath, srcPath, useGitIgnore) args := cm.Called(destPath, srcPath, useGitIgnore)
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }
func (cm *containerMock) Exec(command []string, env map[string]string, user, workdir string) common.Executor { func (cm *containerMock) Exec(command []string, env map[string]string, user, workdir string) common.Executor {
args := cm.Called(command, env, user, workdir) args := cm.Called(command, env, user, workdir)
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }
func (cm *containerMock) GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error) {
args := cm.Called(ctx, srcPath)
err, hasErr := args.Get(1).(error)
if !hasErr {
err = nil
}
return args.Get(0).(io.ReadCloser), err
}

View File

@@ -1,12 +1,19 @@
package runner package runner
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"path"
"reflect"
"regexp" "regexp"
"strings" "strings"
"time"
_ "embed"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/exprparser" "github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -21,8 +28,14 @@ type ExpressionEvaluator interface {
// NewExpressionEvaluator creates a new evaluator // NewExpressionEvaluator creates a new evaluator
func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEvaluator { func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEvaluator {
return rc.NewExpressionEvaluatorWithEnv(ctx, rc.GetEnv())
}
func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map[string]string) ExpressionEvaluator {
var workflowCallResult map[string]*model.WorkflowCallResult
// todo: cleanup EvaluationEnvironment creation // todo: cleanup EvaluationEnvironment creation
using := make(map[string]map[string]map[string]string) using := make(map[string]exprparser.Needs)
strategy := make(map[string]interface{}) strategy := make(map[string]interface{})
if rc.Run != nil { if rc.Run != nil {
job := rc.Run.Job() job := rc.Run.Job()
@@ -35,8 +48,26 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
jobNeeds := rc.Run.Job().Needs() jobNeeds := rc.Run.Job().Needs()
for _, needs := range jobNeeds { for _, needs := range jobNeeds {
using[needs] = map[string]map[string]string{ using[needs] = exprparser.Needs{
"outputs": jobs[needs].Outputs, Outputs: jobs[needs].Outputs,
Result: jobs[needs].Result,
}
}
// only setup jobs context in case of workflow_call
// and existing expression evaluator (this means, jobs are at
// least ready to run)
if rc.caller != nil && rc.ExprEval != nil {
workflowCallResult = map[string]*model.WorkflowCallResult{}
for jobName, job := range jobs {
result := model.WorkflowCallResult{
Outputs: map[string]string{},
}
for k, v := range job.Outputs {
result.Outputs[k] = v
}
workflowCallResult[jobName] = &result
} }
} }
} }
@@ -46,16 +77,19 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
ee := &exprparser.EvaluationEnvironment{ ee := &exprparser.EvaluationEnvironment{
Github: ghc, Github: ghc,
Env: rc.GetEnv(), Env: env,
Job: rc.getJobContext(), Job: rc.getJobContext(),
Jobs: &workflowCallResult,
// todo: should be unavailable // todo: should be unavailable
// but required to interpolate/evaluate the step outputs on the job // but required to interpolate/evaluate the step outputs on the job
Steps: rc.getStepsContext(), Steps: rc.getStepsContext(),
Secrets: rc.Config.Secrets, Secrets: getWorkflowSecrets(ctx, rc),
Strategy: strategy, Vars: getWorkflowVars(ctx, rc),
Matrix: rc.Matrix, Strategy: strategy,
Needs: using, Matrix: rc.Matrix,
Inputs: inputs, Needs: using,
Inputs: inputs,
HashFiles: getHashFilesFunction(ctx, rc),
} }
if rc.JobContainer != nil { if rc.JobContainer != nil {
ee.Runner = rc.JobContainer.GetRunnerContext(ctx) ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
@@ -69,6 +103,9 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
} }
} }
//go:embed hashfiles/index.js
var hashfiles string
// NewExpressionEvaluator creates a new evaluator // NewExpressionEvaluator creates a new evaluator
func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator { func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
// todo: cleanup EvaluationEnvironment creation // todo: cleanup EvaluationEnvironment creation
@@ -82,10 +119,11 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
jobs := rc.Run.Workflow.Jobs jobs := rc.Run.Workflow.Jobs
jobNeeds := rc.Run.Job().Needs() jobNeeds := rc.Run.Job().Needs()
using := make(map[string]map[string]map[string]string) using := make(map[string]exprparser.Needs)
for _, needs := range jobNeeds { for _, needs := range jobNeeds {
using[needs] = map[string]map[string]string{ using[needs] = exprparser.Needs{
"outputs": jobs[needs].Outputs, Outputs: jobs[needs].Outputs,
Result: jobs[needs].Result,
} }
} }
@@ -97,13 +135,15 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
Env: *step.getEnv(), Env: *step.getEnv(),
Job: rc.getJobContext(), Job: rc.getJobContext(),
Steps: rc.getStepsContext(), Steps: rc.getStepsContext(),
Secrets: rc.Config.Secrets, Secrets: getWorkflowSecrets(ctx, rc),
Vars: getWorkflowVars(ctx, rc),
Strategy: strategy, Strategy: strategy,
Matrix: rc.Matrix, Matrix: rc.Matrix,
Needs: using, Needs: using,
// todo: should be unavailable // todo: should be unavailable
// but required to interpolate/evaluate the inputs in actions/composite // but required to interpolate/evaluate the inputs in actions/composite
Inputs: inputs, Inputs: inputs,
HashFiles: getHashFilesFunction(ctx, rc),
} }
if rc.JobContainer != nil { if rc.JobContainer != nil {
ee.Runner = rc.JobContainer.GetRunnerContext(ctx) ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
@@ -117,6 +157,67 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
} }
} }
func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect.Value) (interface{}, error) {
hashFiles := func(v []reflect.Value) (interface{}, error) {
if rc.JobContainer != nil {
timeed, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
name := "workflow/hashfiles/index.js"
hout := &bytes.Buffer{}
herr := &bytes.Buffer{}
patterns := []string{}
followSymlink := false
for i, p := range v {
s := p.String()
if i == 0 {
if strings.HasPrefix(s, "--") {
if strings.EqualFold(s, "--follow-symbolic-links") {
followSymlink = true
continue
}
return "", fmt.Errorf("Invalid glob option %s, available option: '--follow-symbolic-links'", s)
}
}
patterns = append(patterns, s)
}
env := map[string]string{}
for k, v := range rc.Env {
env[k] = v
}
env["patterns"] = strings.Join(patterns, "\n")
if followSymlink {
env["followSymbolicLinks"] = "true"
}
stdout, stderr := rc.JobContainer.ReplaceLogWriter(hout, herr)
_ = rc.JobContainer.Copy(rc.JobContainer.GetActPath(), &container.FileEntry{
Name: name,
Mode: 0o644,
Body: hashfiles,
}).
Then(rc.execJobContainer([]string{"node", path.Join(rc.JobContainer.GetActPath(), name)},
env, "", "")).
Finally(func(context.Context) error {
rc.JobContainer.ReplaceLogWriter(stdout, stderr)
return nil
})(timeed)
output := hout.String() + "\n" + herr.String()
guard := "__OUTPUT__"
outstart := strings.Index(output, guard)
if outstart != -1 {
outstart += len(guard)
outend := strings.Index(output[outstart:], guard)
if outend != -1 {
return output[outstart : outstart+outend], nil
}
}
}
return "", nil
}
return hashFiles
}
type expressionEvaluator struct { type expressionEvaluator struct {
interpreter exprparser.Interpreter interpreter exprparser.Interpreter
} }
@@ -132,67 +233,117 @@ func (ee expressionEvaluator) evaluate(ctx context.Context, in string, defaultSt
return evaluated, err return evaluated, err
} }
func (ee expressionEvaluator) evaluateScalarYamlNode(ctx context.Context, node *yaml.Node) error { func (ee expressionEvaluator) evaluateScalarYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
var in string var in string
if err := node.Decode(&in); err != nil { if err := node.Decode(&in); err != nil {
return err return nil, err
} }
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return nil return nil, nil
} }
expr, _ := rewriteSubExpression(ctx, in, false) expr, _ := rewriteSubExpression(ctx, in, false)
res, err := ee.evaluate(ctx, expr, exprparser.DefaultStatusCheckNone) res, err := ee.evaluate(ctx, expr, exprparser.DefaultStatusCheckNone)
if err != nil { if err != nil {
return err return nil, err
} }
return node.Encode(res) ret := &yaml.Node{}
if err := ret.Encode(res); err != nil {
return nil, err
}
return ret, err
} }
func (ee expressionEvaluator) evaluateMappingYamlNode(ctx context.Context, node *yaml.Node) error { func (ee expressionEvaluator) evaluateMappingYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
var ret *yaml.Node
// GitHub has this undocumented feature to merge maps, called insert directive // GitHub has this undocumented feature to merge maps, called insert directive
insertDirective := regexp.MustCompile(`\${{\s*insert\s*}}`) insertDirective := regexp.MustCompile(`\${{\s*insert\s*}}`)
for i := 0; i < len(node.Content)/2; { for i := 0; i < len(node.Content)/2; i++ {
changed := func() error {
if ret == nil {
ret = &yaml.Node{}
if err := ret.Encode(node); err != nil {
return err
}
ret.Content = ret.Content[:i*2]
}
return nil
}
k := node.Content[i*2] k := node.Content[i*2]
v := node.Content[i*2+1] v := node.Content[i*2+1]
if err := ee.EvaluateYamlNode(ctx, v); err != nil { ev, err := ee.evaluateYamlNodeInternal(ctx, v)
return err if err != nil {
return nil, err
}
if ev != nil {
if err := changed(); err != nil {
return nil, err
}
} else {
ev = v
} }
var sk string var sk string
// Merge the nested map of the insert directive // Merge the nested map of the insert directive
if k.Decode(&sk) == nil && insertDirective.MatchString(sk) { if k.Decode(&sk) == nil && insertDirective.MatchString(sk) {
node.Content = append(append(node.Content[:i*2], v.Content...), node.Content[(i+1)*2:]...) if ev.Kind != yaml.MappingNode {
i += len(v.Content) / 2 return nil, fmt.Errorf("failed to insert node %v into mapping %v unexpected type %v expected MappingNode", ev, node, ev.Kind)
} else { }
if err := ee.EvaluateYamlNode(ctx, k); err != nil { if err := changed(); err != nil {
return err return nil, err
}
ret.Content = append(ret.Content, ev.Content...)
} else {
ek, err := ee.evaluateYamlNodeInternal(ctx, k)
if err != nil {
return nil, err
}
if ek != nil {
if err := changed(); err != nil {
return nil, err
}
} else {
ek = k
}
if ret != nil {
ret.Content = append(ret.Content, ek, ev)
} }
i++
} }
} }
return nil return ret, nil
} }
func (ee expressionEvaluator) evaluateSequenceYamlNode(ctx context.Context, node *yaml.Node) error { func (ee expressionEvaluator) evaluateSequenceYamlNode(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
for i := 0; i < len(node.Content); { var ret *yaml.Node
for i := 0; i < len(node.Content); i++ {
v := node.Content[i] v := node.Content[i]
// Preserve nested sequences // Preserve nested sequences
wasseq := v.Kind == yaml.SequenceNode wasseq := v.Kind == yaml.SequenceNode
if err := ee.EvaluateYamlNode(ctx, v); err != nil { ev, err := ee.evaluateYamlNodeInternal(ctx, v)
return err if err != nil {
return nil, err
} }
// GitHub has this undocumented feature to merge sequences / arrays if ev != nil {
// We have a nested sequence via evaluation, merge the arrays if ret == nil {
if v.Kind == yaml.SequenceNode && !wasseq { ret = &yaml.Node{}
node.Content = append(append(node.Content[:i], v.Content...), node.Content[i+1:]...) if err := ret.Encode(node); err != nil {
i += len(v.Content) return nil, err
} else { }
i++ ret.Content = ret.Content[:i]
}
// GitHub has this undocumented feature to merge sequences / arrays
// We have a nested sequence via evaluation, merge the arrays
if ev.Kind == yaml.SequenceNode && !wasseq {
ret.Content = append(ret.Content, ev.Content...)
} else {
ret.Content = append(ret.Content, ev)
}
} else if ret != nil {
ret.Content = append(ret.Content, v)
} }
} }
return nil return ret, nil
} }
func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.Node) error { func (ee expressionEvaluator) evaluateYamlNodeInternal(ctx context.Context, node *yaml.Node) (*yaml.Node, error) {
switch node.Kind { switch node.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
return ee.evaluateScalarYamlNode(ctx, node) return ee.evaluateScalarYamlNode(ctx, node)
@@ -201,10 +352,21 @@ func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.N
case yaml.SequenceNode: case yaml.SequenceNode:
return ee.evaluateSequenceYamlNode(ctx, node) return ee.evaluateSequenceYamlNode(ctx, node)
default: default:
return nil return nil, nil
} }
} }
func (ee expressionEvaluator) EvaluateYamlNode(ctx context.Context, node *yaml.Node) error {
ret, err := ee.evaluateYamlNodeInternal(ctx, node)
if err != nil {
return err
}
if ret != nil {
return ret.Decode(node)
}
return nil
}
func (ee expressionEvaluator) Interpolate(ctx context.Context, in string) string { func (ee expressionEvaluator) Interpolate(ctx context.Context, in string) string {
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return in return in
@@ -308,9 +470,12 @@ func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (str
return out, nil return out, nil
} }
//nolint:gocyclo
func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *model.GithubContext) map[string]interface{} { func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *model.GithubContext) map[string]interface{} {
inputs := map[string]interface{}{} inputs := map[string]interface{}{}
setupWorkflowInputs(ctx, &inputs, rc)
var env map[string]string var env map[string]string
if step != nil { if step != nil {
env = *step.getEnv() env = *step.getEnv()
@@ -341,5 +506,76 @@ func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *mod
} }
} }
if ghc.EventName == "workflow_call" {
config := rc.Run.Workflow.WorkflowCallConfig()
if config != nil && config.Inputs != nil {
for k, v := range config.Inputs {
value := nestedMapLookup(ghc.Event, "inputs", k)
if value == nil {
value = v.Default
}
if v.Type == "boolean" {
inputs[k] = value == "true"
} else {
inputs[k] = value
}
}
}
}
return inputs return inputs
} }
func setupWorkflowInputs(ctx context.Context, inputs *map[string]interface{}, rc *RunContext) {
if rc.caller != nil {
config := rc.Run.Workflow.WorkflowCallConfig()
for name, input := range config.Inputs {
value := rc.caller.runContext.Run.Job().With[name]
if value != nil {
if str, ok := value.(string); ok {
// evaluate using the calling RunContext (outside)
value = rc.caller.runContext.ExprEval.Interpolate(ctx, str)
}
}
if value == nil && config != nil && config.Inputs != nil {
value = input.Default
if rc.ExprEval != nil {
if str, ok := value.(string); ok {
// evaluate using the called RunContext (inside)
value = rc.ExprEval.Interpolate(ctx, str)
}
}
}
(*inputs)[name] = value
}
}
}
func getWorkflowSecrets(ctx context.Context, rc *RunContext) map[string]string {
if rc.caller != nil {
job := rc.caller.runContext.Run.Job()
secrets := job.Secrets()
if secrets == nil && job.InheritSecrets() {
secrets = rc.caller.runContext.Config.Secrets
}
if secrets == nil {
secrets = map[string]string{}
}
for k, v := range secrets {
secrets[k] = rc.caller.runContext.ExprEval.Interpolate(ctx, v)
}
return secrets
}
return rc.Config.Secrets
}
func getWorkflowVars(_ context.Context, rc *RunContext) map[string]string {
return rc.Config.Vars
}

View File

@@ -28,6 +28,9 @@ func createRunContext(t *testing.T) *RunContext {
Secrets: map[string]string{ Secrets: map[string]string{
"CASE_INSENSITIVE_SECRET": "value", "CASE_INSENSITIVE_SECRET": "value",
}, },
Vars: map[string]string{
"CASE_INSENSITIVE_VAR": "value",
},
}, },
Env: map[string]string{ Env: map[string]string{
"key": "value", "key": "value",
@@ -122,6 +125,8 @@ func TestEvaluateRunContext(t *testing.T) {
{"env.key", "value", ""}, {"env.key", "value", ""},
{"secrets.CASE_INSENSITIVE_SECRET", "value", ""}, {"secrets.CASE_INSENSITIVE_SECRET", "value", ""},
{"secrets.case_insensitive_secret", "value", ""}, {"secrets.case_insensitive_secret", "value", ""},
{"vars.CASE_INSENSITIVE_VAR", "value", ""},
{"vars.case_insensitive_var", "value", ""},
{"format('{{0}}', 'test')", "{0}", ""}, {"format('{{0}}', 'test')", "{0}", ""},
{"format('{{{0}}}', 'test')", "{test}", ""}, {"format('{{{0}}}', 'test')", "{test}", ""},
{"format('}}')", "}", ""}, {"format('}}')", "}", ""},
@@ -195,6 +200,9 @@ func TestInterpolate(t *testing.T) {
Secrets: map[string]string{ Secrets: map[string]string{
"CASE_INSENSITIVE_SECRET": "value", "CASE_INSENSITIVE_SECRET": "value",
}, },
Vars: map[string]string{
"CASE_INSENSITIVE_VAR": "value",
},
}, },
Env: map[string]string{ Env: map[string]string{
"KEYWITHNOTHING": "valuewithnothing", "KEYWITHNOTHING": "valuewithnothing",
@@ -229,6 +237,8 @@ func TestInterpolate(t *testing.T) {
{" ${{ env.KEY_WITH_UNDERSCORES }} ", " value_with_underscores "}, {" ${{ env.KEY_WITH_UNDERSCORES }} ", " value_with_underscores "},
{"${{ secrets.CASE_INSENSITIVE_SECRET }}", "value"}, {"${{ secrets.CASE_INSENSITIVE_SECRET }}", "value"},
{"${{ secrets.case_insensitive_secret }}", "value"}, {"${{ secrets.case_insensitive_secret }}", "value"},
{"${{ vars.CASE_INSENSITIVE_VAR }}", "value"},
{"${{ vars.case_insensitive_var }}", "value"},
{"${{ env.UNKNOWN }}", ""}, {"${{ env.UNKNOWN }}", ""},
{"${{ env.SOMETHING_TRUE }}", "true"}, {"${{ env.SOMETHING_TRUE }}", "true"},
{"${{ env.SOMETHING_FALSE }}", "false"}, {"${{ env.SOMETHING_FALSE }}", "false"},

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@ type jobInfo interface {
result(result string) result(result string)
} }
//nolint:contextcheck,gocyclo
func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor { func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor {
steps := make([]common.Executor, 0) steps := make([]common.Executor, 0)
preSteps := make([]common.Executor, 0) preSteps := make([]common.Executor, 0)
@@ -62,7 +63,6 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
if stepModel.ID == "" { if stepModel.ID == "" {
stepModel.ID = fmt.Sprintf("%d", i) stepModel.ID = fmt.Sprintf("%d", i)
} }
stepModel.Number = i
step, err := sf.newStep(stepModel, rc) step, err := sf.newStep(stepModel, rc)
@@ -88,7 +88,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
postExec := useStepLogger(rc, stepModel, stepStagePost, step.post()) postExec := useStepLogger(rc, stepModel, stepStagePost, step.post())
if postExecutor != nil { if postExecutor != nil {
// run the post exector in reverse order // run the post executor in reverse order
postExecutor = postExec.Finally(postExecutor) postExecutor = postExec.Finally(postExecutor)
} else { } else {
postExecutor = postExec postExecutor = postExec
@@ -96,21 +96,23 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
} }
postExecutor = postExecutor.Finally(func(ctx context.Context) error { postExecutor = postExecutor.Finally(func(ctx context.Context) error {
logger := common.Logger(ctx)
jobError := common.JobError(ctx) jobError := common.JobError(ctx)
if jobError != nil { var err error
info.result("failure") if rc.Config.AutoRemove || jobError == nil {
logger.WithField("jobResult", "failure").Infof("\U0001F3C1 Job failed") // always allow 1 min for stopping and removing the runner, even if we were cancelled
} else { ctx, cancel := context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), time.Minute)
err := info.stopContainer()(ctx) defer cancel()
if err != nil {
return err
}
info.result("success")
logger.WithField("jobResult", "success").Infof("\U0001F3C1 Job succeeded")
}
return nil logger := common.Logger(ctx)
logger.Infof("Cleaning up container for job %s", rc.JobName)
if err = info.stopContainer()(ctx); err != nil {
logger.Errorf("Error while stop job container: %v", err)
}
}
setJobResult(ctx, info, rc, jobError == nil)
setJobOutputs(ctx, rc)
return err
}) })
pipeline := make([]common.Executor, 0) pipeline := make([]common.Executor, 0)
@@ -118,12 +120,12 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
pipeline = append(pipeline, steps...) pipeline = append(pipeline, steps...)
return common.NewPipelineExecutor(info.startContainer(), common.NewPipelineExecutor(pipeline...). return common.NewPipelineExecutor(info.startContainer(), common.NewPipelineExecutor(pipeline...).
Finally(func(ctx context.Context) error { Finally(func(ctx context.Context) error { //nolint:contextcheck
var cancel context.CancelFunc var cancel context.CancelFunc
if ctx.Err() == context.Canceled { if ctx.Err() == context.Canceled {
// in case of an aborted run, we still should execute the // in case of an aborted run, we still should execute the
// post steps to allow cleanup. // post steps to allow cleanup.
ctx, cancel = context.WithTimeout(WithJobLogger(context.Background(), rc.Run.JobID, rc.String(), rc.Config, &rc.Masks, rc.Matrix), 5*time.Minute) ctx, cancel = context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), 5*time.Minute)
defer cancel() defer cancel()
} }
return postExecutor(ctx) return postExecutor(ctx)
@@ -132,9 +134,52 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
Finally(info.closeContainer())) Finally(info.closeContainer()))
} }
func setJobResult(ctx context.Context, info jobInfo, rc *RunContext, success bool) {
logger := common.Logger(ctx)
jobResult := "success"
// we have only one result for a whole matrix build, so we need
// to keep an existing result state if we run a matrix
if len(info.matrix()) > 0 && rc.Run.Job().Result != "" {
jobResult = rc.Run.Job().Result
}
if !success {
jobResult = "failure"
}
info.result(jobResult)
if rc.caller != nil {
// set reusable workflow job result
rc.caller.runContext.result(jobResult)
}
jobResultMessage := "succeeded"
if jobResult != "success" {
jobResultMessage = "failed"
}
logger.WithField("jobResult", jobResult).Infof("\U0001F3C1 Job %s", jobResultMessage)
}
func setJobOutputs(ctx context.Context, rc *RunContext) {
if rc.caller != nil {
// map outputs for reusable workflows
callerOutputs := make(map[string]string)
ee := rc.NewExpressionEvaluator(ctx)
for k, v := range rc.Run.Workflow.WorkflowCallConfig().Outputs {
callerOutputs[k] = ee.Interpolate(ctx, ee.Interpolate(ctx, v.Value))
}
rc.caller.runContext.Run.Job().Outputs = callerOutputs
}
}
func useStepLogger(rc *RunContext, stepModel *model.Step, stage stepStage, executor common.Executor) common.Executor { func useStepLogger(rc *RunContext, stepModel *model.Step, stage stepStage, executor common.Executor) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
ctx = withStepLogger(ctx, stepModel.Number, stepModel.ID, rc.ExprEval.Interpolate(ctx, stepModel.String()), stage.String()) ctx = withStepLogger(ctx, stepModel.ID, rc.ExprEval.Interpolate(ctx, stepModel.String()), stage.String())
rawLogger := common.Logger(ctx).WithField("raw_output", true) rawLogger := common.Logger(ctx).WithField("raw_output", true)
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool { logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {

View File

@@ -15,15 +15,15 @@ import (
func TestJobExecutor(t *testing.T) { func TestJobExecutor(t *testing.T) {
tables := []TestJobFileInfo{ tables := []TestJobFileInfo{
{workdir, "uses-and-run-in-one-step", "push", "Invalid run/uses syntax for job:test step:Test", platforms}, {workdir, "uses-and-run-in-one-step", "push", "Invalid run/uses syntax for job:test step:Test", platforms, secrets},
{workdir, "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms}, {workdir, "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms, secrets},
{workdir, "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms}, {workdir, "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, secrets},
{workdir, "uses-github-root", "push", "", platforms}, {workdir, "uses-github-root", "push", "", platforms, secrets},
{workdir, "uses-github-path", "push", "", platforms}, {workdir, "uses-github-path", "push", "", platforms, secrets},
{workdir, "uses-docker-url", "push", "", platforms}, {workdir, "uses-docker-url", "push", "", platforms, secrets},
{workdir, "uses-github-full-sha", "push", "", platforms}, {workdir, "uses-github-full-sha", "push", "", platforms, secrets},
{workdir, "uses-github-short-sha", "push", "Unable to resolve action `actions/hello-world-docker-action@b136eb8`, the provided ref `b136eb8` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `b136eb8894c5cb1dd5807da824be97ccdf9b5423` instead", platforms}, {workdir, "uses-github-short-sha", "push", "Unable to resolve action `actions/hello-world-docker-action@b136eb8`, the provided ref `b136eb8` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `b136eb8894c5cb1dd5807da824be97ccdf9b5423` instead", platforms, secrets},
{workdir, "job-nil-step", "push", "invalid Step 0: missing run or uses key", platforms}, {workdir, "job-nil-step", "push", "invalid Step 0: missing run or uses key", platforms, secrets},
} }
// These tests are sufficient to only check syntax. // These tests are sufficient to only check syntax.
ctx := common.WithDryrun(context.Background(), true) ctx := common.WithDryrun(context.Background(), true)
@@ -82,7 +82,7 @@ type jobContainerMock struct {
container.LinuxContainerEnvironmentExtensions container.LinuxContainerEnvironmentExtensions
} }
func (jcm *jobContainerMock) ReplaceLogWriter(stdout, stderr io.Writer) (io.Writer, io.Writer) { func (jcm *jobContainerMock) ReplaceLogWriter(_, _ io.Writer) (io.Writer, io.Writer) {
return nil, nil return nil, nil
} }

View File

@@ -57,34 +57,49 @@ func WithMasks(ctx context.Context, masks *[]string) context.Context {
return context.WithValue(ctx, masksContextKeyVal, masks) return context.WithValue(ctx, masksContextKeyVal, masks)
} }
type JobLoggerFactory interface {
WithJobLogger() *logrus.Logger
}
type jobLoggerFactoryContextKey string
var jobLoggerFactoryContextKeyVal = (jobLoggerFactoryContextKey)("jobloggerkey")
func WithJobLoggerFactory(ctx context.Context, factory JobLoggerFactory) context.Context {
return context.WithValue(ctx, jobLoggerFactoryContextKeyVal, factory)
}
// WithJobLogger attaches a new logger to context that is aware of steps // WithJobLogger attaches a new logger to context that is aware of steps
func WithJobLogger(ctx context.Context, jobID string, jobName string, config *Config, masks *[]string, matrix map[string]interface{}) context.Context { func WithJobLogger(ctx context.Context, jobID string, jobName string, config *Config, masks *[]string, matrix map[string]interface{}) context.Context {
mux.Lock()
defer mux.Unlock()
var formatter logrus.Formatter
if config.JSONLogger {
formatter = &jobLogJSONFormatter{
formatter: &logrus.JSONFormatter{},
masker: valueMasker(config.InsecureSecrets, config.Secrets),
}
} else {
formatter = &jobLogFormatter{
color: colors[nextColor%len(colors)],
masker: valueMasker(config.InsecureSecrets, config.Secrets),
}
}
nextColor++
ctx = WithMasks(ctx, masks) ctx = WithMasks(ctx, masks)
logger := logrus.New() var logger *logrus.Logger
if hook := common.LoggerHook(ctx); hook != nil { if jobLoggerFactory, ok := ctx.Value(jobLoggerFactoryContextKeyVal).(JobLoggerFactory); ok && jobLoggerFactory != nil {
logger.AddHook(hook) logger = jobLoggerFactory.WithJobLogger()
} else {
var formatter logrus.Formatter
if config.JSONLogger {
formatter = &logrus.JSONFormatter{}
} else {
mux.Lock()
defer mux.Unlock()
nextColor++
formatter = &jobLogFormatter{
color: colors[nextColor%len(colors)],
logPrefixJobID: config.LogPrefixJobID,
}
}
logger = logrus.New()
logger.SetOutput(os.Stdout)
logger.SetLevel(logrus.GetLevel())
logger.SetFormatter(formatter)
} }
logger.SetFormatter(formatter)
logger.SetOutput(os.Stdout) logger.SetFormatter(&maskedFormatter{
logger.SetLevel(logrus.TraceLevel) // to be aware of steps Formatter: logger.Formatter,
masker: valueMasker(config.InsecureSecrets, config.Secrets),
})
rtn := logger.WithFields(logrus.Fields{ rtn := logger.WithFields(logrus.Fields{
"job": jobName, "job": jobName,
"jobID": jobID, "jobID": jobID,
@@ -117,12 +132,11 @@ func WithCompositeStepLogger(ctx context.Context, stepID string) context.Context
}).WithContext(ctx)) }).WithContext(ctx))
} }
func withStepLogger(ctx context.Context, stepNumber int, stepID, stepName, stageName string) context.Context { func withStepLogger(ctx context.Context, stepID string, stepName string, stageName string) context.Context {
rtn := common.Logger(ctx).WithFields(logrus.Fields{ rtn := common.Logger(ctx).WithFields(logrus.Fields{
"stepNumber": stepNumber, "step": stepName,
"step": stepName, "stepID": []string{stepID},
"stepID": []string{stepID}, "stage": stageName,
"stage": stageName,
}) })
return common.WithLogger(ctx, rtn) return common.WithLogger(ctx, rtn)
} }
@@ -153,16 +167,23 @@ func valueMasker(insecureSecrets bool, secrets map[string]string) entryProcessor
} }
} }
type jobLogFormatter struct { type maskedFormatter struct {
color int logrus.Formatter
masker entryProcessor masker entryProcessor
} }
func (f *maskedFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return f.Formatter.Format(f.masker(entry))
}
type jobLogFormatter struct {
color int
logPrefixJobID bool
}
func (f *jobLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { func (f *jobLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
b := &bytes.Buffer{} b := &bytes.Buffer{}
entry = f.masker(entry)
if f.isColored(entry) { if f.isColored(entry) {
f.printColored(b, entry) f.printColored(b, entry)
} else { } else {
@@ -175,7 +196,14 @@ func (f *jobLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
func (f *jobLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) { func (f *jobLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
entry.Message = strings.TrimSuffix(entry.Message, "\n") entry.Message = strings.TrimSuffix(entry.Message, "\n")
jobName := entry.Data["job"]
var job any
if f.logPrefixJobID {
job = entry.Data["jobID"]
} else {
job = entry.Data["job"]
}
debugFlag := "" debugFlag := ""
if entry.Level == logrus.DebugLevel { if entry.Level == logrus.DebugLevel {
debugFlag = "[DEBUG] " debugFlag = "[DEBUG] "
@@ -184,26 +212,33 @@ func (f *jobLogFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry) {
if entry.Data["raw_output"] == true { if entry.Data["raw_output"] == true {
fmt.Fprintf(b, "\x1b[%dm|\x1b[0m %s", f.color, entry.Message) fmt.Fprintf(b, "\x1b[%dm|\x1b[0m %s", f.color, entry.Message)
} else if entry.Data["dryrun"] == true { } else if entry.Data["dryrun"] == true {
fmt.Fprintf(b, "\x1b[1m\x1b[%dm\x1b[7m*DRYRUN*\x1b[0m \x1b[%dm[%s] \x1b[0m%s%s", gray, f.color, jobName, debugFlag, entry.Message) fmt.Fprintf(b, "\x1b[1m\x1b[%dm\x1b[7m*DRYRUN*\x1b[0m \x1b[%dm[%s] \x1b[0m%s%s", gray, f.color, job, debugFlag, entry.Message)
} else { } else {
fmt.Fprintf(b, "\x1b[%dm[%s] \x1b[0m%s%s", f.color, jobName, debugFlag, entry.Message) fmt.Fprintf(b, "\x1b[%dm[%s] \x1b[0m%s%s", f.color, job, debugFlag, entry.Message)
} }
} }
func (f *jobLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) { func (f *jobLogFormatter) print(b *bytes.Buffer, entry *logrus.Entry) {
entry.Message = strings.TrimSuffix(entry.Message, "\n") entry.Message = strings.TrimSuffix(entry.Message, "\n")
jobName := entry.Data["job"]
var job any
if f.logPrefixJobID {
job = entry.Data["jobID"]
} else {
job = entry.Data["job"]
}
debugFlag := "" debugFlag := ""
if entry.Level == logrus.DebugLevel { if entry.Level == logrus.DebugLevel {
debugFlag = "[DEBUG] " debugFlag = "[DEBUG] "
} }
if entry.Data["raw_output"] == true { if entry.Data["raw_output"] == true {
fmt.Fprintf(b, "[%s] | %s", jobName, entry.Message) fmt.Fprintf(b, "[%s] | %s", job, entry.Message)
} else if entry.Data["dryrun"] == true { } else if entry.Data["dryrun"] == true {
fmt.Fprintf(b, "*DRYRUN* [%s] %s%s", jobName, debugFlag, entry.Message) fmt.Fprintf(b, "*DRYRUN* [%s] %s%s", job, debugFlag, entry.Message)
} else { } else {
fmt.Fprintf(b, "[%s] %s%s", jobName, debugFlag, entry.Message) fmt.Fprintf(b, "[%s] %s%s", job, debugFlag, entry.Message)
} }
} }
@@ -229,12 +264,3 @@ func checkIfTerminal(w io.Writer) bool {
return false return false
} }
} }
type jobLogJSONFormatter struct {
masker entryProcessor
formatter *logrus.JSONFormatter
}
func (f *jobLogJSONFormatter) Format(entry *logrus.Entry) ([]byte, error) {
return f.formatter.Format(f.masker(entry))
}

View File

@@ -0,0 +1,135 @@
package runner
import (
"context"
"errors"
"fmt"
"io/fs"
"os"
"path"
"regexp"
"sync"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/common/git"
"github.com/nektos/act/pkg/model"
)
func newLocalReusableWorkflowExecutor(rc *RunContext) common.Executor {
return newReusableWorkflowExecutor(rc, rc.Config.Workdir, rc.Run.Job().Uses)
}
func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor {
uses := rc.Run.Job().Uses
remoteReusableWorkflow := newRemoteReusableWorkflow(uses)
if remoteReusableWorkflow == nil {
return common.NewErrorExecutor(fmt.Errorf("expected format {owner}/{repo}/.github/workflows/{filename}@{ref}. Actual '%s' Input string was not in a correct format", uses))
}
// uses with safe filename makes the target directory look something like this {owner}-{repo}-.github-workflows-{filename}@{ref}
// instead we will just use {owner}-{repo}@{ref} as our target directory. This should also improve performance when we are using
// multiple reusable workflows from the same repository and ref since for each workflow we won't have to clone it again
filename := fmt.Sprintf("%s/%s@%s", remoteReusableWorkflow.Org, remoteReusableWorkflow.Repo, remoteReusableWorkflow.Ref)
workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(filename))
return common.NewPipelineExecutor(
newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir)),
newReusableWorkflowExecutor(rc, workflowDir, fmt.Sprintf("./.github/workflows/%s", remoteReusableWorkflow.Filename)),
)
}
var (
executorLock sync.Mutex
)
func newMutexExecutor(executor common.Executor) common.Executor {
return func(ctx context.Context) error {
executorLock.Lock()
defer executorLock.Unlock()
return executor(ctx)
}
}
func cloneIfRequired(rc *RunContext, remoteReusableWorkflow remoteReusableWorkflow, targetDirectory string) common.Executor {
return common.NewConditionalExecutor(
func(ctx context.Context) bool {
_, err := os.Stat(targetDirectory)
notExists := errors.Is(err, fs.ErrNotExist)
return notExists
},
func(ctx context.Context) error {
remoteReusableWorkflow.URL = rc.getGithubContext(ctx).ServerURL
return git.NewGitCloneExecutor(git.NewGitCloneExecutorInput{
URL: remoteReusableWorkflow.CloneURL(),
Ref: remoteReusableWorkflow.Ref,
Dir: targetDirectory,
Token: rc.Config.Token,
})(ctx)
},
nil,
)
}
func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow string) common.Executor {
return func(ctx context.Context) error {
planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true)
if err != nil {
return err
}
plan, err := planner.PlanEvent("workflow_call")
if err != nil {
return err
}
runner, err := NewReusableWorkflowRunner(rc)
if err != nil {
return err
}
return runner.NewPlanExecutor(plan)(ctx)
}
}
func NewReusableWorkflowRunner(rc *RunContext) (Runner, error) {
runner := &runnerImpl{
config: rc.Config,
eventJSON: rc.EventJSON,
caller: &caller{
runContext: rc,
},
}
return runner.configure()
}
type remoteReusableWorkflow struct {
URL string
Org string
Repo string
Filename string
Ref string
}
func (r *remoteReusableWorkflow) CloneURL() string {
return fmt.Sprintf("%s/%s/%s", r.URL, r.Org, r.Repo)
}
func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow {
// GitHub docs:
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses
r := regexp.MustCompile(`^([^/]+)/([^/]+)/.github/workflows/([^@]+)@(.*)$`)
matches := r.FindStringSubmatch(uses)
if len(matches) != 5 {
return nil
}
return &remoteReusableWorkflow{
Org: matches[1],
Repo: matches[2],
Filename: matches[3],
Ref: matches[4],
URL: "https://github.com",
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -144,6 +144,7 @@ func TestRunContext_EvalBool(t *testing.T) {
// Check github context // Check github context
{in: "github.actor == 'nektos/act'", out: true}, {in: "github.actor == 'nektos/act'", out: true},
{in: "github.actor == 'unknown'", out: false}, {in: "github.actor == 'unknown'", out: false},
{in: "github.job == 'job1'", out: true},
// The special ACT flag // The special ACT flag
{in: "${{ env.ACT }}", out: true}, {in: "${{ env.ACT }}", out: true},
{in: "${{ !env.ACT }}", out: false}, {in: "${{ !env.ACT }}", out: false},
@@ -364,6 +365,7 @@ func TestGetGitHubContext(t *testing.T) {
StepResults: map[string]*model.StepResult{}, StepResults: map[string]*model.StepResult{},
OutputMappings: map[MappableOutput]MappableOutput{}, OutputMappings: map[MappableOutput]MappableOutput{},
} }
rc.Run.JobID = "job1"
ghc := rc.getGithubContext(context.Background()) ghc := rc.getGithubContext(context.Background())
@@ -392,6 +394,7 @@ func TestGetGitHubContext(t *testing.T) {
assert.Equal(t, ghc.RepositoryOwner, owner) assert.Equal(t, ghc.RepositoryOwner, owner)
assert.Equal(t, ghc.RunnerPerflog, "/dev/null") assert.Equal(t, ghc.RunnerPerflog, "/dev/null")
assert.Equal(t, ghc.Token, rc.Config.Secrets["GITHUB_TOKEN"]) assert.Equal(t, ghc.Token, rc.Config.Secrets["GITHUB_TOKEN"])
assert.Equal(t, ghc.Job, "job1")
} }
func TestGetGithubContextRef(t *testing.T) { func TestGetGithubContextRef(t *testing.T) {
@@ -410,7 +413,7 @@ func TestGetGithubContextRef(t *testing.T) {
{event: "pull_request_target", json: `{"pull_request":{"base":{"ref": "main"}}}`, ref: "refs/heads/main"}, {event: "pull_request_target", json: `{"pull_request":{"base":{"ref": "main"}}}`, ref: "refs/heads/main"},
{event: "deployment", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"}, {event: "deployment", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"},
{event: "deployment_status", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"}, {event: "deployment_status", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"},
{event: "release", json: `{"release": {"tag_name": "tag-name"}}`, ref: "tag-name"}, {event: "release", json: `{"release": {"tag_name": "tag-name"}}`, ref: "refs/tags/tag-name"},
} }
for _, data := range table { for _, data := range table {
@@ -467,6 +470,53 @@ func createJob(t *testing.T, input string, result string) *model.Job {
return job return job
} }
func TestRunContextRunsOnPlatformNames(t *testing.T) {
log.SetLevel(log.DebugLevel)
assertObject := assert.New(t)
rc := createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on: ubuntu-latest`, ""),
})
assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on: ${{ 'ubuntu-latest' }}`, ""),
})
assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on: [self-hosted, my-runner]`, ""),
})
assertObject.Equal([]string{"self-hosted", "my-runner"}, rc.runsOnPlatformNames(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on: [self-hosted, "${{ 'my-runner' }}"]`, ""),
})
assertObject.Equal([]string{"self-hosted", "my-runner"}, rc.runsOnPlatformNames(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on: ${{ fromJSON('["ubuntu-latest"]') }}`, ""),
})
assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
// test missing / invalid runs-on
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `name: something`, ""),
})
assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on:
mapping: value`, ""),
})
assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `runs-on: ${{ invalid expression }}`, ""),
})
assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
}
func TestRunContextIsEnabled(t *testing.T) { func TestRunContextIsEnabled(t *testing.T) {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
assertObject := assert.New(t) assertObject := assert.New(t)
@@ -569,6 +619,17 @@ if: always()`, ""),
}) })
rc.Run.JobID = "job2" rc.Run.JobID = "job2"
assertObject.True(rc.isEnabled(context.Background())) assertObject.True(rc.isEnabled(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `uses: ./.github/workflows/reusable.yml`, ""),
})
assertObject.True(rc.isEnabled(context.Background()))
rc = createIfTestRunContext(map[string]*model.Job{
"job1": createJob(t, `uses: ./.github/workflows/reusable.yml
if: false`, ""),
})
assertObject.False(rc.isEnabled(context.Background()))
} }
func TestRunContextGetEnv(t *testing.T) { func TestRunContextGetEnv(t *testing.T) {
@@ -621,24 +682,3 @@ func TestRunContextGetEnv(t *testing.T) {
}) })
} }
} }
func Test_createSimpleContainerName(t *testing.T) {
tests := []struct {
parts []string
want string
}{
{
parts: []string{"a--a", "BB正", "c-C"},
want: "a-a_BB_c-C",
},
{
parts: []string{"a-a", "", "-"},
want: "a-a",
},
}
for _, tt := range tests {
t.Run(strings.Join(tt.parts, " "), func(t *testing.T) {
assert.Equalf(t, tt.want, createSimpleContainerName(tt.parts...), "createSimpleContainerName(%v)", tt.parts)
})
}
}

Some files were not shown because too many files have changed in this diff Show More