From 0b8e2d7c6546b38c242fbdb764881fdab9e78569 Mon Sep 17 00:00:00 2001 From: Tiexin Guo Date: Wed, 9 Feb 2022 15:20:10 +0800 Subject: [PATCH 1/2] refactor: splitting github actions plugin into different ones according to languages --- Makefile | 4 +- build/package/build_linux_amd64.sh | 4 +- cmd/githubactions/{ => golang}/main.go | 12 +- cmd/githubactions/nodejs/main.go | 39 +++ cmd/githubactions/python/main.go | 39 +++ examples/config.yaml | 2 +- internal/pkg/plugin/githubactions/common.go | 7 + .../pkg/plugin/githubactions/githubactions.go | 250 ------------------ .../githubactions/githubactions_suit_test.go | 13 - .../githubactions/githubactions_test.go | 9 - .../pkg/plugin/githubactions/golang/helper.go | 26 ++ .../plugin/githubactions/golang/install.go | 52 ++++ .../plugin/githubactions/golang/ishealthy.go | 61 +++++ .../plugin/githubactions/{ => golang}/jobs.go | 2 +- .../{validation.go => golang/options.go} | 18 +- .../plugin/githubactions/golang/reinstall.go | 58 ++++ .../pkg/plugin/githubactions/golang/render.go | 32 +++ .../plugin/githubactions/golang/templates.go | 82 +++++- .../plugin/githubactions/golang/uninstall.go | 45 ++++ .../plugin/githubactions/golang/workflow.go | 11 + internal/pkg/plugin/githubactions/install.go | 51 ---- .../pkg/plugin/githubactions/ishealthy.go | 35 --- .../pkg/plugin/githubactions/nodejs/helper.go | 26 ++ .../plugin/githubactions/nodejs/install.go | 35 +++ .../plugin/githubactions/nodejs/ishealthy.go | 45 ++++ .../plugin/githubactions/nodejs/options.go | 44 +++ .../plugin/githubactions/nodejs/reinstall.go | 41 +++ .../plugin/githubactions/nodejs/templates.go | 9 +- .../plugin/githubactions/nodejs/uninstall.go | 36 +++ .../plugin/githubactions/nodejs/workflow.go | 10 + .../pkg/plugin/githubactions/python/helper.go | 26 ++ .../plugin/githubactions/python/install.go | 35 +++ .../plugin/githubactions/python/ishealthy.go | 45 ++++ .../plugin/githubactions/python/options.go | 44 +++ .../plugin/githubactions/python/reinstall.go | 41 +++ .../plugin/githubactions/python/templates.go | 21 +- .../plugin/githubactions/python/uninstall.go | 36 +++ .../plugin/githubactions/python/workflow.go | 10 + .../pkg/plugin/githubactions/reinstall.go | 31 --- .../pkg/plugin/githubactions/uninstall.go | 26 -- internal/pkg/plugin/githubactions/workflow.go | 77 +----- pkg/util/github/github.go | 12 +- pkg/util/github/secrets.go | 27 ++ pkg/util/github/workflow.go | 143 ++++++++++ pkg/util/github/workflow_helper.go | 43 +++ 45 files changed, 1173 insertions(+), 542 deletions(-) rename cmd/githubactions/{ => golang}/main.go (84%) create mode 100644 cmd/githubactions/nodejs/main.go create mode 100644 cmd/githubactions/python/main.go create mode 100644 internal/pkg/plugin/githubactions/common.go delete mode 100644 internal/pkg/plugin/githubactions/githubactions.go delete mode 100644 internal/pkg/plugin/githubactions/githubactions_suit_test.go delete mode 100644 internal/pkg/plugin/githubactions/githubactions_test.go create mode 100644 internal/pkg/plugin/githubactions/golang/helper.go create mode 100644 internal/pkg/plugin/githubactions/golang/install.go create mode 100644 internal/pkg/plugin/githubactions/golang/ishealthy.go rename internal/pkg/plugin/githubactions/{ => golang}/jobs.go (97%) rename internal/pkg/plugin/githubactions/{validation.go => golang/options.go} (72%) create mode 100644 internal/pkg/plugin/githubactions/golang/reinstall.go create mode 100644 internal/pkg/plugin/githubactions/golang/render.go create mode 100644 internal/pkg/plugin/githubactions/golang/uninstall.go create mode 100644 internal/pkg/plugin/githubactions/golang/workflow.go delete mode 100644 internal/pkg/plugin/githubactions/install.go delete mode 100644 internal/pkg/plugin/githubactions/ishealthy.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/helper.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/install.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/ishealthy.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/options.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/reinstall.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/uninstall.go create mode 100644 internal/pkg/plugin/githubactions/nodejs/workflow.go create mode 100644 internal/pkg/plugin/githubactions/python/helper.go create mode 100644 internal/pkg/plugin/githubactions/python/install.go create mode 100644 internal/pkg/plugin/githubactions/python/ishealthy.go create mode 100644 internal/pkg/plugin/githubactions/python/options.go create mode 100644 internal/pkg/plugin/githubactions/python/reinstall.go create mode 100644 internal/pkg/plugin/githubactions/python/uninstall.go create mode 100644 internal/pkg/plugin/githubactions/python/workflow.go delete mode 100644 internal/pkg/plugin/githubactions/reinstall.go delete mode 100644 internal/pkg/plugin/githubactions/uninstall.go create mode 100644 pkg/util/github/workflow.go create mode 100644 pkg/util/github/workflow_helper.go diff --git a/Makefile b/Makefile index 7c502004c..c8df6aba7 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,9 @@ help: ## Display this help. build: fmt vet ## Build dtm & plugins locally. go mod tidy mkdir -p .devstream - go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/githubactions-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/ + go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/githubactions-golang-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/golang + go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/githubactions-python-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/python + go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/githubactions-nodejs-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/nodejs go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/trello-github-integ-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/trellogithub/ go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/argocd-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/argocd/ go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o .devstream/argocdapp-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/argocdapp/ diff --git a/build/package/build_linux_amd64.sh b/build/package/build_linux_amd64.sh index bbdcb6721..cec72a3ae 100755 --- a/build/package/build_linux_amd64.sh +++ b/build/package/build_linux_amd64.sh @@ -6,7 +6,9 @@ export GOOS=linux export GOARCH=amd64 export VERSION go build -trimpath -gcflags="all=-N -l" -o output/dtm-${GOOS}-${GOARCH} ./cmd/devstream/ -go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/githubactions-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/ +go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/githubactions-golang-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/golang +go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/githubactions-python-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/python +go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/githubactions-nodejs-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/githubactions/nodejs go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/trello-github-integ-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/trellogithub/ go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/argocd-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/argocd/ go build -buildmode=plugin -trimpath -gcflags="all=-N -l" -o output/argocdapp-${GOOS}-${GOARCH}_${VERSION}.so ./cmd/argocdapp/ diff --git a/cmd/githubactions/main.go b/cmd/githubactions/golang/main.go similarity index 84% rename from cmd/githubactions/main.go rename to cmd/githubactions/golang/main.go index 3781df3b5..b219455ae 100644 --- a/cmd/githubactions/main.go +++ b/cmd/githubactions/golang/main.go @@ -2,33 +2,33 @@ package main import ( "github.com/merico-dev/stream/internal/pkg/log" - "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/internal/pkg/plugin/githubactions/golang" ) // NAME is the name of this DevStream plugin. -const NAME = "githubactions" +const NAME = "githubactions-golang" // Plugin is the type used by DevStream core. It's a string. type Plugin string // Install implements the installation of some GitHub Actions workflows. func (p Plugin) Install(options *map[string]interface{}) (bool, error) { - return githubactions.Install(options) + return golang.Install(options) } // Reinstall implements the installation of some GitHub Actions workflows. func (p Plugin) Reinstall(options *map[string]interface{}) (bool, error) { - return githubactions.Reinstall(options) + return golang.Reinstall(options) } // Uninstall implements the installation of some GitHub Actions workflows. func (p Plugin) Uninstall(options *map[string]interface{}) (bool, error) { - return githubactions.Uninstall(options) + return golang.Uninstall(options) } // IsHealthy implements the healthy check of GitHub Actions workflows. func (p Plugin) IsHealthy(options *map[string]interface{}) (bool, error) { - return githubactions.IsHealthy(options) + return golang.IsHealthy(options) } // DevStreamPlugin is the exported variable used by the DevStream core. diff --git a/cmd/githubactions/nodejs/main.go b/cmd/githubactions/nodejs/main.go new file mode 100644 index 000000000..f172a9e12 --- /dev/null +++ b/cmd/githubactions/nodejs/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + "github.com/merico-dev/stream/internal/pkg/plugin/githubactions/nodejs" +) + +// NAME is the name of this DevStream plugin. +const NAME = "githubactions-nodejs" + +// Plugin is the type used by DevStream core. It's a string. +type Plugin string + +// Install implements the installation of some GitHub Actions workflows. +func (p Plugin) Install(options *map[string]interface{}) (bool, error) { + return nodejs.Install(options) +} + +// Reinstall implements the installation of some GitHub Actions workflows. +func (p Plugin) Reinstall(options *map[string]interface{}) (bool, error) { + return nodejs.Reinstall(options) +} + +// Uninstall implements the installation of some GitHub Actions workflows. +func (p Plugin) Uninstall(options *map[string]interface{}) (bool, error) { + return nodejs.Uninstall(options) +} + +// IsHealthy implements the healthy check of GitHub Actions workflows. +func (p Plugin) IsHealthy(options *map[string]interface{}) (bool, error) { + return nodejs.IsHealthy(options) +} + +// DevStreamPlugin is the exported variable used by the DevStream core. +var DevStreamPlugin Plugin + +func main() { + log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", NAME, DevStreamPlugin) +} diff --git a/cmd/githubactions/python/main.go b/cmd/githubactions/python/main.go new file mode 100644 index 000000000..929b5e5ed --- /dev/null +++ b/cmd/githubactions/python/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + "github.com/merico-dev/stream/internal/pkg/plugin/githubactions/python" +) + +// NAME is the name of this DevStream plugin. +const NAME = "githubactions-python" + +// Plugin is the type used by DevStream core. It's a string. +type Plugin string + +// Install implements the installation of some GitHub Actions workflows. +func (p Plugin) Install(options *map[string]interface{}) (bool, error) { + return python.Install(options) +} + +// Reinstall implements the installation of some GitHub Actions workflows. +func (p Plugin) Reinstall(options *map[string]interface{}) (bool, error) { + return python.Reinstall(options) +} + +// Uninstall implements the installation of some GitHub Actions workflows. +func (p Plugin) Uninstall(options *map[string]interface{}) (bool, error) { + return python.Uninstall(options) +} + +// IsHealthy implements the healthy check of GitHub Actions workflows. +func (p Plugin) IsHealthy(options *map[string]interface{}) (bool, error) { + return python.IsHealthy(options) +} + +// DevStreamPlugin is the exported variable used by the DevStream core. +var DevStreamPlugin Plugin + +func main() { + log.Infof("%T: %s is a plugin for DevStream. Use it with DevStream.\n", NAME, DevStreamPlugin) +} diff --git a/examples/config.yaml b/examples/config.yaml index 8fbc87091..c5dae3a76 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -12,7 +12,7 @@ tools: image_repo: ironcore864/golang-demo - name: golang-demo-app plugin: - kind: githubactions + kind: githubactions-golang version: 0.0.2 options: owner: ironcore864 diff --git a/internal/pkg/plugin/githubactions/common.go b/internal/pkg/plugin/githubactions/common.go new file mode 100644 index 000000000..8988f2ba1 --- /dev/null +++ b/internal/pkg/plugin/githubactions/common.go @@ -0,0 +1,7 @@ +package githubactions + +import "fmt" + +func GetLanguage(l *Language) string { + return fmt.Sprintf("%s-%s", l.Name, l.Version) +} diff --git a/internal/pkg/plugin/githubactions/githubactions.go b/internal/pkg/plugin/githubactions/githubactions.go deleted file mode 100644 index 787fcfd16..000000000 --- a/internal/pkg/plugin/githubactions/githubactions.go +++ /dev/null @@ -1,250 +0,0 @@ -package githubactions - -import ( - "bytes" - "context" - "fmt" - "net/http" - "strings" - "text/template" - - "github.com/google/go-github/v42/github" - "github.com/mitchellh/mapstructure" - "github.com/spf13/viper" - "golang.org/x/oauth2" - - "github.com/merico-dev/stream/internal/pkg/log" - "github.com/merico-dev/stream/pkg/util/mapz" - "github.com/merico-dev/stream/pkg/util/slicez" -) - -type GithubActions struct { - ctx context.Context - client *github.Client - options *Options -} - -func NewGithubActions(options *map[string]interface{}) (*GithubActions, error) { - ctx := context.Background() - - var opt Options - err := mapstructure.Decode(*options, &opt) - if err != nil { - return nil, err - } - - if errs := validate(&opt); len(errs) != 0 { - for _, e := range errs { - log.Errorf("Param error: %s", e) - } - return nil, fmt.Errorf("params are illegal") - } - - client, err := getGitHubClient(ctx) - if err != nil { - return nil, err - } - - return &GithubActions{ - ctx: ctx, - client: client, - options: &opt, - }, nil -} - -func (ga *GithubActions) GetLanguage() *Language { - return ga.options.Language -} - -func (ga *GithubActions) AddWorkflow(workflow *Workflow) error { - sha, err := ga.getFileSHA(workflow.workflowFileName) - if err != nil { - return err - } - if sha != "" { - log.Infof("GitHub Actions workflow %s already exists.", workflow.workflowFileName) - return nil - } - - // Note: the file needs to be absent from the repository as you are not - // specifying a SHA reference here. - opts := &github.RepositoryContentFileOptions{ - Message: github.String(workflow.commitMessage), - Content: []byte(workflow.workflowContent), - Branch: github.String(ga.options.Branch), - } - - log.Infof("Creating GitHub Actions workflow %s ...", workflow.workflowFileName) - _, _, err = ga.client.Repositories.CreateFile( - ga.ctx, - ga.options.Owner, - ga.options.Repo, - generateGitHubWorkflowFileByName(workflow.workflowFileName), - opts) - - if err != nil { - return err - } - log.Successf("Github Actions workflow %s created.", workflow.workflowFileName) - return nil -} - -func (ga *GithubActions) DeleteWorkflow(workflow *Workflow) error { - sha, err := ga.getFileSHA(workflow.workflowFileName) - if err != nil { - return err - } - if sha == "" { - log.Successf("Github Actions workflow %s already removed.", workflow.workflowFileName) - return nil - } - - // Note: the file needs to be absent from the repository as you are not - // specifying a SHA reference here. - opts := &github.RepositoryContentFileOptions{ - Message: github.String(workflow.commitMessage), - SHA: github.String(sha), - Branch: github.String(ga.options.Branch), - } - - log.Infof("Deleting GitHub Actions workflow %s ...", workflow.workflowFileName) - _, _, err = ga.client.Repositories.DeleteFile( - ga.ctx, - ga.options.Owner, - ga.options.Repo, - generateGitHubWorkflowFileByName(workflow.workflowFileName), - opts) - - if err != nil { - return err - } - log.Successf("GitHub Actions workflow %s removed.", workflow.workflowFileName) - return nil -} - -// VerifyWorkflows get the workflows with names "wf1.yml", "wf2.yml", then: -// If all workflows is ok => return ({"wf1.yml":nil, "wf2.yml:nil}, nil) -// If some error occurred => return (nil, error) -// If wf1.yml is not found => return ({"wf1.yml":error("not found"), "wf2.yml:nil},nil) -func (ga *GithubActions) VerifyWorkflows(workflows []*Workflow) (map[string]error, error) { - wsFiles := make([]string, 0) - for _, w := range workflows { - wsFiles = append(wsFiles, w.workflowFileName) - } - fmt.Printf("Workflow files: %v", wsFiles) - - _, dirContent, resp, err := ga.client.Repositories.GetContents( - ga.ctx, - ga.options.Owner, - ga.options.Repo, - ".github/workflows", - &github.RepositoryContentGetOptions{}, - ) - - // error reason is not 404 - if err != nil && !strings.Contains(err.Error(), "404") { - log.Errorf("GetContents failed with error: %s", err) - return nil, err - } - // StatusCode == 404 - if resp.StatusCode == http.StatusNotFound { - log.Errorf("GetContents return with status code 404") - retMap := mapz.FillMapWithStrAndError(wsFiles, fmt.Errorf("not found")) - return retMap, nil - } - // StatusCode != 200 - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("got some error is not expected: %s", resp.Status) - } - // StatusCode == 200 - log.Success("GetContents return with status code 200") - var filesInRemoteDir = make([]string, 0) - for _, f := range dirContent { - log.Infof("Found remote file: %s", f.GetName()) - filesInRemoteDir = append(filesInRemoteDir, f.GetName()) - } - - lostFiles := slicez.SliceInSliceStr(wsFiles, filesInRemoteDir) - // all files exist - if len(lostFiles) == 0 { - log.Info("All files exist") - retMap := mapz.FillMapWithStrAndError(wsFiles, nil) - return retMap, nil - } - // some files lost - log.Warn("Some files lost") - retMap := mapz.FillMapWithStrAndError(wsFiles, nil) - for _, f := range lostFiles { - log.Infof("Lost file: %s", f) - retMap[f] = fmt.Errorf("not found") - } - return retMap, nil -} - -// getFileSHA will try to collect the SHA hash value of the file, then return it. the return values will be: -// 1. If file exists without error -> string(SHA), nil -// 2. If some errors occurred -> return "", err -// 3. If file not found without error -> return "", nil -func (ga *GithubActions) getFileSHA(filename string) (string, error) { - content, _, resp, err := ga.client.Repositories.GetContents( - ga.ctx, - ga.options.Owner, - ga.options.Repo, - generateGitHubWorkflowFileByName(filename), - &github.RepositoryContentGetOptions{}, - ) - - // error reason is not 404 - if err != nil && !strings.Contains(err.Error(), "404") { - return "", err - } - - // error reason is 404 - if resp.StatusCode == http.StatusNotFound { - return "", nil - } - - // no error occurred - if resp.StatusCode == http.StatusOK { - return *content.SHA, nil - } - return "", fmt.Errorf("got some error is not expected") -} - -// renderTemplate render the github actions template with config.yaml -func (ga *GithubActions) renderTemplate(workflow *Workflow) error { - var jobs Jobs - err := mapstructure.Decode(ga.options.Jobs, &jobs) - if err != nil { - return err - } - //if use default {{.}}, it will confict (github actions vars also use them) - t, err := template.New("githubactions").Delims("[[", "]]").Parse(workflow.workflowContent) - if err != nil { - return err - } - - var buff bytes.Buffer - err = t.Execute(&buff, jobs) - if err != nil { - return err - } - workflow.workflowContent = buff.String() - return nil -} - -func generateGitHubWorkflowFileByName(f string) string { - return fmt.Sprintf(".github/workflows/%s", f) -} - -func getGitHubClient(ctx context.Context) (*github.Client, error) { - token := viper.GetString("github_token") - if token == "" { - return nil, fmt.Errorf("failed to initialize GitHub token. More info - https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token") - } - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: token}, - ) - tc := oauth2.NewClient(ctx, ts) - return github.NewClient(tc), nil -} diff --git a/internal/pkg/plugin/githubactions/githubactions_suit_test.go b/internal/pkg/plugin/githubactions/githubactions_suit_test.go deleted file mode 100644 index 362aae417..000000000 --- a/internal/pkg/plugin/githubactions/githubactions_suit_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package githubactions - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestPlanmanager(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Util Githubactions Suite") -} diff --git a/internal/pkg/plugin/githubactions/githubactions_test.go b/internal/pkg/plugin/githubactions/githubactions_test.go deleted file mode 100644 index 38d3a0226..000000000 --- a/internal/pkg/plugin/githubactions/githubactions_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package githubactions - -import ( - . "github.com/onsi/ginkgo/v2" -) - -var _ = Describe("Githubactions", func() { - // TODO(daniel-hutao): implement the unit tests -}) diff --git a/internal/pkg/plugin/githubactions/golang/helper.go b/internal/pkg/plugin/githubactions/golang/helper.go new file mode 100644 index 000000000..17dc6e551 --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/helper.go @@ -0,0 +1,26 @@ +package golang + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" + + "github.com/merico-dev/stream/internal/pkg/log" +) + +func parseAndValidateOptions(options *map[string]interface{}) (*Options, error) { + var opt Options + err := mapstructure.Decode(options, &opt) + if err != nil { + return nil, err + } + + if errs := validate(&opt); len(errs) != 0 { + for _, e := range errs { + log.Errorf("Param error: %s", e) + } + return nil, fmt.Errorf("incorrect params") + } + + return &opt, nil +} diff --git a/internal/pkg/plugin/githubactions/golang/install.go b/internal/pkg/plugin/githubactions/golang/install.go new file mode 100644 index 000000000..fa2b7e4c5 --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/install.go @@ -0,0 +1,52 @@ +package golang + +import ( + "github.com/spf13/viper" + + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Install sets up GitHub Actions workflow(s). +func Install(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("Language is: %s.", ga.GetLanguage(opt.Language)) + + // if docker is enabled, create repo secrets for DOCKERHUB_USERNAME and DOCKERHUB_TOKEN + if opt.Jobs.Docker.Enable { + if err := gitHubClient.AddRepoSecret("DOCKERHUB_USERNAME", viper.GetString("dockerhub_username")); err != nil { + return false, err + } + if err := gitHubClient.AddRepoSecret("DOCKERHUB_TOKEN", viper.GetString("dockerhub_token")); err != nil { + return false, err + } + } + + for _, w := range workflows { + content, err := renderTemplate(w, opt) + if err != nil { + return false, err + } + w.WorkflowContent = content + if err := gitHubClient.AddWorkflow(w, opt.Branch); err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/golang/ishealthy.go b/internal/pkg/plugin/githubactions/golang/ishealthy.go new file mode 100644 index 000000000..f0bcd7605 --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/ishealthy.go @@ -0,0 +1,61 @@ +package golang + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +func IsHealthy(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("Language is: %s.", ga.GetLanguage(opt.Language)) + + retMap, err := gitHubClient.VerifyWorkflows(workflows) + if err != nil { + return false, err + } + + errFlag := false + for name, err := range retMap { + if err != nil { + errFlag = true + log.Errorf("The workflow/file %s is not ok: %s", name, err) + } + log.Successf("The workflow/file %s is ok", name) + } + if errFlag { + return false, nil + } + + // if docker is enabled, verify if secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN are deleted + if opt.Jobs.Docker.Enable { + for _, secret := range []string{"DOCKERHUB_USERNAME", "DOCKERHUB_TOKEN"} { + exists, err := gitHubClient.RepoSecretExists(secret) + if err != nil { + return false, err + } + if !exists { + errFlag = true + log.Errorf("The secret %s doesn't exist.", secret) + } else { + log.Successf("The secret %s is ok", secret) + } + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/jobs.go b/internal/pkg/plugin/githubactions/golang/jobs.go similarity index 97% rename from internal/pkg/plugin/githubactions/jobs.go rename to internal/pkg/plugin/githubactions/golang/jobs.go index 035ef76de..62582b34d 100644 --- a/internal/pkg/plugin/githubactions/jobs.go +++ b/internal/pkg/plugin/githubactions/golang/jobs.go @@ -1,4 +1,4 @@ -package githubactions +package golang // Jobs is the struct for github actions custom config. type Jobs struct { diff --git a/internal/pkg/plugin/githubactions/validation.go b/internal/pkg/plugin/githubactions/golang/options.go similarity index 72% rename from internal/pkg/plugin/githubactions/validation.go rename to internal/pkg/plugin/githubactions/golang/options.go index 3550f57f0..a276f66dd 100644 --- a/internal/pkg/plugin/githubactions/validation.go +++ b/internal/pkg/plugin/githubactions/golang/options.go @@ -1,6 +1,20 @@ -package githubactions +package golang -import "fmt" +import ( + "fmt" + + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" +) + +// TODO(daniel-hutao): Options should keep as same as other plugins named Param +// Options is the struct for configurations of the githubactions plugin. +type Options struct { + Owner string + Repo string + Branch string + Language *ga.Language + Jobs *Jobs +} // validate validates the options provided by the core. func validate(param *Options) []error { diff --git a/internal/pkg/plugin/githubactions/golang/reinstall.go b/internal/pkg/plugin/githubactions/golang/reinstall.go new file mode 100644 index 000000000..4834995cd --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/reinstall.go @@ -0,0 +1,58 @@ +package golang + +import ( + "github.com/spf13/viper" + + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Reinstall remove and set up GitHub Actions workflows. +func Reinstall(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("language is %s", ga.GetLanguage(opt.Language)) + + if opt.Jobs.Docker.Enable { + for _, secret := range []string{"DOCKERHUB_USERNAME", "DOCKERHUB_TOKEN"} { + if err := gitHubClient.DeleteRepoSecret(secret); err != nil { + return false, err + } + } + + if err := gitHubClient.AddRepoSecret("DOCKERHUB_USERNAME", viper.GetString("dockerhub_username")); err != nil { + return false, err + } + if err := gitHubClient.AddRepoSecret("DOCKERHUB_TOKEN", viper.GetString("dockerhub_token")); err != nil { + return false, err + } + } + + for _, pipeline := range workflows { + err := gitHubClient.DeleteWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + + err = gitHubClient.AddWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/golang/render.go b/internal/pkg/plugin/githubactions/golang/render.go new file mode 100644 index 000000000..58d2b498a --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/render.go @@ -0,0 +1,32 @@ +package golang + +import ( + "bytes" + "html/template" + + "github.com/mitchellh/mapstructure" + + github "github.com/merico-dev/stream/pkg/util/github" +) + +func renderTemplate(workflow *github.Workflow, options *Options) (string, error) { + var jobs Jobs + err := mapstructure.Decode(options.Jobs, &jobs) + if err != nil { + return "", err + } + + //if use default {{.}}, it will confict (github actions vars also use them) + t, err := template.New("githubactions").Delims("[[", "]]").Parse(workflow.WorkflowContent) + if err != nil { + return "", err + } + + var buff bytes.Buffer + err = t.Execute(&buff, jobs) + if err != nil { + return "", err + } + + return buff.String(), nil +} diff --git a/internal/pkg/plugin/githubactions/golang/templates.go b/internal/pkg/plugin/githubactions/golang/templates.go index 7dc184a32..e61197181 100644 --- a/internal/pkg/plugin/githubactions/golang/templates.go +++ b/internal/pkg/plugin/githubactions/golang/templates.go @@ -1,9 +1,7 @@ package golang -var PipelineBuilder = `name: Pipeline Builder +var prPipeline = `name: Pull Requests Builder on: - push: - branches: [ master, main ] pull_request: branches: [ master, main ] jobs: @@ -25,15 +23,75 @@ jobs: go-version: 1.17 - name: Test run: [[.Test.Command]] [[- if .Test.Coverage.Enable]] -race -covermode=atomic -coverprofile=[[.Test.Coverage.Output]] [[- end]] -# TODO(ironcore): this is a bug. If it runs on main branch, it should not comment PR since there isn't any. -# [[- if .Test.Coverage.Enable]] -# - name: comment PR -# uses: machine-learning-apps/pr-comment@master -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# path: [[.Test.Coverage.Output]] -# [[- end]] + [[- if .Test.Coverage.Enable]] + - name: comment PR + uses: machine-learning-apps/pr-comment@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + path: [[.Test.Coverage.Output]] + [[- end]] + [[- end]] + tag: + name: Tag + needs: [test] + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-latest + outputs: + new_tag: ${{ steps.tag_version.outputs.new_tag }} + steps: + - uses: actions/checkout@v2 + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v5.6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + tag_prefix: "" + [[- if .Docker.Enable]] + image: + name: Build Docker Image + needs: [tag] + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/[[.Docker.Repo]]:${{needs.tag.outputs.new_tag}} + [[- end]] +` + +var mainPipeline = `name: Main Branch Builder +on: + push: + branches: [ master, main ] +jobs: + [[- if .Build.Enable]] + build: + runs-on: ubuntu-latest + steps: + - name: Build + run: [[.Build.Command]] + [[- end]] + [[- if .Test.Enable]] + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + - name: Test + run: [[.Test.Command]] [[- if .Test.Coverage.Enable]] -race -covermode=atomic -coverprofile=[[.Test.Coverage.Output]] [[- end]] [[- end]] tag: name: Tag diff --git a/internal/pkg/plugin/githubactions/golang/uninstall.go b/internal/pkg/plugin/githubactions/golang/uninstall.go new file mode 100644 index 000000000..3b06375c1 --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/uninstall.go @@ -0,0 +1,45 @@ +package golang + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Uninstall remove GitHub Actions workflows. +func Uninstall(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("language is %s", ga.GetLanguage(opt.Language)) + + // if docker is enabled, delete repo secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN + if opt.Jobs.Docker.Enable { + for _, secret := range []string{"DOCKERHUB_USERNAME", "DOCKERHUB_TOKEN"} { + if err := gitHubClient.DeleteRepoSecret(secret); err != nil { + return false, err + } + } + } + + for _, pipeline := range workflows { + err := gitHubClient.DeleteWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/golang/workflow.go b/internal/pkg/plugin/githubactions/golang/workflow.go new file mode 100644 index 000000000..12f524e84 --- /dev/null +++ b/internal/pkg/plugin/githubactions/golang/workflow.go @@ -0,0 +1,11 @@ +package golang + +import ( + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + github "github.com/merico-dev/stream/pkg/util/github" +) + +var workflows = []*github.Workflow{ + {CommitMessage: ga.CommitMessage, WorkflowFileName: ga.PRBuilderFileName, WorkflowContent: prPipeline}, + {CommitMessage: ga.CommitMessage, WorkflowFileName: ga.MainBuilderFileName, WorkflowContent: mainPipeline}, +} diff --git a/internal/pkg/plugin/githubactions/install.go b/internal/pkg/plugin/githubactions/install.go deleted file mode 100644 index f91df3c5e..000000000 --- a/internal/pkg/plugin/githubactions/install.go +++ /dev/null @@ -1,51 +0,0 @@ -package githubactions - -import ( - "github.com/spf13/viper" - - "github.com/merico-dev/stream/internal/pkg/log" - "github.com/merico-dev/stream/pkg/util/github" -) - -// Install sets up GitHub Actions workflows. -func Install(options *map[string]interface{}) (bool, error) { - githubActions, err := NewGithubActions(options) - if err != nil { - return false, err - } - - language := githubActions.GetLanguage() - log.Infof("Language is: %s.", language.String()) - - // if docker is enabled, create repo secrets for DOCKERHUB_USERNAME and DOCKERHUB_TOKEN - if githubActions.options.Jobs.Docker.Enable { - ghOptions := &github.Option{ - Owner: githubActions.options.Owner, - Repo: githubActions.options.Repo, - NeedAuth: true, - } - c, err := github.NewClient(ghOptions) - if err != nil { - return false, err - } - if err := c.AddRepoSecret("DOCKERHUB_USERNAME", viper.GetString("dockerhub_username")); err != nil { - return false, err - } - if err := c.AddRepoSecret("DOCKERHUB_TOKEN", viper.GetString("dockerhub_token")); err != nil { - return false, err - } - } - - ws := defaultWorkflows.GetWorkflowByNameVersionTypeString(language.String()) - - for _, w := range ws { - if err := githubActions.renderTemplate(w); err != nil { - return false, err - } - if err := githubActions.AddWorkflow(w); err != nil { - return false, err - } - } - - return true, nil -} diff --git a/internal/pkg/plugin/githubactions/ishealthy.go b/internal/pkg/plugin/githubactions/ishealthy.go deleted file mode 100644 index c9dcb5acb..000000000 --- a/internal/pkg/plugin/githubactions/ishealthy.go +++ /dev/null @@ -1,35 +0,0 @@ -package githubactions - -import ( - "github.com/merico-dev/stream/internal/pkg/log" -) - -func IsHealthy(options *map[string]interface{}) (bool, error) { - ghActions, err := NewGithubActions(options) - if err != nil { - return false, err - } - - language := ghActions.GetLanguage() - log.Infof("Language is: %s.", language.String()) - - ws := defaultWorkflows.GetWorkflowByNameVersionTypeString(language.String()) - retMap, err := ghActions.VerifyWorkflows(ws) - if err != nil { - return false, err - } - - errFlag := false - for name, err := range retMap { - if err != nil { - errFlag = true - log.Errorf("The workflow/file %s is not ok: %s", name, err) - } - log.Successf("The workflow/file %s is ok", name) - } - if errFlag { - return false, nil - } - - return true, nil -} diff --git a/internal/pkg/plugin/githubactions/nodejs/helper.go b/internal/pkg/plugin/githubactions/nodejs/helper.go new file mode 100644 index 000000000..01ab2df46 --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/helper.go @@ -0,0 +1,26 @@ +package nodejs + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" + + "github.com/merico-dev/stream/internal/pkg/log" +) + +func parseAndValidateOptions(options *map[string]interface{}) (*Options, error) { + var opt Options + err := mapstructure.Decode(options, &opt) + if err != nil { + return nil, err + } + + if errs := validate(&opt); len(errs) != 0 { + for _, e := range errs { + log.Errorf("Param error: %s", e) + } + return nil, fmt.Errorf("incorrect params") + } + + return &opt, nil +} diff --git a/internal/pkg/plugin/githubactions/nodejs/install.go b/internal/pkg/plugin/githubactions/nodejs/install.go new file mode 100644 index 000000000..7c6bf925d --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/install.go @@ -0,0 +1,35 @@ +package nodejs + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Install sets up GitHub Actions workflow(s). +func Install(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("Language is: %s.", ga.GetLanguage(opt.Language)) + + for _, w := range workflows { + if err := gitHubClient.AddWorkflow(w, opt.Branch); err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/nodejs/ishealthy.go b/internal/pkg/plugin/githubactions/nodejs/ishealthy.go new file mode 100644 index 000000000..93f9767bf --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/ishealthy.go @@ -0,0 +1,45 @@ +package nodejs + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +func IsHealthy(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("Language is: %s.", ga.GetLanguage(opt.Language)) + + retMap, err := gitHubClient.VerifyWorkflows(workflows) + if err != nil { + return false, err + } + + errFlag := false + for name, err := range retMap { + if err != nil { + errFlag = true + log.Errorf("The workflow/file %s is not ok: %s", name, err) + } + log.Successf("The workflow/file %s is ok", name) + } + if errFlag { + return false, nil + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/nodejs/options.go b/internal/pkg/plugin/githubactions/nodejs/options.go new file mode 100644 index 000000000..db2a4e285 --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/options.go @@ -0,0 +1,44 @@ +package nodejs + +import ( + "fmt" + + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" +) + +// TODO(daniel-hutao): Options should keep as same as other plugins named Param +// Options is the struct for configurations of the githubactions plugin. +type Options struct { + Owner string + Repo string + Branch string + Language *ga.Language +} + +// validate validates the options provided by the core. +func validate(param *Options) []error { + retErrors := make([]error, 0) + + // owner/repo/branch + if param.Owner == "" { + retErrors = append(retErrors, fmt.Errorf("owner is empty")) + } + if param.Repo == "" { + retErrors = append(retErrors, fmt.Errorf("repo is empty")) + } + if param.Branch == "" { + retErrors = append(retErrors, fmt.Errorf("branch is empty")) + } + + // language + if param.Language == nil { + retErrors = append(retErrors, fmt.Errorf("language is empty")) + } + if errs := param.Language.Validate(); len(errs) != 0 { + for _, e := range errs { + retErrors = append(retErrors, fmt.Errorf("language is invalid: %s", e)) + } + } + + return retErrors +} diff --git a/internal/pkg/plugin/githubactions/nodejs/reinstall.go b/internal/pkg/plugin/githubactions/nodejs/reinstall.go new file mode 100644 index 000000000..6d7f6030d --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/reinstall.go @@ -0,0 +1,41 @@ +package nodejs + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Reinstall remove and set up GitHub Actions workflows. +func Reinstall(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("language is %s", ga.GetLanguage(opt.Language)) + + for _, pipeline := range workflows { + err := gitHubClient.DeleteWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + + err = gitHubClient.AddWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/nodejs/templates.go b/internal/pkg/plugin/githubactions/nodejs/templates.go index 29ee649dd..5773b7c62 100644 --- a/internal/pkg/plugin/githubactions/nodejs/templates.go +++ b/internal/pkg/plugin/githubactions/nodejs/templates.go @@ -1,13 +1,12 @@ package nodejs -var PrBuilder = ` -name: Node.js CI +var mainPipeline = `name: Node.js CI on: push: - branches: [ main ] + branches: [ main, master ] pull_request: - branches: [ main ] + branches: [ main, master ] jobs: build: @@ -29,5 +28,3 @@ jobs: - run: npm run build --if-present - run: npm test ` - -var MasterBuilder = `` diff --git a/internal/pkg/plugin/githubactions/nodejs/uninstall.go b/internal/pkg/plugin/githubactions/nodejs/uninstall.go new file mode 100644 index 000000000..9c79d916c --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/uninstall.go @@ -0,0 +1,36 @@ +package nodejs + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Uninstall remove GitHub Actions workflows. +func Uninstall(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("language is %s", ga.GetLanguage(opt.Language)) + + for _, pipeline := range workflows { + err := gitHubClient.DeleteWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/nodejs/workflow.go b/internal/pkg/plugin/githubactions/nodejs/workflow.go new file mode 100644 index 000000000..d80e1e5b0 --- /dev/null +++ b/internal/pkg/plugin/githubactions/nodejs/workflow.go @@ -0,0 +1,10 @@ +package nodejs + +import ( + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + github "github.com/merico-dev/stream/pkg/util/github" +) + +var workflows = []*github.Workflow{ + {CommitMessage: ga.CommitMessage, WorkflowFileName: ga.MainBuilderFileName, WorkflowContent: mainPipeline}, +} diff --git a/internal/pkg/plugin/githubactions/python/helper.go b/internal/pkg/plugin/githubactions/python/helper.go new file mode 100644 index 000000000..3b62bc630 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/helper.go @@ -0,0 +1,26 @@ +package python + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" + + "github.com/merico-dev/stream/internal/pkg/log" +) + +func parseAndValidateOptions(options *map[string]interface{}) (*Options, error) { + var opt Options + err := mapstructure.Decode(options, &opt) + if err != nil { + return nil, err + } + + if errs := validate(&opt); len(errs) != 0 { + for _, e := range errs { + log.Errorf("Param error: %s", e) + } + return nil, fmt.Errorf("incorrect params") + } + + return &opt, nil +} diff --git a/internal/pkg/plugin/githubactions/python/install.go b/internal/pkg/plugin/githubactions/python/install.go new file mode 100644 index 000000000..bbe90ed43 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/install.go @@ -0,0 +1,35 @@ +package python + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Install sets up GitHub Actions workflow(s). +func Install(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("Language is: %s.", ga.GetLanguage(opt.Language)) + + for _, w := range workflows { + if err := gitHubClient.AddWorkflow(w, opt.Branch); err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/python/ishealthy.go b/internal/pkg/plugin/githubactions/python/ishealthy.go new file mode 100644 index 000000000..7221d45c6 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/ishealthy.go @@ -0,0 +1,45 @@ +package python + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +func IsHealthy(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("Language is: %s.", ga.GetLanguage(opt.Language)) + + retMap, err := gitHubClient.VerifyWorkflows(workflows) + if err != nil { + return false, err + } + + errFlag := false + for name, err := range retMap { + if err != nil { + errFlag = true + log.Errorf("The workflow/file %s is not ok: %s", name, err) + } + log.Successf("The workflow/file %s is ok", name) + } + if errFlag { + return false, nil + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/python/options.go b/internal/pkg/plugin/githubactions/python/options.go new file mode 100644 index 000000000..1d8b94995 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/options.go @@ -0,0 +1,44 @@ +package python + +import ( + "fmt" + + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" +) + +// TODO(daniel-hutao): Options should keep as same as other plugins named Param +// Options is the struct for configurations of the githubactions plugin. +type Options struct { + Owner string + Repo string + Branch string + Language *ga.Language +} + +// validate validates the options provided by the core. +func validate(param *Options) []error { + retErrors := make([]error, 0) + + // owner/repo/branch + if param.Owner == "" { + retErrors = append(retErrors, fmt.Errorf("owner is empty")) + } + if param.Repo == "" { + retErrors = append(retErrors, fmt.Errorf("repo is empty")) + } + if param.Branch == "" { + retErrors = append(retErrors, fmt.Errorf("branch is empty")) + } + + // language + if param.Language == nil { + retErrors = append(retErrors, fmt.Errorf("language is empty")) + } + if errs := param.Language.Validate(); len(errs) != 0 { + for _, e := range errs { + retErrors = append(retErrors, fmt.Errorf("language is invalid: %s", e)) + } + } + + return retErrors +} diff --git a/internal/pkg/plugin/githubactions/python/reinstall.go b/internal/pkg/plugin/githubactions/python/reinstall.go new file mode 100644 index 000000000..b881dad15 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/reinstall.go @@ -0,0 +1,41 @@ +package python + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Reinstall remove and set up GitHub Actions workflows. +func Reinstall(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("language is %s", ga.GetLanguage(opt.Language)) + + for _, pipeline := range workflows { + err := gitHubClient.DeleteWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + + err = gitHubClient.AddWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/python/templates.go b/internal/pkg/plugin/githubactions/python/templates.go index f9d945c91..39a9a7119 100644 --- a/internal/pkg/plugin/githubactions/python/templates.go +++ b/internal/pkg/plugin/githubactions/python/templates.go @@ -1,25 +1,6 @@ package python -var PrBuilder = ` -name: "Pull Request Workflow" -on: - pull_request: - types: [ready_for_review] - -jobs: - # Enforces the update of a changelog file on every pull request - changelog: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: dangoslen/changelog-enforcer@v1.1.1 - with: - changeLogPath: 'CHANGELOG.md' - skipLabel: 'skip-changelog' -` - -var MasterBuilder = ` -name: Tests +var mainPipeline = `name: Tests on: [push, pull_request] diff --git a/internal/pkg/plugin/githubactions/python/uninstall.go b/internal/pkg/plugin/githubactions/python/uninstall.go new file mode 100644 index 000000000..5c000a091 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/uninstall.go @@ -0,0 +1,36 @@ +package python + +import ( + "github.com/merico-dev/stream/internal/pkg/log" + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + "github.com/merico-dev/stream/pkg/util/github" +) + +// Uninstall remove GitHub Actions workflows. +func Uninstall(options *map[string]interface{}) (bool, error) { + opt, err := parseAndValidateOptions(options) + if err != nil { + return false, err + } + + ghOptions := &github.Option{ + Owner: opt.Owner, + Repo: opt.Repo, + NeedAuth: true, + } + gitHubClient, err := github.NewClient(ghOptions) + if err != nil { + return false, err + } + + log.Infof("language is %s", ga.GetLanguage(opt.Language)) + + for _, pipeline := range workflows { + err := gitHubClient.DeleteWorkflow(pipeline, opt.Branch) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/internal/pkg/plugin/githubactions/python/workflow.go b/internal/pkg/plugin/githubactions/python/workflow.go new file mode 100644 index 000000000..b6ca35a19 --- /dev/null +++ b/internal/pkg/plugin/githubactions/python/workflow.go @@ -0,0 +1,10 @@ +package python + +import ( + ga "github.com/merico-dev/stream/internal/pkg/plugin/githubactions" + github "github.com/merico-dev/stream/pkg/util/github" +) + +var workflows = []*github.Workflow{ + {CommitMessage: ga.CommitMessage, WorkflowFileName: ga.MainBuilderFileName, WorkflowContent: mainPipeline}, +} diff --git a/internal/pkg/plugin/githubactions/reinstall.go b/internal/pkg/plugin/githubactions/reinstall.go deleted file mode 100644 index 1db0b2b9f..000000000 --- a/internal/pkg/plugin/githubactions/reinstall.go +++ /dev/null @@ -1,31 +0,0 @@ -package githubactions - -import ( - "github.com/merico-dev/stream/internal/pkg/log" -) - -// Reinstall remove and set up GitHub Actions workflows. -func Reinstall(options *map[string]interface{}) (bool, error) { - githubActions, err := NewGithubActions(options) - if err != nil { - return false, err - } - - language := githubActions.GetLanguage() - log.Infof("language is %s", language.String()) - ws := defaultWorkflows.GetWorkflowByNameVersionTypeString(language.String()) - - for _, pipeline := range ws { - err := githubActions.DeleteWorkflow(pipeline) - if err != nil { - return false, err - } - - err = githubActions.AddWorkflow(pipeline) - if err != nil { - return false, err - } - } - - return true, nil -} diff --git a/internal/pkg/plugin/githubactions/uninstall.go b/internal/pkg/plugin/githubactions/uninstall.go deleted file mode 100644 index d8e6ab356..000000000 --- a/internal/pkg/plugin/githubactions/uninstall.go +++ /dev/null @@ -1,26 +0,0 @@ -package githubactions - -import ( - "github.com/merico-dev/stream/internal/pkg/log" -) - -// Uninstall remove GitHub Actions workflows. -func Uninstall(options *map[string]interface{}) (bool, error) { - githubActions, err := NewGithubActions(options) - if err != nil { - return false, err - } - - language := githubActions.GetLanguage() - log.Infof("language is %s", language.String()) - ws := defaultWorkflows.GetWorkflowByNameVersionTypeString(language.String()) - - for _, pipeline := range ws { - err := githubActions.DeleteWorkflow(pipeline) - if err != nil { - return false, err - } - } - - return true, nil -} diff --git a/internal/pkg/plugin/githubactions/workflow.go b/internal/pkg/plugin/githubactions/workflow.go index b3d4f919f..7625bab6a 100644 --- a/internal/pkg/plugin/githubactions/workflow.go +++ b/internal/pkg/plugin/githubactions/workflow.go @@ -1,76 +1,19 @@ package githubactions -import ( - "fmt" - - "github.com/merico-dev/stream/internal/pkg/plugin/githubactions/golang" - "github.com/merico-dev/stream/internal/pkg/plugin/githubactions/nodejs" - "github.com/merico-dev/stream/internal/pkg/plugin/githubactions/python" -) +import "fmt" const ( - defaultCommitMessage = "builder by DevStream" - BuilderYmlPr = "pr-builder.yml" - BuilderYmlMaster = "master-builder.yml" - BuilderYmlPipeline = "pipeline.yml" + CommitMessage = "GitHub Actions workflow, created by DevStream" + PRBuilderFileName = "pr-builder.yml" + MainBuilderFileName = "main-builder.yml" ) -var go117 = &Language{ - Name: "go", - Version: "1.17", -} - -var python3 = &Language{ - Name: "python", - Version: "3", -} - -var nodejs9 = &Language{ - Name: "nodejs", - Version: "9", -} - -var defaultWorkflows = workflows{ - go117.String(): { - {defaultCommitMessage, BuilderYmlPipeline, golang.PipelineBuilder}, - }, - python3.String(): { - {defaultCommitMessage, BuilderYmlPr, python.PrBuilder}, - {defaultCommitMessage, BuilderYmlMaster, python.MasterBuilder}, - }, - nodejs9.String(): { - {defaultCommitMessage, BuilderYmlPr, nodejs.PrBuilder}, - {defaultCommitMessage, BuilderYmlMaster, nodejs.MasterBuilder}, - }, -} - -// TODO(daniel-hutao): Options should keep as same as other plugins named Param -// Options is the struct for configurations of the githubactions plugin. -type Options struct { - Owner string - Repo string - Branch string - Language *Language - Jobs *Jobs -} - // Language is the struct containing details of a programming language specified in the GitHub Actions Workflow. type Language struct { Name string Version string } -// Workflow is the struct for a GitHub Actions Workflow. -type Workflow struct { - commitMessage string - workflowFileName string - workflowContent string -} - -type LanguageString string - -type workflows map[LanguageString][]*Workflow - func (l *Language) Validate() []error { retErrors := make([]error, 0) @@ -80,15 +23,3 @@ func (l *Language) Validate() []error { return retErrors } - -func (l *Language) String() LanguageString { - return LanguageString(fmt.Sprintf("%s-%s", l.Name, l.Version)) -} - -func (ws *workflows) GetWorkflowByNameVersionTypeString(nvtStr LanguageString) []*Workflow { - workflowList, exist := (*ws)[nvtStr] - if exist { - return workflowList - } - return nil -} diff --git a/pkg/util/github/github.go b/pkg/util/github/github.go index f977e9f0d..1ed553343 100644 --- a/pkg/util/github/github.go +++ b/pkg/util/github/github.go @@ -24,6 +24,7 @@ var client *Client type Client struct { *Option *github.Client + context.Context } type Option struct { @@ -50,8 +51,9 @@ func NewClient(option *Option) (*Client, error) { if !option.NeedAuth { log.Debug("Auth is not enabled") client = &Client{ - Option: option, - Client: github.NewClient(nil), + Option: option, + Client: github.NewClient(nil), + Context: context.Background(), } return client, nil @@ -74,6 +76,7 @@ func NewClient(option *Option) (*Client, error) { } log.Debugf("Token: %s", token) + ctx := context.Background() tc := oauth2.NewClient( context.TODO(), oauth2.StaticTokenSource( @@ -84,8 +87,9 @@ func NewClient(option *Option) (*Client, error) { ) client = &Client{ - Option: option, - Client: github.NewClient(tc), + Option: option, + Client: github.NewClient(tc), + Context: ctx, } return client, nil diff --git a/pkg/util/github/secrets.go b/pkg/util/github/secrets.go index 5c0faa394..6b683664e 100644 --- a/pkg/util/github/secrets.go +++ b/pkg/util/github/secrets.go @@ -66,3 +66,30 @@ func encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName string, } return encryptedSecret, nil } + +// DeleteRepoSecret deletes a secret in a GitHub repo. +func (c *Client) DeleteRepoSecret(secretKey string) error { + ctx := context.Background() + response, err := client.Actions.DeleteRepoSecret(ctx, c.Owner, c.Repo, secretKey) + if err != nil { + if response.StatusCode == 404 { + return nil + } + return fmt.Errorf("github Actions.DeleteRepoSecret returned error: %v", err) + } + + return nil +} + +// RepoSecretExists detects if a secret exists in a GitHub repo. +func (c *Client) RepoSecretExists(secretKey string) (bool, error) { + ctx := context.Background() + _, response, err := client.Actions.GetRepoSecret(ctx, c.Owner, c.Repo, secretKey) + if err != nil { + if response.StatusCode == 404 { + return false, nil + } + return false, fmt.Errorf("github Actions.GetRepoSecret returned error: %v", err) + } + return true, nil +} diff --git a/pkg/util/github/workflow.go b/pkg/util/github/workflow.go new file mode 100644 index 000000000..e26f8fd66 --- /dev/null +++ b/pkg/util/github/workflow.go @@ -0,0 +1,143 @@ +package github + +import ( + "fmt" + "net/http" + "strings" + + "github.com/google/go-github/v42/github" + "github.com/merico-dev/stream/internal/pkg/log" + "github.com/merico-dev/stream/pkg/util/mapz" + "github.com/merico-dev/stream/pkg/util/slicez" +) + +// Workflow is the struct for a GitHub Actions Workflow. +type Workflow struct { + CommitMessage string + WorkflowFileName string + WorkflowContent string +} + +func (c *Client) AddWorkflow(workflow *Workflow, branch string) error { + sha, err := c.getFileSHA(workflow.WorkflowFileName) + if err != nil { + return err + } + if sha != "" { + log.Infof("GitHub Actions workflow %s already exists.", workflow.WorkflowFileName) + return nil + } + + // Note: the file needs to be absent from the repository as you are not + // specifying a SHA reference here. + opts := &github.RepositoryContentFileOptions{ + Message: github.String(workflow.CommitMessage), + Content: []byte(workflow.WorkflowContent), + Branch: github.String(branch), + } + + log.Infof("Creating GitHub Actions workflow %s ...", workflow.WorkflowFileName) + _, _, err = c.Client.Repositories.CreateFile( + c.Context, + c.Option.Owner, + c.Option.Repo, + generateGitHubWorkflowFileByName(workflow.WorkflowFileName), + opts) + + if err != nil { + return err + } + log.Successf("Github Actions workflow %s created.", workflow.WorkflowFileName) + return nil +} +func (c *Client) DeleteWorkflow(workflow *Workflow, branch string) error { + sha, err := c.getFileSHA(workflow.WorkflowFileName) + if err != nil { + return err + } + if sha == "" { + log.Successf("Github Actions workflow %s already removed.", workflow.WorkflowFileName) + return nil + } + + // Note: the file needs to be absent from the repository as you are not + // specifying a SHA reference here. + opts := &github.RepositoryContentFileOptions{ + Message: github.String(workflow.CommitMessage), + SHA: github.String(sha), + Branch: github.String(branch), + } + + log.Infof("Deleting GitHub Actions workflow %s ...", workflow.WorkflowFileName) + _, _, err = c.Client.Repositories.DeleteFile( + c.Context, + c.Option.Owner, + c.Option.Repo, + generateGitHubWorkflowFileByName(workflow.WorkflowFileName), + opts) + + if err != nil { + return err + } + log.Successf("GitHub Actions workflow %s removed.", workflow.WorkflowFileName) + return nil +} + +// VerifyWorkflows get the workflows with names "wf1.yml", "wf2.yml", then: +// If all workflows is ok => return ({"wf1.yml":nil, "wf2.yml:nil}, nil) +// If some error occurred => return (nil, error) +// If wf1.yml is not found => return ({"wf1.yml":error("not found"), "wf2.yml:nil},nil) +func (c *Client) VerifyWorkflows(workflows []*Workflow) (map[string]error, error) { + wsFiles := make([]string, 0) + for _, w := range workflows { + wsFiles = append(wsFiles, w.WorkflowFileName) + } + fmt.Printf("Workflow files: %v", wsFiles) + + _, dirContent, resp, err := c.Client.Repositories.GetContents( + c.Context, + c.Option.Owner, + c.Option.Repo, + ".github/workflows", + &github.RepositoryContentGetOptions{}, + ) + + // error reason is not 404 + if err != nil && !strings.Contains(err.Error(), "404") { + log.Errorf("GetContents failed with error: %s", err) + return nil, err + } + // StatusCode == 404 + if resp.StatusCode == http.StatusNotFound { + log.Errorf("GetContents return with status code 404") + retMap := mapz.FillMapWithStrAndError(wsFiles, fmt.Errorf("not found")) + return retMap, nil + } + // StatusCode != 200 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("got some error is not expected: %s", resp.Status) + } + // StatusCode == 200 + log.Success("GetContents return with status code 200") + var filesInRemoteDir = make([]string, 0) + for _, f := range dirContent { + log.Infof("Found remote file: %s", f.GetName()) + filesInRemoteDir = append(filesInRemoteDir, f.GetName()) + } + + lostFiles := slicez.SliceInSliceStr(wsFiles, filesInRemoteDir) + // all files exist + if len(lostFiles) == 0 { + log.Info("All files exist") + retMap := mapz.FillMapWithStrAndError(wsFiles, nil) + return retMap, nil + } + // some files lost + log.Warn("Some files lost") + retMap := mapz.FillMapWithStrAndError(wsFiles, nil) + for _, f := range lostFiles { + log.Infof("Lost file: %s", f) + retMap[f] = fmt.Errorf("not found") + } + return retMap, nil +} diff --git a/pkg/util/github/workflow_helper.go b/pkg/util/github/workflow_helper.go new file mode 100644 index 000000000..c2649534b --- /dev/null +++ b/pkg/util/github/workflow_helper.go @@ -0,0 +1,43 @@ +package github + +import ( + "fmt" + "net/http" + "strings" + + "github.com/google/go-github/v42/github" +) + +func generateGitHubWorkflowFileByName(f string) string { + return fmt.Sprintf(".github/workflows/%s", f) +} + +// getFileSHA will try to collect the SHA hash value of the file, then return it. the return values will be: +// 1. If file exists without error -> string(SHA), nil +// 2. If some errors occurred -> return "", err +// 3. If file not found without error -> return "", nil +func (c *Client) getFileSHA(filename string) (string, error) { + content, _, resp, err := c.Client.Repositories.GetContents( + c.Context, + c.Option.Owner, + c.Option.Repo, + generateGitHubWorkflowFileByName(filename), + &github.RepositoryContentGetOptions{}, + ) + + // error reason is not 404 + if err != nil && !strings.Contains(err.Error(), "404") { + return "", err + } + + // error reason is 404 + if resp.StatusCode == http.StatusNotFound { + return "", nil + } + + // no error occurred + if resp.StatusCode == http.StatusOK { + return *content.SHA, nil + } + return "", fmt.Errorf("got some error is not expected") +} From 5e1ceea01443b31d09f30b3214b7830a1b51bfa0 Mon Sep 17 00:00:00 2001 From: Tiexin Guo Date: Wed, 9 Feb 2022 15:49:16 +0800 Subject: [PATCH 2/2] fixing lint --- .../pkg/plugin/githubactions/golang/ishealthy.go | 14 ++++++-------- .../pkg/plugin/githubactions/nodejs/ishealthy.go | 12 +++++------- .../pkg/plugin/githubactions/python/ishealthy.go | 12 +++++------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/internal/pkg/plugin/githubactions/golang/ishealthy.go b/internal/pkg/plugin/githubactions/golang/ishealthy.go index f0bcd7605..85070735d 100644 --- a/internal/pkg/plugin/githubactions/golang/ishealthy.go +++ b/internal/pkg/plugin/githubactions/golang/ishealthy.go @@ -29,16 +29,14 @@ func IsHealthy(options *map[string]interface{}) (bool, error) { return false, err } - errFlag := false + healthy := true for name, err := range retMap { if err != nil { - errFlag = true + healthy = false log.Errorf("The workflow/file %s is not ok: %s", name, err) + } else { + log.Successf("The workflow/file %s is ok", name) } - log.Successf("The workflow/file %s is ok", name) - } - if errFlag { - return false, nil } // if docker is enabled, verify if secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN are deleted @@ -49,7 +47,7 @@ func IsHealthy(options *map[string]interface{}) (bool, error) { return false, err } if !exists { - errFlag = true + healthy = false log.Errorf("The secret %s doesn't exist.", secret) } else { log.Successf("The secret %s is ok", secret) @@ -57,5 +55,5 @@ func IsHealthy(options *map[string]interface{}) (bool, error) { } } - return true, nil + return healthy, nil } diff --git a/internal/pkg/plugin/githubactions/nodejs/ishealthy.go b/internal/pkg/plugin/githubactions/nodejs/ishealthy.go index 93f9767bf..b6bc6ec52 100644 --- a/internal/pkg/plugin/githubactions/nodejs/ishealthy.go +++ b/internal/pkg/plugin/githubactions/nodejs/ishealthy.go @@ -29,17 +29,15 @@ func IsHealthy(options *map[string]interface{}) (bool, error) { return false, err } - errFlag := false + healthy := true for name, err := range retMap { if err != nil { - errFlag = true + healthy = false log.Errorf("The workflow/file %s is not ok: %s", name, err) + } else { + log.Successf("The workflow/file %s is ok", name) } - log.Successf("The workflow/file %s is ok", name) - } - if errFlag { - return false, nil } - return true, nil + return healthy, nil } diff --git a/internal/pkg/plugin/githubactions/python/ishealthy.go b/internal/pkg/plugin/githubactions/python/ishealthy.go index 7221d45c6..3c9a192c7 100644 --- a/internal/pkg/plugin/githubactions/python/ishealthy.go +++ b/internal/pkg/plugin/githubactions/python/ishealthy.go @@ -29,17 +29,15 @@ func IsHealthy(options *map[string]interface{}) (bool, error) { return false, err } - errFlag := false + healthy := true for name, err := range retMap { if err != nil { - errFlag = true + healthy = false log.Errorf("The workflow/file %s is not ok: %s", name, err) + } else { + log.Successf("The workflow/file %s is ok", name) } - log.Successf("The workflow/file %s is ok", name) - } - if errFlag { - return false, nil } - return true, nil + return healthy, nil }