Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0c1f2edb99 | ||
|
721857e4a0 | ||
|
6b1010ad07 | ||
|
e12252a43a | ||
|
8609522aa4 | ||
|
6a876c4f99 | ||
|
de529139af | ||
|
d3a56cdb69 | ||
|
9bdddf18e0 | ||
|
ac1ba34518 | ||
|
5c4a96bcb7 | ||
|
62abf4fe11 | ||
|
cfedc518ca | ||
|
5e76853b55 | ||
|
2eb4de02ee | ||
|
342ad6a51a | ||
|
568f053723 | ||
|
8f12a6c947 | ||
|
83fb85f702 | ||
|
3daf313205 | ||
|
7c5400d75b | ||
|
929ea6df75 | ||
|
f6a8a0e643 | ||
|
556fd20aed | ||
|
a8298365fe |
44
.gitea/workflows/test.yml
Normal file
44
.gitea/workflows/test.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: checks
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOPROXY: https://goproxy.io,direct
|
||||||
|
GOPATH: /go_path
|
||||||
|
GOCACHE: /go_cache
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: check and test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: cache go path
|
||||||
|
id: cache-go-path
|
||||||
|
uses: https://github.com/actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: /go_path
|
||||||
|
key: go_path-${{ github.repository }}-${{ github.ref_name }}
|
||||||
|
restore-keys: |
|
||||||
|
go_path-${{ github.repository }}-
|
||||||
|
go_path-
|
||||||
|
- name: cache go cache
|
||||||
|
id: cache-go-cache
|
||||||
|
uses: https://github.com/actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: /go_cache
|
||||||
|
key: go_cache-${{ github.repository }}-${{ github.ref_name }}
|
||||||
|
restore-keys: |
|
||||||
|
go_cache-${{ github.repository }}-
|
||||||
|
go_cache-
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.20
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: vet checks
|
||||||
|
run: go vet -v ./...
|
||||||
|
- name: build
|
||||||
|
run: go build -v ./...
|
||||||
|
- name: test
|
||||||
|
run: go test -v ./pkg/jobparser
|
||||||
|
# TODO test more packages
|
2
go.mod
2
go.mod
@@ -80,4 +80,4 @@ require (
|
|||||||
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
|
replace github.com/go-git/go-git/v5 => github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220224134545-c785af3f4559
|
||||||
|
4
go.sum
4
go.sum
@@ -14,8 +14,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
|
|||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad h1:K3cVQxnwoVf5R2XLZknct3+tJWocEuJUmF7ZGwB2FK8=
|
github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad h1:K3cVQxnwoVf5R2XLZknct3+tJWocEuJUmF7ZGwB2FK8=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220315170230-29ec1bc1e5db h1:b0xyxkCQ0PQEH7gFQ8D+xa9lb+bur6RgVsRBodaqza4=
|
github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220224134545-c785af3f4559 h1:N+UKYPjQ7xkYzbzKWUkDGW5XrhhQDMD4lkwRJCUUA8w=
|
||||||
github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220315170230-29ec1bc1e5db/go.mod h1:U7oc8MDRtQhVD6StooNkBMVsh/Y4J/2Vl36Mo4IclvM=
|
github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220224134545-c785af3f4559/go.mod h1:U7oc8MDRtQhVD6StooNkBMVsh/Y4J/2Vl36Mo4IclvM=
|
||||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||||
|
@@ -29,6 +29,8 @@ type NewContainerInput struct {
|
|||||||
|
|
||||||
// Gitea specific
|
// Gitea specific
|
||||||
AutoRemove bool
|
AutoRemove bool
|
||||||
|
|
||||||
|
NetworkAliases []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileEntry is a file to copy to a container
|
// FileEntry is a file to copy to a container
|
||||||
@@ -41,6 +43,7 @@ type FileEntry struct {
|
|||||||
// Container for managing docker run containers
|
// Container for managing docker run containers
|
||||||
type Container interface {
|
type Container interface {
|
||||||
Create(capAdd []string, capDrop []string) common.Executor
|
Create(capAdd []string, capDrop []string) common.Executor
|
||||||
|
ConnectToNetwork(name string) common.Executor
|
||||||
Copy(destPath string, files ...*FileEntry) common.Executor
|
Copy(destPath string, files ...*FileEntry) common.Executor
|
||||||
CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor
|
CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor
|
||||||
GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error)
|
GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error)
|
||||||
|
40
pkg/container/docker_network.go
Normal file
40
pkg/container/docker_network.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
|
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return cli.NetworkRemove(ctx, name)
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
|
|
||||||
"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"
|
||||||
@@ -46,6 +48,25 @@ func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
|
|||||||
return cr
|
return cr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cr *containerReference) ConnectToNetwork(name string) common.Executor {
|
||||||
|
return common.
|
||||||
|
NewDebugExecutor("%sdocker network connect %s %s", logPrefix, name, cr.input.Name).
|
||||||
|
Then(
|
||||||
|
common.NewPipelineExecutor(
|
||||||
|
cr.connect(),
|
||||||
|
cr.connectToNetwork(name, cr.input.NetworkAliases),
|
||||||
|
).IfNot(common.Dryrun),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *containerReference) connectToNetwork(name string, aliases []string) common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
return cr.cli.NetworkConnect(ctx, name, cr.input.Name, &networktypes.EndpointSettings{
|
||||||
|
Aliases: aliases,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// supportsContainerImagePlatform returns true if the underlying Docker server
|
// supportsContainerImagePlatform returns true if the underlying Docker server
|
||||||
// API version is 1.41 and beyond
|
// API version is 1.41 and beyond
|
||||||
func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) bool {
|
func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) bool {
|
||||||
|
@@ -55,3 +55,15 @@ func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor {
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -40,6 +40,12 @@ func (e *HostEnvironment) Create(capAdd []string, capDrop []string) common.Execu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *HostEnvironment) ConnectToNetwork(name string) common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *HostEnvironment) Close() common.Executor {
|
func (e *HostEnvironment) Close() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -23,6 +23,8 @@ type EvaluationEnvironment struct {
|
|||||||
Matrix map[string]interface{}
|
Matrix map[string]interface{}
|
||||||
Needs map[string]Needs
|
Needs map[string]Needs
|
||||||
Inputs map[string]interface{}
|
Inputs map[string]interface{}
|
||||||
|
|
||||||
|
Vars map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Needs struct {
|
type Needs struct {
|
||||||
@@ -152,6 +154,8 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
|
|||||||
switch strings.ToLower(variableNode.Name) {
|
switch strings.ToLower(variableNode.Name) {
|
||||||
case "github":
|
case "github":
|
||||||
return impl.env.Github, nil
|
return impl.env.Github, nil
|
||||||
|
case "gitea": // compatible with Gitea
|
||||||
|
return impl.env.Github, nil
|
||||||
case "env":
|
case "env":
|
||||||
return impl.env.Env, nil
|
return impl.env.Env, nil
|
||||||
case "job":
|
case "job":
|
||||||
@@ -179,6 +183,8 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
|
|||||||
return math.Inf(1), nil
|
return math.Inf(1), nil
|
||||||
case "nan":
|
case "nan":
|
||||||
return math.NaN(), nil
|
return math.NaN(), nil
|
||||||
|
case "vars":
|
||||||
|
return impl.env.Vars, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unavailable context: %s", variableNode.Name)
|
return nil, fmt.Errorf("Unavailable context: %s", variableNode.Name)
|
||||||
}
|
}
|
||||||
|
@@ -41,11 +41,12 @@ func NewInterpeter(
|
|||||||
jobs := run.Workflow.Jobs
|
jobs := run.Workflow.Jobs
|
||||||
jobNeeds := run.Job().Needs()
|
jobNeeds := run.Job().Needs()
|
||||||
|
|
||||||
using := map[string]map[string]map[string]string{}
|
using := map[string]exprparser.Needs{}
|
||||||
for _, need := range jobNeeds {
|
for _, need := range jobNeeds {
|
||||||
if v, ok := jobs[need]; ok {
|
if v, ok := jobs[need]; ok {
|
||||||
using[need] = map[string]map[string]string{
|
using[need] = exprparser.Needs{
|
||||||
"outputs": v.Outputs,
|
Outputs: v.Outputs,
|
||||||
|
Result: v.Result,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +62,8 @@ func NewInterpeter(
|
|||||||
Matrix: matrix,
|
Matrix: matrix,
|
||||||
Needs: using,
|
Needs: using,
|
||||||
Inputs: nil, // not supported yet
|
Inputs: nil, // not supported yet
|
||||||
|
|
||||||
|
Vars: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
config := exprparser.Config{
|
config := exprparser.Config{
|
||||||
|
@@ -36,8 +36,17 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ret []*SingleWorkflow
|
var ret []*SingleWorkflow
|
||||||
for id, job := range workflow.Jobs {
|
ids, jobs, err := workflow.jobs()
|
||||||
for _, matrix := range getMatrixes(origin.GetJob(id)) {
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid jobs: %w", err)
|
||||||
|
}
|
||||||
|
for i, id := range ids {
|
||||||
|
job := jobs[i]
|
||||||
|
matricxes, err := getMatrixes(origin.GetJob(id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getMatrixes: %w", err)
|
||||||
|
}
|
||||||
|
for _, matrix := range matricxes {
|
||||||
job := job.Clone()
|
job := job.Clone()
|
||||||
if job.Name == "" {
|
if job.Name == "" {
|
||||||
job.Name = id
|
job.Name = id
|
||||||
@@ -50,17 +59,18 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
|||||||
runsOn[i] = evaluator.Interpolate(v)
|
runsOn[i] = evaluator.Interpolate(v)
|
||||||
}
|
}
|
||||||
job.RawRunsOn = encodeRunsOn(runsOn)
|
job.RawRunsOn = encodeRunsOn(runsOn)
|
||||||
job.EraseNeeds() // there will be only one job in SingleWorkflow, it cannot have needs
|
swf := &SingleWorkflow{
|
||||||
ret = append(ret, &SingleWorkflow{
|
|
||||||
Name: workflow.Name,
|
Name: workflow.Name,
|
||||||
RawOn: workflow.RawOn,
|
RawOn: workflow.RawOn,
|
||||||
Env: workflow.Env,
|
Env: workflow.Env,
|
||||||
Jobs: map[string]*Job{id: job},
|
|
||||||
Defaults: workflow.Defaults,
|
Defaults: workflow.Defaults,
|
||||||
})
|
}
|
||||||
|
if err := swf.SetJob(id, job); err != nil {
|
||||||
|
return nil, fmt.Errorf("SetJob: %w", err)
|
||||||
|
}
|
||||||
|
ret = append(ret, swf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sortWorkflows(ret)
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +93,15 @@ type parseContext struct {
|
|||||||
|
|
||||||
type ParseOption func(c *parseContext)
|
type ParseOption func(c *parseContext)
|
||||||
|
|
||||||
func getMatrixes(job *model.Job) []map[string]interface{} {
|
func getMatrixes(job *model.Job) ([]map[string]interface{}, error) {
|
||||||
ret := job.GetMatrixes()
|
ret, err := job.GetMatrixes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetMatrixes: %w", err)
|
||||||
|
}
|
||||||
sort.Slice(ret, func(i, j int) bool {
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
return matrixName(ret[i]) < matrixName(ret[j])
|
return matrixName(ret[i]) < matrixName(ret[j])
|
||||||
})
|
})
|
||||||
return ret
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeMatrix(matrix map[string]interface{}) yaml.Node {
|
func encodeMatrix(matrix map[string]interface{}) yaml.Node {
|
||||||
@@ -135,19 +148,3 @@ func matrixName(m map[string]interface{}) string {
|
|||||||
|
|
||||||
return fmt.Sprintf("(%s)", strings.Join(vs, ", "))
|
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package jobparser
|
package jobparser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -13,9 +11,6 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed testdata
|
|
||||||
var f embed.FS
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -37,13 +32,26 @@ func TestParse(t *testing.T) {
|
|||||||
options: nil,
|
options: nil,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "has_with",
|
||||||
|
options: nil,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "has_secrets",
|
||||||
|
options: nil,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_step",
|
||||||
|
options: nil,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
content, err := f.ReadFile(filepath.Join("testdata", tt.name+".in.yaml"))
|
content := ReadTestdata(t, tt.name+".in.yaml")
|
||||||
require.NoError(t, err)
|
want := ReadTestdata(t, tt.name+".out.yaml")
|
||||||
want, err := f.ReadFile(filepath.Join("testdata", tt.name+".out.yaml"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
got, err := Parse(content, tt.options...)
|
got, err := Parse(content, tt.options...)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -57,7 +65,10 @@ func TestParse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
encoder := yaml.NewEncoder(builder)
|
encoder := yaml.NewEncoder(builder)
|
||||||
encoder.SetIndent(2)
|
encoder.SetIndent(2)
|
||||||
_ = encoder.Encode(v)
|
require.NoError(t, encoder.Encode(v))
|
||||||
|
id, job := v.Job()
|
||||||
|
assert.NotEmpty(t, id)
|
||||||
|
assert.NotNil(t, job)
|
||||||
}
|
}
|
||||||
assert.Equal(t, string(want), builder.String())
|
assert.Equal(t, string(want), builder.String())
|
||||||
})
|
})
|
||||||
|
@@ -12,17 +12,70 @@ type SingleWorkflow struct {
|
|||||||
Name string `yaml:"name,omitempty"`
|
Name string `yaml:"name,omitempty"`
|
||||||
RawOn yaml.Node `yaml:"on,omitempty"`
|
RawOn yaml.Node `yaml:"on,omitempty"`
|
||||||
Env map[string]string `yaml:"env,omitempty"`
|
Env map[string]string `yaml:"env,omitempty"`
|
||||||
Jobs map[string]*Job `yaml:"jobs,omitempty"`
|
RawJobs yaml.Node `yaml:"jobs,omitempty"`
|
||||||
Defaults Defaults `yaml:"defaults,omitempty"`
|
Defaults Defaults `yaml:"defaults,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SingleWorkflow) Job() (string, *Job) {
|
func (w *SingleWorkflow) Job() (string, *Job) {
|
||||||
for k, v := range w.Jobs {
|
ids, jobs, _ := w.jobs()
|
||||||
return k, v
|
if len(ids) >= 1 {
|
||||||
|
return ids[0], jobs[0]
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *SingleWorkflow) jobs() ([]string, []*Job, error) {
|
||||||
|
var ids []string
|
||||||
|
var jobs []*Job
|
||||||
|
expectKey := true
|
||||||
|
for _, item := range w.RawJobs.Content {
|
||||||
|
if expectKey {
|
||||||
|
if item.Kind != yaml.ScalarNode {
|
||||||
|
return nil, nil, fmt.Errorf("invalid job id: %v", item.Value)
|
||||||
|
}
|
||||||
|
ids = append(ids, item.Value)
|
||||||
|
expectKey = false
|
||||||
|
} else {
|
||||||
|
job := &Job{}
|
||||||
|
if err := item.Decode(job); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("yaml.Unmarshal: %w", err)
|
||||||
|
}
|
||||||
|
steps := make([]*Step, 0, len(job.Steps))
|
||||||
|
for _, s := range job.Steps {
|
||||||
|
if s != nil {
|
||||||
|
steps = append(steps, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
job.Steps = steps
|
||||||
|
jobs = append(jobs, job)
|
||||||
|
expectKey = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ids) != len(jobs) {
|
||||||
|
return nil, nil, fmt.Errorf("invalid jobs: %v", w.RawJobs.Value)
|
||||||
|
}
|
||||||
|
return ids, jobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SingleWorkflow) SetJob(id string, job *Job) error {
|
||||||
|
m := map[string]*Job{
|
||||||
|
id: job,
|
||||||
|
}
|
||||||
|
out, err := yaml.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
node := yaml.Node{}
|
||||||
|
if err := yaml.Unmarshal(out, &node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(node.Content) != 1 || node.Content[0].Kind != yaml.MappingNode {
|
||||||
|
return fmt.Errorf("can not set job: %q", out)
|
||||||
|
}
|
||||||
|
w.RawJobs = *node.Content[0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *SingleWorkflow) Marshal() ([]byte, error) {
|
func (w *SingleWorkflow) Marshal() ([]byte, error) {
|
||||||
return yaml.Marshal(w)
|
return yaml.Marshal(w)
|
||||||
}
|
}
|
||||||
@@ -41,6 +94,8 @@ type Job struct {
|
|||||||
Defaults Defaults `yaml:"defaults,omitempty"`
|
Defaults Defaults `yaml:"defaults,omitempty"`
|
||||||
Outputs map[string]string `yaml:"outputs,omitempty"`
|
Outputs map[string]string `yaml:"outputs,omitempty"`
|
||||||
Uses string `yaml:"uses,omitempty"`
|
Uses string `yaml:"uses,omitempty"`
|
||||||
|
With map[string]interface{} `yaml:"with,omitempty"`
|
||||||
|
RawSecrets yaml.Node `yaml:"secrets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) Clone() *Job {
|
func (j *Job) Clone() *Job {
|
||||||
@@ -61,6 +116,8 @@ func (j *Job) Clone() *Job {
|
|||||||
Defaults: j.Defaults,
|
Defaults: j.Defaults,
|
||||||
Outputs: j.Outputs,
|
Outputs: j.Outputs,
|
||||||
Uses: j.Uses,
|
Uses: j.Uses,
|
||||||
|
With: j.With,
|
||||||
|
RawSecrets: j.RawSecrets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +125,9 @@ func (j *Job) Needs() []string {
|
|||||||
return (&model.Job{RawNeeds: j.RawNeeds}).Needs()
|
return (&model.Job{RawNeeds: j.RawNeeds}).Needs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) EraseNeeds() {
|
func (j *Job) EraseNeeds() *Job {
|
||||||
j.RawNeeds = yaml.Node{}
|
j.RawNeeds = yaml.Node{}
|
||||||
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) RunsOn() []string {
|
func (j *Job) RunsOn() []string {
|
||||||
@@ -92,6 +150,9 @@ type Step struct {
|
|||||||
|
|
||||||
// String gets the name of step
|
// String gets the name of step
|
||||||
func (s *Step) String() string {
|
func (s *Step) String() string {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return (&model.Step{
|
return (&model.Step{
|
||||||
ID: s.ID,
|
ID: s.ID,
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
@@ -107,6 +168,7 @@ type ContainerSpec struct {
|
|||||||
Volumes []string `yaml:"volumes,omitempty"`
|
Volumes []string `yaml:"volumes,omitempty"`
|
||||||
Options string `yaml:"options,omitempty"`
|
Options string `yaml:"options,omitempty"`
|
||||||
Credentials map[string]string `yaml:"credentials,omitempty"`
|
Credentials map[string]string `yaml:"credentials,omitempty"`
|
||||||
|
Cmd []string `yaml:"cmd,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
@@ -125,8 +187,21 @@ type RunDefaults struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Name string
|
Name string
|
||||||
Acts map[string][]string
|
acts map[string][]string
|
||||||
|
schedules []map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) IsSchedule() bool {
|
||||||
|
return evt.schedules != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) Acts() map[string][]string {
|
||||||
|
return evt.acts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt *Event) Schedules() []map[string]string {
|
||||||
|
return evt.schedules
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
||||||
@@ -164,16 +239,23 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
|||||||
}
|
}
|
||||||
res := make([]*Event, 0, len(val))
|
res := make([]*Event, 0, len(val))
|
||||||
for k, v := range val {
|
for k, v := range val {
|
||||||
|
if v == nil {
|
||||||
|
res = append(res, &Event{
|
||||||
|
Name: k,
|
||||||
|
acts: map[string][]string{},
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
res = append(res, &Event{
|
res = append(res, &Event{
|
||||||
Name: k,
|
Name: k,
|
||||||
Acts: map[string][]string{},
|
acts: map[string][]string{},
|
||||||
})
|
})
|
||||||
case []string:
|
case []string:
|
||||||
res = append(res, &Event{
|
res = append(res, &Event{
|
||||||
Name: k,
|
Name: k,
|
||||||
Acts: map[string][]string{},
|
acts: map[string][]string{},
|
||||||
})
|
})
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
acts := make(map[string][]string, len(t))
|
acts := make(map[string][]string, len(t))
|
||||||
@@ -186,7 +268,10 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
|||||||
case []interface{}:
|
case []interface{}:
|
||||||
acts[act] = make([]string, len(b))
|
acts[act] = make([]string, len(b))
|
||||||
for i, v := range b {
|
for i, v := range b {
|
||||||
acts[act][i] = v.(string)
|
var ok bool
|
||||||
|
if acts[act][i], ok = v.(string); !ok {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", branches)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown on type: %#v", branches)
|
return nil, fmt.Errorf("unknown on type: %#v", branches)
|
||||||
@@ -194,7 +279,29 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) {
|
|||||||
}
|
}
|
||||||
res = append(res, &Event{
|
res = append(res, &Event{
|
||||||
Name: k,
|
Name: k,
|
||||||
Acts: acts,
|
acts: acts,
|
||||||
|
})
|
||||||
|
case []interface{}:
|
||||||
|
if k != "schedule" {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
}
|
||||||
|
schedules := make([]map[string]string, len(t))
|
||||||
|
for i, tt := range t {
|
||||||
|
vv, ok := tt.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
}
|
||||||
|
schedules[i] = make(map[string]string, len(vv))
|
||||||
|
for k, vvv := range vv {
|
||||||
|
var ok bool
|
||||||
|
if schedules[i][k], ok = vvv.(string); !ok {
|
||||||
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = append(res, &Event{
|
||||||
|
Name: k,
|
||||||
|
schedules: schedules,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown on type: %#v", v)
|
return nil, fmt.Errorf("unknown on type: %#v", v)
|
||||||
|
@@ -6,7 +6,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseRawOn(t *testing.T) {
|
func TestParseRawOn(t *testing.T) {
|
||||||
@@ -47,7 +50,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "push",
|
Name: "push",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"branches": {
|
"branches": {
|
||||||
"master",
|
"master",
|
||||||
},
|
},
|
||||||
@@ -60,7 +63,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "branch_protection_rule",
|
Name: "branch_protection_rule",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"types": {
|
"types": {
|
||||||
"created",
|
"created",
|
||||||
"deleted",
|
"deleted",
|
||||||
@@ -74,7 +77,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "project",
|
Name: "project",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"types": {
|
"types": {
|
||||||
"created",
|
"created",
|
||||||
"deleted",
|
"deleted",
|
||||||
@@ -83,7 +86,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "milestone",
|
Name: "milestone",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"types": {
|
"types": {
|
||||||
"opened",
|
"opened",
|
||||||
"deleted",
|
"deleted",
|
||||||
@@ -97,7 +100,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "pull_request",
|
Name: "pull_request",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"types": {
|
"types": {
|
||||||
"opened",
|
"opened",
|
||||||
},
|
},
|
||||||
@@ -113,7 +116,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "push",
|
Name: "push",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"branches": {
|
"branches": {
|
||||||
"main",
|
"main",
|
||||||
},
|
},
|
||||||
@@ -121,7 +124,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "pull_request",
|
Name: "pull_request",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"types": {
|
"types": {
|
||||||
"opened",
|
"opened",
|
||||||
},
|
},
|
||||||
@@ -137,7 +140,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "push",
|
Name: "push",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"branches": {
|
"branches": {
|
||||||
"main",
|
"main",
|
||||||
"releases/**",
|
"releases/**",
|
||||||
@@ -151,7 +154,7 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
result: []*Event{
|
result: []*Event{
|
||||||
{
|
{
|
||||||
Name: "push",
|
Name: "push",
|
||||||
Acts: map[string][]string{
|
acts: map[string][]string{
|
||||||
"tags": {
|
"tags": {
|
||||||
"v1.**",
|
"v1.**",
|
||||||
},
|
},
|
||||||
@@ -170,6 +173,19 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "on:\n schedule:\n - cron: '20 6 * * *'",
|
||||||
|
result: []*Event{
|
||||||
|
{
|
||||||
|
Name: "schedule",
|
||||||
|
schedules: []map[string]string{
|
||||||
|
{
|
||||||
|
"cron": "20 6 * * *",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, kase := range kases {
|
for _, kase := range kases {
|
||||||
t.Run(kase.input, func(t *testing.T) {
|
t.Run(kase.input, func(t *testing.T) {
|
||||||
@@ -182,3 +198,25 @@ func TestParseRawOn(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSingleWorkflow_SetJob(t *testing.T) {
|
||||||
|
t.Run("erase needs", func(t *testing.T) {
|
||||||
|
content := ReadTestdata(t, "erase_needs.in.yaml")
|
||||||
|
want := ReadTestdata(t, "erase_needs.out.yaml")
|
||||||
|
swf, err := Parse(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
builder := &strings.Builder{}
|
||||||
|
for _, v := range swf {
|
||||||
|
id, job := v.Job()
|
||||||
|
require.NoError(t, v.SetJob(id, job.EraseNeeds()))
|
||||||
|
|
||||||
|
if builder.Len() > 0 {
|
||||||
|
builder.WriteString("---\n")
|
||||||
|
}
|
||||||
|
encoder := yaml.NewEncoder(builder)
|
||||||
|
encoder.SetIndent(2)
|
||||||
|
require.NoError(t, encoder.Encode(v))
|
||||||
|
}
|
||||||
|
assert.Equal(t, string(want), builder.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
8
pkg/jobparser/testdata/empty_step.in.yaml
vendored
Normal file
8
pkg/jobparser/testdata/empty_step.in.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
name: job1
|
||||||
|
runs-on: linux
|
||||||
|
steps:
|
||||||
|
- run: echo job-1
|
||||||
|
-
|
7
pkg/jobparser/testdata/empty_step.out.yaml
vendored
Normal file
7
pkg/jobparser/testdata/empty_step.out.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
name: job1
|
||||||
|
runs-on: linux
|
||||||
|
steps:
|
||||||
|
- run: echo job-1
|
16
pkg/jobparser/testdata/erase_needs.in.yaml
vendored
Normal file
16
pkg/jobparser/testdata/erase_needs.in.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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]
|
23
pkg/jobparser/testdata/erase_needs.out.yaml
vendored
Normal file
23
pkg/jobparser/testdata/erase_needs.out.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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
|
2
pkg/jobparser/testdata/has_needs.out.yaml
vendored
2
pkg/jobparser/testdata/has_needs.out.yaml
vendored
@@ -10,6 +10,7 @@ name: test
|
|||||||
jobs:
|
jobs:
|
||||||
job2:
|
job2:
|
||||||
name: job2
|
name: job2
|
||||||
|
needs: job1
|
||||||
runs-on: linux
|
runs-on: linux
|
||||||
steps:
|
steps:
|
||||||
- run: uname -a
|
- run: uname -a
|
||||||
@@ -18,6 +19,7 @@ name: test
|
|||||||
jobs:
|
jobs:
|
||||||
job3:
|
job3:
|
||||||
name: job3
|
name: job3
|
||||||
|
needs: [job1, job2]
|
||||||
runs-on: linux
|
runs-on: linux
|
||||||
steps:
|
steps:
|
||||||
- run: uname -a
|
- run: uname -a
|
||||||
|
14
pkg/jobparser/testdata/has_secrets.in.yaml
vendored
Normal file
14
pkg/jobparser/testdata/has_secrets.in.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
name: job1
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
secrets:
|
||||||
|
secret: hideme
|
||||||
|
|
||||||
|
job2:
|
||||||
|
name: job2
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
secrets: inherit
|
16
pkg/jobparser/testdata/has_secrets.out.yaml
vendored
Normal file
16
pkg/jobparser/testdata/has_secrets.out.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
name: job1
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
secrets:
|
||||||
|
secret: hideme
|
||||||
|
---
|
||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job2:
|
||||||
|
name: job2
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
secrets: inherit
|
15
pkg/jobparser/testdata/has_with.in.yaml
vendored
Normal file
15
pkg/jobparser/testdata/has_with.in.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
name: job1
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
with:
|
||||||
|
package: service
|
||||||
|
|
||||||
|
job2:
|
||||||
|
name: job2
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
with:
|
||||||
|
package: module
|
17
pkg/jobparser/testdata/has_with.out.yaml
vendored
Normal file
17
pkg/jobparser/testdata/has_with.out.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
name: job1
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
with:
|
||||||
|
package: service
|
||||||
|
---
|
||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
job2:
|
||||||
|
name: job2
|
||||||
|
runs-on: linux
|
||||||
|
uses: .gitea/workflows/build.yml
|
||||||
|
with:
|
||||||
|
package: module
|
8
pkg/jobparser/testdata/multiple_jobs.in.yaml
vendored
8
pkg/jobparser/testdata/multiple_jobs.in.yaml
vendored
@@ -1,5 +1,9 @@
|
|||||||
name: test
|
name: test
|
||||||
jobs:
|
jobs:
|
||||||
|
zzz:
|
||||||
|
runs-on: linux
|
||||||
|
steps:
|
||||||
|
- run: echo zzz
|
||||||
job1:
|
job1:
|
||||||
runs-on: linux
|
runs-on: linux
|
||||||
steps:
|
steps:
|
||||||
@@ -12,3 +16,7 @@ jobs:
|
|||||||
runs-on: linux
|
runs-on: linux
|
||||||
steps:
|
steps:
|
||||||
- run: uname -a && go version
|
- run: uname -a && go version
|
||||||
|
aaa:
|
||||||
|
runs-on: linux
|
||||||
|
steps:
|
||||||
|
- run: uname -a && go version
|
||||||
|
16
pkg/jobparser/testdata/multiple_jobs.out.yaml
vendored
16
pkg/jobparser/testdata/multiple_jobs.out.yaml
vendored
@@ -1,4 +1,12 @@
|
|||||||
name: test
|
name: test
|
||||||
|
jobs:
|
||||||
|
zzz:
|
||||||
|
name: zzz
|
||||||
|
runs-on: linux
|
||||||
|
steps:
|
||||||
|
- run: echo zzz
|
||||||
|
---
|
||||||
|
name: test
|
||||||
jobs:
|
jobs:
|
||||||
job1:
|
job1:
|
||||||
name: job1
|
name: job1
|
||||||
@@ -21,3 +29,11 @@ jobs:
|
|||||||
runs-on: linux
|
runs-on: linux
|
||||||
steps:
|
steps:
|
||||||
- run: uname -a && go version
|
- run: uname -a && go version
|
||||||
|
---
|
||||||
|
name: test
|
||||||
|
jobs:
|
||||||
|
aaa:
|
||||||
|
name: aaa
|
||||||
|
runs-on: linux
|
||||||
|
steps:
|
||||||
|
- run: uname -a && go version
|
||||||
|
18
pkg/jobparser/testdata_test.go
Normal file
18
pkg/jobparser/testdata_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package jobparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed testdata
|
||||||
|
var testdata embed.FS
|
||||||
|
|
||||||
|
func ReadTestdata(t *testing.T, name string) []byte {
|
||||||
|
content, err := testdata.ReadFile(filepath.Join("testdata", name))
|
||||||
|
require.NoError(t, err)
|
||||||
|
return content
|
||||||
|
}
|
@@ -271,15 +271,13 @@ func (j *Job) Container() *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
|
||||||
@@ -290,16 +288,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
|
||||||
}
|
}
|
||||||
@@ -311,16 +307,14 @@ func (j *Job) RunsOn() []string {
|
|||||||
switch j.RawRunsOn.Kind {
|
switch j.RawRunsOn.Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
var val string
|
var val string
|
||||||
err := j.RawRunsOn.Decode(&val)
|
if !decodeNode(j.RawRunsOn, &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(j.RawRunsOn, &val) {
|
||||||
if err != nil {
|
return nil
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
@@ -330,8 +324,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
|
||||||
@@ -346,8 +340,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
|
||||||
}
|
}
|
||||||
@@ -358,7 +352,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()
|
||||||
@@ -409,7 +403,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,7 +448,7 @@ func (j *Job) GetMatrixes() []map[string]interface{} {
|
|||||||
} else {
|
} else {
|
||||||
matrixes = append(matrixes, make(map[string]interface{}))
|
matrixes = append(matrixes, make(map[string]interface{}))
|
||||||
}
|
}
|
||||||
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 {
|
||||||
@@ -506,8 +500,12 @@ func (j JobType) String() string {
|
|||||||
func (j *Job) Type() JobType {
|
func (j *Job) Type() JobType {
|
||||||
if strings.HasPrefix(j.Uses, "./.github/workflows") && (strings.HasSuffix(j.Uses, ".yml") || strings.HasSuffix(j.Uses, ".yaml")) {
|
if strings.HasPrefix(j.Uses, "./.github/workflows") && (strings.HasSuffix(j.Uses, ".yml") || strings.HasSuffix(j.Uses, ".yaml")) {
|
||||||
return JobTypeReusableWorkflowLocal
|
return JobTypeReusableWorkflowLocal
|
||||||
|
} else if strings.HasPrefix(j.Uses, "./.gitea/workflows") && (strings.HasSuffix(j.Uses, ".yml") || strings.HasSuffix(j.Uses, ".yaml")) {
|
||||||
|
return JobTypeReusableWorkflowLocal
|
||||||
} else if !strings.HasPrefix(j.Uses, "./") && strings.Contains(j.Uses, ".github/workflows") && (strings.Contains(j.Uses, ".yml@") || strings.Contains(j.Uses, ".yaml@")) {
|
} else if !strings.HasPrefix(j.Uses, "./") && strings.Contains(j.Uses, ".github/workflows") && (strings.Contains(j.Uses, ".yml@") || strings.Contains(j.Uses, ".yaml@")) {
|
||||||
return JobTypeReusableWorkflowRemote
|
return JobTypeReusableWorkflowRemote
|
||||||
|
} else if !strings.HasPrefix(j.Uses, "./") && strings.Contains(j.Uses, ".gitea/workflows") && (strings.Contains(j.Uses, ".yml@") || strings.Contains(j.Uses, ".yaml@")) {
|
||||||
|
return JobTypeReusableWorkflowRemote
|
||||||
}
|
}
|
||||||
return JobTypeDefault
|
return JobTypeDefault
|
||||||
}
|
}
|
||||||
@@ -524,6 +522,9 @@ type ContainerSpec struct {
|
|||||||
Args string
|
Args string
|
||||||
Name string
|
Name string
|
||||||
Reuse bool
|
Reuse bool
|
||||||
|
|
||||||
|
// Gitea specific
|
||||||
|
Cmd []string `yaml:"cmd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step is the structure of one step in a job
|
// Step is the structure of one step in a job
|
||||||
@@ -695,3 +696,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
|
||||||
|
}
|
||||||
|
@@ -332,25 +332,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"},
|
||||||
|
@@ -176,6 +176,8 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc.ApplyExtraPath(ctx, step.getEnv())
|
||||||
|
|
||||||
execFileName := fmt.Sprintf("%s.out", action.Runs.Main)
|
execFileName := fmt.Sprintf("%s.out", action.Runs.Main)
|
||||||
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Main}
|
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Main}
|
||||||
execArgs := []string{filepath.Join(containerActionDir, execFileName)}
|
execArgs := []string{filepath.Join(containerActionDir, execFileName)}
|
||||||
@@ -554,6 +556,8 @@ func runPreStep(step actionStep) common.Executor {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc.ApplyExtraPath(ctx, step.getEnv())
|
||||||
|
|
||||||
execFileName := fmt.Sprintf("%s.out", action.Runs.Pre)
|
execFileName := fmt.Sprintf("%s.out", action.Runs.Pre)
|
||||||
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Pre}
|
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Pre}
|
||||||
execArgs := []string{filepath.Join(containerActionDir, execFileName)}
|
execArgs := []string{filepath.Join(containerActionDir, execFileName)}
|
||||||
@@ -657,6 +661,7 @@ func runPostStep(step actionStep) common.Executor {
|
|||||||
|
|
||||||
case model.ActionRunsUsingGo:
|
case model.ActionRunsUsingGo:
|
||||||
populateEnvsFromSavedState(step.getEnv(), step, rc)
|
populateEnvsFromSavedState(step.getEnv(), step, rc)
|
||||||
|
rc.ApplyExtraPath(ctx, step.getEnv())
|
||||||
|
|
||||||
execFileName := fmt.Sprintf("%s.out", action.Runs.Post)
|
execFileName := fmt.Sprintf("%s.out", action.Runs.Post)
|
||||||
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Post}
|
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Post}
|
||||||
|
@@ -77,7 +77,8 @@ func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
|
|||||||
logger.Infof(" \U00002753 %s", line)
|
logger.Infof(" \U00002753 %s", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// return true to let gitea's logger handle these special outputs also
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -81,6 +81,8 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
|
|||||||
Matrix: rc.Matrix,
|
Matrix: rc.Matrix,
|
||||||
Needs: using,
|
Needs: using,
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
|
|
||||||
|
Vars: rc.getVarsContext(),
|
||||||
}
|
}
|
||||||
if rc.JobContainer != nil {
|
if rc.JobContainer != nil {
|
||||||
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
||||||
@@ -130,6 +132,8 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
|||||||
// 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,
|
||||||
|
|
||||||
|
Vars: rc.getVarsContext(),
|
||||||
}
|
}
|
||||||
if rc.JobContainer != nil {
|
if rc.JobContainer != nil {
|
||||||
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
||||||
|
@@ -70,7 +70,19 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
|||||||
return common.NewErrorExecutor(err)
|
return common.NewErrorExecutor(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preSteps = append(preSteps, useStepLogger(rc, stepModel, stepStagePre, step.pre()))
|
preExec := step.pre()
|
||||||
|
preSteps = append(preSteps, useStepLogger(rc, stepModel, stepStagePre, func(ctx context.Context) error {
|
||||||
|
logger := common.Logger(ctx)
|
||||||
|
preErr := preExec(ctx)
|
||||||
|
if preErr != nil {
|
||||||
|
logger.Errorf("%v", preErr)
|
||||||
|
common.SetJobError(ctx, preErr)
|
||||||
|
} else if ctx.Err() != nil {
|
||||||
|
logger.Errorf("%v", ctx.Err())
|
||||||
|
common.SetJobError(ctx, ctx.Err())
|
||||||
|
}
|
||||||
|
return preErr
|
||||||
|
}))
|
||||||
|
|
||||||
stepExec := step.main()
|
stepExec := step.main()
|
||||||
steps = append(steps, useStepLogger(rc, stepModel, stepStageMain, func(ctx context.Context) error {
|
steps = append(steps, useStepLogger(rc, stepModel, stepStageMain, func(ctx context.Context) error {
|
||||||
@@ -102,7 +114,21 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
|||||||
// always allow 1 min for stopping and removing the runner, even if we were cancelled
|
// always allow 1 min for stopping and removing the runner, even if we were cancelled
|
||||||
ctx, cancel := context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), time.Minute)
|
ctx, cancel := context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
logger := common.Logger(ctx)
|
||||||
|
logger.Infof("Cleaning up services for job %s", rc.JobName)
|
||||||
|
if err := rc.stopServiceContainers()(ctx); err != nil {
|
||||||
|
logger.Errorf("Error while cleaning services: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Cleaning up container for job %s", rc.JobName)
|
||||||
err = info.stopContainer()(ctx)
|
err = info.stopContainer()(ctx)
|
||||||
|
|
||||||
|
logger.Infof("Cleaning up network for job %s", rc.JobName)
|
||||||
|
networkName := fmt.Sprintf("%s-network", rc.jobContainerName())
|
||||||
|
if err := rc.removeNetwork(networkName)(ctx); err != nil {
|
||||||
|
logger.Errorf("Error while cleaning network: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setJobResult(ctx, info, rc, jobError == nil)
|
setJobResult(ctx, info, rc, jobError == nil)
|
||||||
setJobOutputs(ctx, rc)
|
setJobOutputs(ctx, rc)
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/common"
|
"github.com/nektos/act/pkg/common"
|
||||||
@@ -16,23 +17,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newLocalReusableWorkflowExecutor(rc *RunContext) common.Executor {
|
func newLocalReusableWorkflowExecutor(rc *RunContext) common.Executor {
|
||||||
return newReusableWorkflowExecutor(rc, rc.Config.Workdir, rc.Run.Job().Uses)
|
// ./.gitea/workflows/wf.yml -> .gitea/workflows/wf.yml
|
||||||
}
|
trimmedUses := strings.TrimPrefix(rc.Run.Job().Uses, "./")
|
||||||
|
// uses string format is {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}
|
||||||
|
uses := fmt.Sprintf("%s/%s@%s", rc.Config.PresetGitHubContext.Repository, trimmedUses, rc.Config.PresetGitHubContext.Sha)
|
||||||
|
|
||||||
func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor {
|
remoteReusableWorkflow := newRemoteReusableWorkflowWithPlat(uses)
|
||||||
uses := rc.Run.Job().Uses
|
|
||||||
|
|
||||||
remoteReusableWorkflow := newRemoteReusableWorkflow(uses)
|
|
||||||
if remoteReusableWorkflow == nil {
|
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))
|
return common.NewErrorExecutor(fmt.Errorf("expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}. Actual '%s' Input string was not in a correct format", uses))
|
||||||
}
|
}
|
||||||
remoteReusableWorkflow.URL = rc.Config.GitHubInstance
|
remoteReusableWorkflow.URL = rc.Config.GitHubInstance
|
||||||
|
|
||||||
workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(uses))
|
workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(uses))
|
||||||
|
|
||||||
|
// If the repository is private, we need a token to clone it
|
||||||
|
token := rc.Config.GetToken()
|
||||||
|
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir)),
|
newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir, token)),
|
||||||
newReusableWorkflowExecutor(rc, workflowDir, fmt.Sprintf("./.github/workflows/%s", remoteReusableWorkflow.Filename)),
|
newReusableWorkflowExecutor(rc, workflowDir, remoteReusableWorkflow.FilePath()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor {
|
||||||
|
uses := rc.Run.Job().Uses
|
||||||
|
|
||||||
|
remoteReusableWorkflow := newRemoteReusableWorkflowWithPlat(uses)
|
||||||
|
if remoteReusableWorkflow == nil {
|
||||||
|
return common.NewErrorExecutor(fmt.Errorf("expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}. Actual '%s' Input string was not in a correct format", uses))
|
||||||
|
}
|
||||||
|
remoteReusableWorkflow.URL = rc.Config.GitHubInstance
|
||||||
|
|
||||||
|
workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(uses))
|
||||||
|
|
||||||
|
// FIXME: if the reusable workflow is from a private repository, we need to provide a token to access the repository.
|
||||||
|
token := ""
|
||||||
|
|
||||||
|
return common.NewPipelineExecutor(
|
||||||
|
newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir, token)),
|
||||||
|
newReusableWorkflowExecutor(rc, workflowDir, remoteReusableWorkflow.FilePath()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +72,7 @@ func newMutexExecutor(executor common.Executor) common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneIfRequired(rc *RunContext, remoteReusableWorkflow remoteReusableWorkflow, targetDirectory string) common.Executor {
|
func cloneIfRequired(rc *RunContext, remoteReusableWorkflow remoteReusableWorkflow, targetDirectory, token string) common.Executor {
|
||||||
return common.NewConditionalExecutor(
|
return common.NewConditionalExecutor(
|
||||||
func(ctx context.Context) bool {
|
func(ctx context.Context) bool {
|
||||||
_, err := os.Stat(targetDirectory)
|
_, err := os.Stat(targetDirectory)
|
||||||
@@ -60,7 +83,7 @@ func cloneIfRequired(rc *RunContext, remoteReusableWorkflow remoteReusableWorkfl
|
|||||||
URL: remoteReusableWorkflow.CloneURL(),
|
URL: remoteReusableWorkflow.CloneURL(),
|
||||||
Ref: remoteReusableWorkflow.Ref,
|
Ref: remoteReusableWorkflow.Ref,
|
||||||
Dir: targetDirectory,
|
Dir: targetDirectory,
|
||||||
Token: rc.Config.Token,
|
Token: token,
|
||||||
}),
|
}),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
@@ -105,12 +128,40 @@ type remoteReusableWorkflow struct {
|
|||||||
Repo string
|
Repo string
|
||||||
Filename string
|
Filename string
|
||||||
Ref string
|
Ref string
|
||||||
|
|
||||||
|
GitPlatform string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *remoteReusableWorkflow) CloneURL() string {
|
func (r *remoteReusableWorkflow) CloneURL() string {
|
||||||
|
// In Gitea, r.URL always has the protocol prefix, we don't need to add extra prefix in this case.
|
||||||
|
if strings.HasPrefix(r.URL, "http://") || strings.HasPrefix(r.URL, "https://") {
|
||||||
|
return fmt.Sprintf("%s/%s/%s", r.URL, r.Org, r.Repo)
|
||||||
|
}
|
||||||
return fmt.Sprintf("https://%s/%s/%s", r.URL, r.Org, r.Repo)
|
return fmt.Sprintf("https://%s/%s/%s", r.URL, r.Org, r.Repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *remoteReusableWorkflow) FilePath() string {
|
||||||
|
return fmt.Sprintf("./.%s/workflows/%s", r.GitPlatform, r.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteReusableWorkflowWithPlat(uses string) *remoteReusableWorkflow {
|
||||||
|
// GitHub docs:
|
||||||
|
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses
|
||||||
|
r := regexp.MustCompile(`^([^/]+)/([^/]+)/\.([^/]+)/workflows/([^@]+)@(.*)$`)
|
||||||
|
matches := r.FindStringSubmatch(uses)
|
||||||
|
if len(matches) != 6 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &remoteReusableWorkflow{
|
||||||
|
Org: matches[1],
|
||||||
|
Repo: matches[2],
|
||||||
|
GitPlatform: matches[3],
|
||||||
|
Filename: matches[4],
|
||||||
|
Ref: matches[5],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deprecated: use newRemoteReusableWorkflowWithPlat
|
||||||
func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow {
|
func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow {
|
||||||
// GitHub docs:
|
// GitHub docs:
|
||||||
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses
|
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses
|
||||||
|
@@ -18,6 +18,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/opencontainers/selinux/go-selinux"
|
"github.com/opencontainers/selinux/go-selinux"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -43,6 +44,7 @@ type RunContext struct {
|
|||||||
IntraActionState map[string]map[string]string
|
IntraActionState map[string]map[string]string
|
||||||
ExprEval ExpressionEvaluator
|
ExprEval ExpressionEvaluator
|
||||||
JobContainer container.ExecutionsEnvironment
|
JobContainer container.ExecutionsEnvironment
|
||||||
|
ServiceContainers []container.ExecutionsEnvironment
|
||||||
OutputMappings map[MappableOutput]MappableOutput
|
OutputMappings map[MappableOutput]MappableOutput
|
||||||
JobName string
|
JobName string
|
||||||
ActionPath string
|
ActionPath string
|
||||||
@@ -242,6 +244,50 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
ext := container.LinuxContainerEnvironmentExtensions{}
|
ext := container.LinuxContainerEnvironmentExtensions{}
|
||||||
binds, mounts := rc.GetBindsAndMounts()
|
binds, mounts := rc.GetBindsAndMounts()
|
||||||
|
|
||||||
|
// add service containers
|
||||||
|
for name, spec := range rc.Run.Job().Services {
|
||||||
|
// interpolate env
|
||||||
|
interpolatedEnvs := make(map[string]string, len(spec.Env))
|
||||||
|
for k, v := range spec.Env {
|
||||||
|
interpolatedEnvs[k] = rc.ExprEval.Interpolate(ctx, v)
|
||||||
|
}
|
||||||
|
envs := make([]string, 0, len(interpolatedEnvs))
|
||||||
|
for k, v := range interpolatedEnvs {
|
||||||
|
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
// interpolate cmd
|
||||||
|
interpolatedCmd := make([]string, 0, len(spec.Cmd))
|
||||||
|
for _, v := range spec.Cmd {
|
||||||
|
interpolatedCmd = append(interpolatedCmd, rc.ExprEval.Interpolate(ctx, v))
|
||||||
|
}
|
||||||
|
serviceContainerName := createSimpleContainerName(rc.jobContainerName(), name)
|
||||||
|
c := container.NewContainer(&container.NewContainerInput{
|
||||||
|
Name: serviceContainerName,
|
||||||
|
WorkingDir: ext.ToContainerPath(rc.Config.Workdir),
|
||||||
|
Image: spec.Image,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Cmd: interpolatedCmd,
|
||||||
|
Env: envs,
|
||||||
|
Mounts: map[string]string{
|
||||||
|
// TODO merge volumes
|
||||||
|
name: ext.ToContainerPath(rc.Config.Workdir),
|
||||||
|
"act-toolcache": "/toolcache",
|
||||||
|
"act-actions": "/actions",
|
||||||
|
},
|
||||||
|
Binds: binds,
|
||||||
|
Stdout: logWriter,
|
||||||
|
Stderr: logWriter,
|
||||||
|
Privileged: rc.Config.Privileged,
|
||||||
|
UsernsMode: rc.Config.UsernsMode,
|
||||||
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
|
AutoRemove: rc.Config.AutoRemove,
|
||||||
|
Options: spec.Options,
|
||||||
|
NetworkAliases: []string{name},
|
||||||
|
})
|
||||||
|
rc.ServiceContainers = append(rc.ServiceContainers, c)
|
||||||
|
}
|
||||||
|
|
||||||
rc.cleanUpJobContainer = func(ctx context.Context) error {
|
rc.cleanUpJobContainer = func(ctx context.Context) error {
|
||||||
if rc.JobContainer != nil && !rc.Config.ReuseContainers {
|
if rc.JobContainer != nil && !rc.Config.ReuseContainers {
|
||||||
return rc.JobContainer.Remove().
|
return rc.JobContainer.Remove().
|
||||||
@@ -275,11 +321,24 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
return errors.New("Failed to create job container")
|
return errors.New("Failed to create job container")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkName := fmt.Sprintf("%s-network", rc.jobContainerName())
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
|
rc.pullServicesImages(rc.Config.ForcePull),
|
||||||
rc.JobContainer.Pull(rc.Config.ForcePull),
|
rc.JobContainer.Pull(rc.Config.ForcePull),
|
||||||
|
rc.stopServiceContainers(),
|
||||||
rc.stopJobContainer(),
|
rc.stopJobContainer(),
|
||||||
|
func(ctx context.Context) error {
|
||||||
|
err := rc.removeNetwork(networkName)(ctx)
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
rc.createNetwork(networkName),
|
||||||
|
rc.startServiceContainers(networkName),
|
||||||
rc.JobContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
rc.JobContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
||||||
rc.JobContainer.Start(false),
|
rc.JobContainer.Start(false),
|
||||||
|
rc.JobContainer.ConnectToNetwork(networkName),
|
||||||
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
|
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
|
||||||
Name: "workflow/event.json",
|
Name: "workflow/event.json",
|
||||||
Mode: 0o644,
|
Mode: 0o644,
|
||||||
@@ -293,6 +352,18 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) createNetwork(name string) common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
return container.NewDockerNetworkCreateExecutor(name)(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) removeNetwork(name string) common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
return container.NewDockerNetworkRemoveExecutor(name)(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rc *RunContext) execJobContainer(cmd []string, env map[string]string, user, workdir string) common.Executor {
|
func (rc *RunContext) execJobContainer(cmd []string, env map[string]string, user, workdir string) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
return rc.JobContainer.Exec(cmd, env, user, workdir)(ctx)
|
return rc.JobContainer.Exec(cmd, env, user, workdir)(ctx)
|
||||||
@@ -354,6 +425,41 @@ func (rc *RunContext) stopJobContainer() common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) pullServicesImages(forcePull bool) common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
execs := []common.Executor{}
|
||||||
|
for _, c := range rc.ServiceContainers {
|
||||||
|
execs = append(execs, c.Pull(forcePull))
|
||||||
|
}
|
||||||
|
return common.NewParallelExecutor(len(execs), execs...)(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) startServiceContainers(networkName string) common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
execs := []common.Executor{}
|
||||||
|
for _, c := range rc.ServiceContainers {
|
||||||
|
execs = append(execs, common.NewPipelineExecutor(
|
||||||
|
c.Pull(false),
|
||||||
|
c.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
||||||
|
c.Start(false),
|
||||||
|
c.ConnectToNetwork(networkName),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return common.NewParallelExecutor(len(execs), execs...)(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) stopServiceContainers() common.Executor {
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
execs := []common.Executor{}
|
||||||
|
for _, c := range rc.ServiceContainers {
|
||||||
|
execs = append(execs, c.Remove())
|
||||||
|
}
|
||||||
|
return common.NewParallelExecutor(len(execs), execs...)(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare the mounts and binds for the worker
|
// Prepare the mounts and binds for the worker
|
||||||
|
|
||||||
// ActionCacheDir is for rc
|
// ActionCacheDir is for rc
|
||||||
@@ -589,6 +695,10 @@ func (rc *RunContext) getStepsContext() map[string]*model.StepResult {
|
|||||||
return rc.StepResults
|
return rc.StepResults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) getVarsContext() map[string]string {
|
||||||
|
return rc.Config.Vars
|
||||||
|
}
|
||||||
|
|
||||||
func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext {
|
func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
ghc := &model.GithubContext{
|
ghc := &model.GithubContext{
|
||||||
|
@@ -7,11 +7,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"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"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runner provides capabilities to run GitHub actions
|
// Runner provides capabilities to run GitHub actions
|
||||||
@@ -64,6 +64,15 @@ type Config struct {
|
|||||||
DefaultActionInstance string // the default actions web site
|
DefaultActionInstance string // the default actions web site
|
||||||
PlatformPicker func(labels []string) string // platform picker, it will take precedence over Platforms if isn't nil
|
PlatformPicker func(labels []string) string // platform picker, it will take precedence over Platforms if isn't nil
|
||||||
JobLoggerLevel *log.Level // the level of job logger
|
JobLoggerLevel *log.Level // the level of job logger
|
||||||
|
Vars map[string]string // the list of variables set at the repository, environment, or organization levels.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) GetToken() string {
|
||||||
|
token := c.Secrets["GITHUB_TOKEN"]
|
||||||
|
if c.Secrets["GITEA_TOKEN"] != "" {
|
||||||
|
token = c.Secrets["GITEA_TOKEN"]
|
||||||
|
}
|
||||||
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
type caller struct {
|
type caller struct {
|
||||||
@@ -128,7 +137,11 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
|
|||||||
log.Errorf("Error while evaluating matrix: %v", err)
|
log.Errorf("Error while evaluating matrix: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matrixes := job.GetMatrixes()
|
matrixes, err := job.GetMatrixes()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while get job's matrix: %v", err)
|
||||||
|
// fall back to empty matrixes
|
||||||
|
}
|
||||||
maxParallel := 4
|
maxParallel := 4
|
||||||
if job.Strategy != nil {
|
if job.Strategy != nil {
|
||||||
maxParallel = job.Strategy.MaxParallel
|
maxParallel = job.Strategy.MaxParallel
|
||||||
|
@@ -546,6 +546,43 @@ func TestRunEventSecrets(t *testing.T) {
|
|||||||
tjfi.runTest(context.Background(), t, &Config{Secrets: secrets, Env: env})
|
tjfi.runTest(context.Background(), t, &Config{Secrets: secrets, Env: env})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunWithService(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
platforms := map[string]string{
|
||||||
|
"ubuntu-latest": "node:12.20.1-buster-slim",
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowPath := "services"
|
||||||
|
eventName := "push"
|
||||||
|
|
||||||
|
workdir, err := filepath.Abs("testdata")
|
||||||
|
assert.NoError(t, err, workflowPath)
|
||||||
|
|
||||||
|
runnerConfig := &Config{
|
||||||
|
Workdir: workdir,
|
||||||
|
EventName: eventName,
|
||||||
|
Platforms: platforms,
|
||||||
|
ReuseContainers: false,
|
||||||
|
}
|
||||||
|
runner, err := New(runnerConfig)
|
||||||
|
assert.NoError(t, err, workflowPath)
|
||||||
|
|
||||||
|
planner, err := model.NewWorkflowPlanner(fmt.Sprintf("testdata/%s", workflowPath), true)
|
||||||
|
assert.NoError(t, err, workflowPath)
|
||||||
|
|
||||||
|
plan, err := planner.PlanEvent(eventName)
|
||||||
|
assert.NoError(t, err, workflowPath)
|
||||||
|
|
||||||
|
err = runner.NewPlanExecutor(plan)(ctx)
|
||||||
|
assert.NoError(t, err, workflowPath)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunActionInputs(t *testing.T) {
|
func TestRunActionInputs(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping integration test")
|
t.Skip("skipping integration test")
|
||||||
|
26
pkg/runner/testdata/services/push.yaml
vendored
Normal file
26
pkg/runner/testdata/services/push.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: services
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
services:
|
||||||
|
name: Reproduction of failing Services interpolation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:12
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: runner
|
||||||
|
POSTGRES_PASSWORD: mysecretdbpass
|
||||||
|
POSTGRES_DB: mydb
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
steps:
|
||||||
|
- name: Echo the Postgres service ID / Network / Ports
|
||||||
|
run: |
|
||||||
|
echo "id: ${{ job.services.postgres.id }}"
|
||||||
|
echo "network: ${{ job.services.postgres.network }}"
|
||||||
|
echo "ports: ${{ job.services.postgres.ports }}"
|
Reference in New Issue
Block a user