diff --git a/Makefile b/Makefile index d9b32e5..81574af 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,14 @@ build: go build -o bin/gaw . +test: + go test ./... +pre-commit: test build + copy: build cp bin/gaw /usr/local/bin test-gh: act -W pkg/data/ -j imageTest image: - docker build . + docker build . -t ghcr.io/linuxsuren/github-action-workflow:dev +image-push: image + docker push ghcr.io/linuxsuren/github-action-workflow:dev diff --git a/cmd/convert_test.go b/cmd/convert_test.go index 3d44626..08f1f2e 100644 --- a/cmd/convert_test.go +++ b/cmd/convert_test.go @@ -187,6 +187,12 @@ metadata: name: build spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work diff --git a/cmd/data/combine-workflow.yaml b/cmd/data/combine-workflow.yaml index 40844ff..ec3dc80 100644 --- a/cmd/data/combine-workflow.yaml +++ b/cmd/data/combine-workflow.yaml @@ -6,6 +6,12 @@ metadata: name: simple spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work @@ -38,6 +44,12 @@ metadata: name: simple spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work diff --git a/cmd/data/one-workflow-env.yaml b/cmd/data/one-workflow-env.yaml index fdf6663..1be74c8 100644 --- a/cmd/data/one-workflow-env.yaml +++ b/cmd/data/one-workflow-env.yaml @@ -4,6 +4,12 @@ metadata: name: simple spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work diff --git a/cmd/data/one-workflow.yaml b/cmd/data/one-workflow.yaml index 7e9a97b..3a46dff 100644 --- a/cmd/data/one-workflow.yaml +++ b/cmd/data/one-workflow.yaml @@ -4,6 +4,12 @@ metadata: name: simple spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work diff --git a/go.mod b/go.mod index 7987992..f6d6f75 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/linuxsuren/github-action-workflow go 1.19 require ( + github.com/go-git/go-git/v5 v5.5.1 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 @@ -10,6 +11,9 @@ require ( ) require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect @@ -18,11 +22,14 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-git/go-git/v5 v5.5.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect diff --git a/go.sum b/go.sum index e1b1718..bd706e9 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,18 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= 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/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= @@ -16,15 +24,22 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= github.com/go-git/go-git/v5 v5.5.1 h1:5vtv2TB5PM/gPM+EvsHJ16hJh4uAkdGcKilcwY7FYwo= github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= @@ -35,14 +50,22 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -107,20 +130,22 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= diff --git a/pkg/argo-workflow.go b/pkg/argo-workflow.go index 3ffda1a..612430b 100644 --- a/pkg/argo-workflow.go +++ b/pkg/argo-workflow.go @@ -3,8 +3,9 @@ package pkg import ( "bytes" "fmt" - "html/template" + "github.com/Masterminds/sprig" "strings" + "text/template" ) func k8sStyleName(name string) (result string) { @@ -13,6 +14,55 @@ func k8sStyleName(name string) (result string) { return } +func (w *Workflow) GetWorkflowBindings() (wfbs []WorkflowEventBinding) { + projectName := w.GitRepository + if strings.Contains(projectName, "/") { + projectName = strings.TrimSuffix(projectName, ".git") + projectName = strings.Split(projectName, "/")[1] + } + + for _, e := range w.GetEvent() { + binding := WorkflowEventBinding{ + Selector: fmt.Sprintf(`payload.object_kind == "%s" && payload.project.path_with_namespace endsWith "%s"`, e, projectName), + Ref: w.Name, + Name: fmt.Sprintf("%s-%s", w.Name, e), + Parameters: map[string]string{}, + } + // read more about the expression from https://github.com/antonmedv/expr + if es, err := w.GetEventDetail(e); err == nil && es != nil { + if len(es.Branches) > 0 { + branchSelector := "(" + for _, b := range es.Branches { + branchSelector = branchSelector + getBranchSelector(e, b) + } + branchSelector = strings.TrimSuffix(branchSelector, " || ") + ")" + binding.Selector = binding.Selector + " && " + branchSelector + } + } + + switch e { + case "push": + binding.Parameters["branch"] = "payload.ref" + binding.Parameters["pr"] = "-1" + case "merge_request": + binding.Parameters["branch"] = "payload.object_attributes.source_branch" + binding.Parameters["pr"] = "payload.object_attributes.iid" + binding.Selector = binding.Selector + ` && payload.object_attributes.state == "opened"` + } + wfbs = append(wfbs, binding) + } + return +} + +func getBranchSelector(eventName, branch string) string { + switch eventName { + case "push": + return fmt.Sprintf(`payload.ref == "refs/heads/%s" || `, branch) + default: // it should be merge_request + return fmt.Sprintf("payload.object_attributes.target_branch == %s || ", branch) + } +} + func (w *Workflow) ConvertToArgoWorkflow() (output string, err error) { // pre-handle defaultImage := "alpine" @@ -20,18 +70,25 @@ func (w *Workflow) ConvertToArgoWorkflow() (output string, err error) { for i := range w.Jobs { job := w.Jobs[i] job.Name = k8sStyleName(job.Name) + var newSteps []Step for j := range w.Jobs[i].Steps { w.Jobs[i].Steps[j].Name = k8sStyleName(w.Jobs[i].Steps[j].Name) if strings.HasPrefix(w.Jobs[i].Steps[j].Uses, "actions/checkout") { w.Jobs[i].Steps[j].Image = "alpine/git:v2.26.2" - w.Jobs[i].Steps[j].Run = fmt.Sprintf("git clone %s .", w.GitRepository) + w.Jobs[i].Steps[j].Run = fmt.Sprintf(`branch=$(echo {{workflow.parameters.branch}} | sed -e 's/refs\/heads\///g') +git clone --branch $branch %s . +if [ {{workflow.parameters.pr}} != -1 ]; then + git fetch origin merge-requests/{{workflow.parameters.pr}}/head:mr-{{workflow.parameters.pr}} + git checkout mr-{{workflow.parameters.pr}} +fi`, w.GitRepository) } else if strings.HasPrefix(w.Jobs[i].Steps[j].Uses, "actions/setup-go") { defaultImage = "golang:1.19" if ver, ok := w.Jobs[i].Steps[j].With["go-version"]; ok { defaultImage = fmt.Sprintf("golang:%s", ver) } + continue } else if strings.HasPrefix(w.Jobs[i].Steps[j].Uses, "goreleaser/goreleaser-action") { w.Jobs[i].Steps[j].Image = "goreleaser/goreleaser:v1.13.1" w.Jobs[i].Steps[j].Run = "goreleaser " + w.Jobs[i].Steps[j].With["args"] @@ -40,26 +97,146 @@ func (w *Workflow) ConvertToArgoWorkflow() (output string, err error) { w.Jobs[i].Steps[j].Run = w.Jobs[i].Steps[j].With["args"] } else if w.Jobs[i].Steps[j].Uses != "" { // TODO not support yet, do nothing + continue } else { w.Jobs[i].Steps[j].Image = defaultImage } w.Jobs[i].Steps[j].Run = strings.TrimSpace(w.Jobs[i].Steps[j].Run) + newSteps = append(newSteps, w.Jobs[i].Steps[j]) } + // make sure a correct depends order + for j := 1; j < len(newSteps); j++ { + newSteps[j].Depends = newSteps[j-1].Name + } + (&job).Steps = newSteps + w.Jobs[i] = job + // TODO currently we can only handle one job break } + // generate workflowTemplate var t *template.Template - t, err = template.New("argo").Parse(argoworkflowTemplate) - + t, err = template.New("argo").Funcs(sprig.FuncMap()).Parse(argoworkflowTemplate) data := bytes.NewBuffer([]byte{}) if err = t.Execute(data, w); err == nil { output = strings.TrimSpace(data.String()) } + + // generate workflowEventBinding + for _, binding := range w.GetWorkflowBindings() { + t, err = template.New("argo").Funcs(sprig.FuncMap()).Parse(argoworkflowEventBinding) + data := bytes.NewBuffer([]byte{}) + if err = t.Execute(data, binding); err == nil { + output = output + "\n---\n" + strings.TrimSpace(data.String()) + } + } return } +type WorkflowEventBinding struct { + Name string + Ref string + Selector string + Parameters map[string]string +} + +var argoworkflowEventBinding = ` +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowEventBinding +metadata: + name: {{.Name}} +spec: + event: + selector: {{.Selector}} + submit: + workflowTemplateRef: + name: {{.Ref}} + {{- if .Parameters}} + arguments: + parameters: + {{- range $key, $val := .Parameters}} + - name: {{$key}} + valueFrom: + event: "{{$val}}" + {{- end}} + {{- end}} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: submit-workflow-template +rules: + - apiGroups: + - argoproj.io + resources: + - workfloweventbindings + verbs: + - list + - apiGroups: + - argoproj.io + resources: + - workflowtemplates + verbs: + - get + - apiGroups: + - argoproj.io + resources: + - workflows + verbs: + - create +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: github.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: github.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: github.com + namespace: default +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gitlab.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gitlab.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: gitlab.com + namespace: default +--- +kind: Secret +apiVersion: v1 +metadata: + name: argo-workflows-webhook-clients +stringData: + bitbucket.org: | + type: bitbucket + bitbucketserver: | + type: bitbucketserver + github.com: | + type: github + gitlab.com: | + type: gitlab` + var argoworkflowTemplate = ` apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate @@ -67,6 +244,12 @@ metadata: name: {{.Name}} spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work @@ -85,6 +268,9 @@ spec: {{- if $step.Image}} - name: {{$step.Name}} template: {{$step.Name}} + {{- if $step.Depends}} + depends: {{$step.Depends}} + {{- end}} {{- end}} {{- end}} {{- end}} @@ -93,6 +279,13 @@ spec: {{- range $i, $step := $job.Steps}} {{- if $step.Image}} - name: {{$step.Name}} + {{- if $step.Secret}} + volumes: + - name: {{$step.Secret}} + secret: + defaultMode: 0400 + secretName: {{$step.Secret}} + {{- end}} script: image: {{$step.Image}} command: [sh] @@ -104,10 +297,14 @@ spec: {{- end}} {{- end}} source: | - {{$step.Run}} +{{indent 10 $step.Run}} volumeMounts: - mountPath: /work name: work + {{- if $step.Secret}} + - mountPath: /root/.ssh/ + name: {{$step.Secret}} + {{- end}} workingDir: /work {{- end}} {{- end}} diff --git a/pkg/argo-workflow_test.go b/pkg/argo-workflow_test.go index 5586680..a0684ed 100644 --- a/pkg/argo-workflow_test.go +++ b/pkg/argo-workflow_test.go @@ -21,6 +21,10 @@ func TestWorkflow_ConvertToArgoWorkflow(t *testing.T) { name: "with image", githubActions: "data/github-action-image.yaml", argoWorkflows: "data/argo-workflows-image.yaml", + }, { + name: "complex event", + githubActions: "data/github-action-complex-event.yaml", + argoWorkflows: "data/argo-workflows-complex-event.yaml", }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/data/argo-workflows-complex-event.yaml b/pkg/data/argo-workflows-complex-event.yaml new file mode 100644 index 0000000..0557b30 --- /dev/null +++ b/pkg/data/argo-workflows-complex-event.yaml @@ -0,0 +1,223 @@ +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: imagetest +spec: + entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 + volumeClaimTemplates: + - metadata: + name: work + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 64Mi + + templates: + - name: main + dag: + tasks: + - name: test + template: test + - name: test + script: + image: alpine:3.8 + command: [sh] + source: | + echo 1 + volumeMounts: + - mountPath: /work + name: work + workingDir: /work +--- +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowEventBinding +metadata: + name: imagetest-push +spec: + event: + selector: payload.object_kind == "push" && payload.project.path_with_namespace endsWith "" && (payload.ref == "refs/heads/main" || payload.ref == "refs/heads/test") + submit: + workflowTemplateRef: + name: imagetest + arguments: + parameters: + - name: branch + valueFrom: + event: "payload.ref" + - name: pr + valueFrom: + event: "-1" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: submit-workflow-template +rules: + - apiGroups: + - argoproj.io + resources: + - workfloweventbindings + verbs: + - list + - apiGroups: + - argoproj.io + resources: + - workflowtemplates + verbs: + - get + - apiGroups: + - argoproj.io + resources: + - workflows + verbs: + - create +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: github.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: github.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: github.com + namespace: default +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gitlab.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gitlab.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: gitlab.com + namespace: default +--- +kind: Secret +apiVersion: v1 +metadata: + name: argo-workflows-webhook-clients +stringData: + bitbucket.org: | + type: bitbucket + bitbucketserver: | + type: bitbucketserver + github.com: | + type: github + gitlab.com: | + type: gitlab +--- +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowEventBinding +metadata: + name: imagetest-merge_request +spec: + event: + selector: payload.object_kind == "merge_request" && payload.project.path_with_namespace endsWith "" && (payload.object_attributes.target_branch == main) && payload.object_attributes.state == "opened" + submit: + workflowTemplateRef: + name: imagetest + arguments: + parameters: + - name: branch + valueFrom: + event: "payload.object_attributes.source_branch" + - name: pr + valueFrom: + event: "payload.object_attributes.iid" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: submit-workflow-template +rules: + - apiGroups: + - argoproj.io + resources: + - workfloweventbindings + verbs: + - list + - apiGroups: + - argoproj.io + resources: + - workflowtemplates + verbs: + - get + - apiGroups: + - argoproj.io + resources: + - workflows + verbs: + - create +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: github.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: github.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: github.com + namespace: default +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gitlab.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gitlab.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: gitlab.com + namespace: default +--- +kind: Secret +apiVersion: v1 +metadata: + name: argo-workflows-webhook-clients +stringData: + bitbucket.org: | + type: bitbucket + bitbucketserver: | + type: bitbucketserver + github.com: | + type: github + gitlab.com: | + type: gitlab \ No newline at end of file diff --git a/pkg/data/argo-workflows-image.yaml b/pkg/data/argo-workflows-image.yaml index 24d1d4c..e7875c6 100644 --- a/pkg/data/argo-workflows-image.yaml +++ b/pkg/data/argo-workflows-image.yaml @@ -4,6 +4,12 @@ metadata: name: imagetest spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work @@ -28,4 +34,97 @@ spec: volumeMounts: - mountPath: /work name: work - workingDir: /work \ No newline at end of file + workingDir: /work +--- +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowEventBinding +metadata: + name: imagetest-push +spec: + event: + selector: payload.object_kind == "push" && payload.project.path_with_namespace endsWith "" + submit: + workflowTemplateRef: + name: imagetest + arguments: + parameters: + - name: branch + valueFrom: + event: "payload.ref" + - name: pr + valueFrom: + event: "-1" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: submit-workflow-template +rules: + - apiGroups: + - argoproj.io + resources: + - workfloweventbindings + verbs: + - list + - apiGroups: + - argoproj.io + resources: + - workflowtemplates + verbs: + - get + - apiGroups: + - argoproj.io + resources: + - workflows + verbs: + - create +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: github.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: github.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: github.com + namespace: default +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gitlab.com +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gitlab.com +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: submit-workflow-template +subjects: + - kind: ServiceAccount + name: gitlab.com + namespace: default +--- +kind: Secret +apiVersion: v1 +metadata: + name: argo-workflows-webhook-clients +stringData: + bitbucket.org: | + type: bitbucket + bitbucketserver: | + type: bitbucketserver + github.com: | + type: github + gitlab.com: | + type: gitlab \ No newline at end of file diff --git a/pkg/data/argo-workflows.yaml b/pkg/data/argo-workflows.yaml index 26b87bf..dab219f 100644 --- a/pkg/data/argo-workflows.yaml +++ b/pkg/data/argo-workflows.yaml @@ -4,6 +4,12 @@ metadata: name: build spec: entrypoint: main + arguments: + parameters: + - name: branch + default: master + - name: pr + default: -1 volumeClaimTemplates: - metadata: name: work @@ -21,14 +27,21 @@ spec: template: clone - name: test template: test + depends: clone - name: goreleaser template: goreleaser + depends: test - name: clone script: image: alpine/git:v2.26.2 command: [sh] source: | - git clone https://gitee.com/LinuxSuRen/yaml-readme . + branch=$(echo {{workflow.parameters.branch}} | sed -e 's/refs\/heads\///g') + git clone --branch $branch https://gitee.com/LinuxSuRen/yaml-readme . + if [ {{workflow.parameters.pr}} != -1 ]; then + git fetch origin merge-requests/{{workflow.parameters.pr}}/head:mr-{{workflow.parameters.pr}} + git checkout mr-{{workflow.parameters.pr}} + fi volumeMounts: - mountPath: /work name: work diff --git a/pkg/data/github-action-complex-event.yaml b/pkg/data/github-action-complex-event.yaml new file mode 100644 index 0000000..a9b21f7 --- /dev/null +++ b/pkg/data/github-action-complex-event.yaml @@ -0,0 +1,20 @@ +name: imageTest + +on: + push: + branches: + - main + - test + merge_request: + branches: + - main + +jobs: + imageTest: + name: build + runs-on: ubuntu-20.04 + steps: + - name: test + uses: docker://alpine:3.8 + with: + args: echo 1 diff --git a/pkg/data/github-action-image.yaml b/pkg/data/github-action-image.yaml index 20eba86..3b93315 100644 --- a/pkg/data/github-action-image.yaml +++ b/pkg/data/github-action-image.yaml @@ -1,5 +1,7 @@ name: imageTest +on: push + jobs: imageTest: name: build diff --git a/pkg/data/github-actions.yaml b/pkg/data/github-actions.yaml index 920f94c..5848719 100644 --- a/pkg/data/github-actions.yaml +++ b/pkg/data/github-actions.yaml @@ -1,13 +1,5 @@ name: build -on: - push: - branches: - - master - pull_request: - branches: - - master - jobs: build: name: build diff --git a/pkg/github.go b/pkg/github.go index 544bb39..765ae73 100644 --- a/pkg/github.go +++ b/pkg/github.go @@ -1,13 +1,64 @@ package pkg +import ( + "gopkg.in/yaml.v2" +) + type Workflow struct { Name string + On interface{} // could be: string, []string, Event Jobs map[string]Job // extra fields GitRepository string } +func (w *Workflow) GetEventDetail(name string) (es *EventSource, err error) { + switch w.On.(type) { + case map[interface{}]interface{}: + raw := w.On.(map[interface{}]interface{}) + + if val, ok := raw[name]; ok { + var data []byte + if data, err = yaml.Marshal(val); err == nil { + es = &EventSource{} + err = yaml.Unmarshal(data, es) + } + } + } + return +} + +func (w *Workflow) GetEvent() (result []string) { + switch w.On.(type) { + case string: + result = []string{w.On.(string)} + case []interface{}: + for _, item := range w.On.([]interface{}) { + result = append(result, item.(string)) + } + case map[interface{}]interface{}: + for key := range w.On.(map[interface{}]interface{}) { + result = append(result, key.(string)) + } + } + return +} + +type Event struct { + Push EventSource + PullRequest EventSource `yaml:"pull_request"` + Schedule []string +} + +type EventSource struct { + Branches []string + Tags []string + Paths []string + PathsIgnore []string `yaml:"paths-ignore"` + BranchesIgnore []string `yaml:"branches-ignore"` +} + type Job struct { Name string RunsOn string `yaml:"runs-on"` @@ -23,5 +74,7 @@ type Step struct { ID string // extra fields - Image string + Image string + Depends string + Secret string } diff --git a/pkg/github_test.go b/pkg/github_test.go new file mode 100644 index 0000000..762b00e --- /dev/null +++ b/pkg/github_test.go @@ -0,0 +1,49 @@ +package pkg + +import ( + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + "testing" +) + +func TestEvent(t *testing.T) { + // single event + wf := &Workflow{} + err := yaml.Unmarshal([]byte(`on: push`), wf) + assert.Nil(t, err) + assert.EqualValues(t, []string{"push"}, wf.GetEvent()) + + // multiple events + wf = &Workflow{} + err = yaml.Unmarshal([]byte(`on: [push, fork]`), wf) + assert.Nil(t, err) + assert.EqualValues(t, []string{"push", "fork"}, wf.GetEvent()) + + // push event + wf = &Workflow{} + err = yaml.Unmarshal([]byte(` +on: + push: + branches: + - main + tags: + - 1.1 + paths: + - /work + paths-ignore: + - /bin + branches-ignore: + - bugfix`), wf) + assert.Nil(t, err) + assert.EqualValues(t, []string{"push"}, wf.GetEvent()) + var es *EventSource + es, err = wf.GetEventDetail("push") + assert.Nil(t, err) + assert.Equal(t, EventSource{ + Branches: []string{"main"}, + Tags: []string{"1.1"}, + Paths: []string{"/work"}, + PathsIgnore: []string{"/bin"}, + BranchesIgnore: []string{"bugfix"}, + }, *es) +}