From ef0a24e6581d0762b1acc678c133badaf7e551e8 Mon Sep 17 00:00:00 2001 From: xh3b4sd Date: Thu, 28 Sep 2023 11:33:43 +0200 Subject: [PATCH] init (#1) --- .github/dependabot.yaml | 35 ++ .github/workflows/go-build.yaml | 43 ++ .github/workflows/go-release.yaml | 38 ++ .gitignore | 4 + README.md | 90 ++++ cmd/command.go | 106 ++++ cmd/completion/command.go | 55 +++ cmd/completion/error.go | 15 + cmd/completion/runner.go | 53 ++ cmd/create/cfmtest/command.go | 44 ++ cmd/create/cfmtest/error.go | 23 + cmd/create/cfmtest/flag.go | 67 +++ cmd/create/cfmtest/runner.go | 101 ++++ cmd/create/cfmtest/template.go | 297 ++++++++++++ cmd/create/command.go | 253 ++++++++++ cmd/create/dependabot/command.go | 44 ++ cmd/create/dependabot/error.go | 23 + cmd/create/dependabot/flag.go | 51 ++ cmd/create/dependabot/runner.go | 114 +++++ cmd/create/dependabot/template.go | 27 ++ cmd/create/dockergo/command.go | 44 ++ cmd/create/dockergo/error.go | 23 + cmd/create/dockergo/flag.go | 35 ++ cmd/create/dockergo/runner.go | 85 ++++ cmd/create/dockergo/template.go | 50 ++ cmd/create/dockerts/command.go | 44 ++ cmd/create/dockerts/error.go | 23 + cmd/create/dockerts/flag.go | 35 ++ cmd/create/dockerts/runner.go | 85 ++++ cmd/create/dockerts/template.go | 51 ++ cmd/create/dsmupdate/command.go | 44 ++ cmd/create/dsmupdate/error.go | 23 + cmd/create/dsmupdate/flag.go | 45 ++ cmd/create/dsmupdate/runner.go | 93 ++++ cmd/create/dsmupdate/template.go | 82 ++++ cmd/create/dsmverify/command.go | 44 ++ cmd/create/dsmverify/error.go | 23 + cmd/create/dsmverify/flag.go | 35 ++ cmd/create/dsmverify/runner.go | 85 ++++ cmd/create/dsmverify/template.go | 43 ++ cmd/create/error.go | 15 + cmd/create/golang/command.go | 44 ++ cmd/create/golang/error.go | 23 + cmd/create/golang/flag.go | 48 ++ cmd/create/golang/runner.go | 144 ++++++ cmd/create/golang/template.go | 63 +++ cmd/create/npm/command.go | 44 ++ cmd/create/npm/error.go | 23 + cmd/create/npm/flag.go | 35 ++ cmd/create/npm/runner.go | 85 ++++ cmd/create/npm/template.go | 57 +++ cmd/create/pbfgo/command.go | 57 +++ cmd/create/pbfgo/error.go | 23 + cmd/create/pbfgo/flag.go | 58 +++ cmd/create/pbfgo/runner.go | 83 ++++ cmd/create/pbflint/command.go | 44 ++ cmd/create/pbflint/error.go | 23 + cmd/create/pbflint/flag.go | 43 ++ cmd/create/pbflint/runner.go | 81 ++++ cmd/create/pbfts/command.go | 57 +++ cmd/create/pbfts/error.go | 23 + cmd/create/pbfts/flag.go | 58 +++ cmd/create/pbfts/runner.go | 83 ++++ cmd/create/redigo/command.go | 44 ++ cmd/create/redigo/error.go | 23 + cmd/create/redigo/flag.go | 35 ++ cmd/create/redigo/runner.go | 95 ++++ cmd/create/redigo/template.go | 53 ++ cmd/create/releasego/command.go | 44 ++ cmd/create/releasego/error.go | 23 + cmd/create/releasego/flag.go | 76 +++ cmd/create/releasego/runner.go | 124 +++++ cmd/create/releasego/template.go | 43 ++ cmd/create/releases3/command.go | 78 +++ cmd/create/releases3/error.go | 23 + cmd/create/releases3/flag.go | 75 +++ cmd/create/releases3/runner.go | 124 +++++ cmd/create/releases3/template.go | 52 ++ cmd/create/runner.go | 33 ++ cmd/create/typescript/command.go | 44 ++ cmd/create/typescript/error.go | 23 + cmd/create/typescript/flag.go | 35 ++ cmd/create/typescript/runner.go | 85 ++++ cmd/create/typescript/template.go | 54 +++ cmd/error.go | 15 + cmd/runner.go | 33 ++ cmd/update/all/command.go | 59 +++ cmd/update/all/error.go | 23 + cmd/update/all/runner.go | 117 +++++ cmd/update/command.go | 57 +++ cmd/update/error.go | 15 + cmd/update/runner.go | 33 ++ cmd/version/command.go | 36 ++ cmd/version/error.go | 15 + cmd/version/runner.go | 40 ++ go.mod | 19 + go.sum | 453 ++++++++++++++++++ main.go | 51 ++ pkg/env/env.go | 44 ++ pkg/file/exists.go | 14 + pkg/generator/interface.go | 5 + pkg/generator/pbfgo/error.go | 15 + pkg/generator/pbfgo/pbfgo.go | 122 +++++ pkg/generator/pbfgo/pbfgo_test.go | 104 ++++ pkg/generator/pbfgo/template.go | 89 ++++ .../pbfgo/testdata/workflow/case-0.golden | 86 ++++ .../pbfgo/testdata/workflow/case-1.golden | 86 ++++ pkg/generator/pbflint/error.go | 15 + pkg/generator/pbflint/pbflint.go | 100 ++++ pkg/generator/pbflint/pbflint_test.go | 93 ++++ pkg/generator/pbflint/template.go | 67 +++ .../pbflint/testdata/workflow/case-0.golden | 64 +++ .../pbflint/testdata/workflow/case-1.golden | 64 +++ pkg/generator/pbfts/error.go | 15 + pkg/generator/pbfts/pbfts.go | 122 +++++ pkg/generator/pbfts/pbfts_test.go | 104 ++++ pkg/generator/pbfts/template.go | 90 ++++ .../pbfts/testdata/workflow/case-0.golden | 87 ++++ .../pbfts/testdata/workflow/case-1.golden | 87 ++++ pkg/parser/command/command.go | 117 +++++ pkg/parser/command/command_test.go | 155 ++++++ pkg/parser/command/error.go | 15 + .../command/testdata/parse/case-0.golden | 1 + .../command/testdata/parse/case-1.golden | 3 + pkg/parser/interface.go | 10 + pkg/project/project.go | 29 ++ pkg/repo/current.go | 61 +++ pkg/repo/current_test.go | 38 ++ pkg/version/checkout.go | 5 + pkg/version/golang.go | 5 + pkg/version/golang_ci_lint.go | 5 + pkg/version/node.go | 5 + pkg/version/protoc.go | 5 + pkg/version/setup_go.go | 5 + pkg/version/setup_node.go | 5 + pkg/version/version.go | 11 + 136 files changed, 7789 insertions(+) create mode 100644 .github/dependabot.yaml create mode 100644 .github/workflows/go-build.yaml create mode 100644 .github/workflows/go-release.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cmd/command.go create mode 100644 cmd/completion/command.go create mode 100644 cmd/completion/error.go create mode 100644 cmd/completion/runner.go create mode 100644 cmd/create/cfmtest/command.go create mode 100644 cmd/create/cfmtest/error.go create mode 100644 cmd/create/cfmtest/flag.go create mode 100644 cmd/create/cfmtest/runner.go create mode 100644 cmd/create/cfmtest/template.go create mode 100644 cmd/create/command.go create mode 100644 cmd/create/dependabot/command.go create mode 100644 cmd/create/dependabot/error.go create mode 100644 cmd/create/dependabot/flag.go create mode 100644 cmd/create/dependabot/runner.go create mode 100644 cmd/create/dependabot/template.go create mode 100644 cmd/create/dockergo/command.go create mode 100644 cmd/create/dockergo/error.go create mode 100644 cmd/create/dockergo/flag.go create mode 100644 cmd/create/dockergo/runner.go create mode 100644 cmd/create/dockergo/template.go create mode 100644 cmd/create/dockerts/command.go create mode 100644 cmd/create/dockerts/error.go create mode 100644 cmd/create/dockerts/flag.go create mode 100644 cmd/create/dockerts/runner.go create mode 100644 cmd/create/dockerts/template.go create mode 100644 cmd/create/dsmupdate/command.go create mode 100644 cmd/create/dsmupdate/error.go create mode 100644 cmd/create/dsmupdate/flag.go create mode 100644 cmd/create/dsmupdate/runner.go create mode 100644 cmd/create/dsmupdate/template.go create mode 100644 cmd/create/dsmverify/command.go create mode 100644 cmd/create/dsmverify/error.go create mode 100644 cmd/create/dsmverify/flag.go create mode 100644 cmd/create/dsmverify/runner.go create mode 100644 cmd/create/dsmverify/template.go create mode 100644 cmd/create/error.go create mode 100644 cmd/create/golang/command.go create mode 100644 cmd/create/golang/error.go create mode 100644 cmd/create/golang/flag.go create mode 100644 cmd/create/golang/runner.go create mode 100644 cmd/create/golang/template.go create mode 100644 cmd/create/npm/command.go create mode 100644 cmd/create/npm/error.go create mode 100644 cmd/create/npm/flag.go create mode 100644 cmd/create/npm/runner.go create mode 100644 cmd/create/npm/template.go create mode 100644 cmd/create/pbfgo/command.go create mode 100644 cmd/create/pbfgo/error.go create mode 100644 cmd/create/pbfgo/flag.go create mode 100644 cmd/create/pbfgo/runner.go create mode 100644 cmd/create/pbflint/command.go create mode 100644 cmd/create/pbflint/error.go create mode 100644 cmd/create/pbflint/flag.go create mode 100644 cmd/create/pbflint/runner.go create mode 100644 cmd/create/pbfts/command.go create mode 100644 cmd/create/pbfts/error.go create mode 100644 cmd/create/pbfts/flag.go create mode 100644 cmd/create/pbfts/runner.go create mode 100644 cmd/create/redigo/command.go create mode 100644 cmd/create/redigo/error.go create mode 100644 cmd/create/redigo/flag.go create mode 100644 cmd/create/redigo/runner.go create mode 100644 cmd/create/redigo/template.go create mode 100644 cmd/create/releasego/command.go create mode 100644 cmd/create/releasego/error.go create mode 100644 cmd/create/releasego/flag.go create mode 100644 cmd/create/releasego/runner.go create mode 100644 cmd/create/releasego/template.go create mode 100644 cmd/create/releases3/command.go create mode 100644 cmd/create/releases3/error.go create mode 100644 cmd/create/releases3/flag.go create mode 100644 cmd/create/releases3/runner.go create mode 100644 cmd/create/releases3/template.go create mode 100644 cmd/create/runner.go create mode 100644 cmd/create/typescript/command.go create mode 100644 cmd/create/typescript/error.go create mode 100644 cmd/create/typescript/flag.go create mode 100644 cmd/create/typescript/runner.go create mode 100644 cmd/create/typescript/template.go create mode 100644 cmd/error.go create mode 100644 cmd/runner.go create mode 100644 cmd/update/all/command.go create mode 100644 cmd/update/all/error.go create mode 100644 cmd/update/all/runner.go create mode 100644 cmd/update/command.go create mode 100644 cmd/update/error.go create mode 100644 cmd/update/runner.go create mode 100644 cmd/version/command.go create mode 100644 cmd/version/error.go create mode 100644 cmd/version/runner.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/env/env.go create mode 100644 pkg/file/exists.go create mode 100644 pkg/generator/interface.go create mode 100644 pkg/generator/pbfgo/error.go create mode 100644 pkg/generator/pbfgo/pbfgo.go create mode 100644 pkg/generator/pbfgo/pbfgo_test.go create mode 100644 pkg/generator/pbfgo/template.go create mode 100644 pkg/generator/pbfgo/testdata/workflow/case-0.golden create mode 100644 pkg/generator/pbfgo/testdata/workflow/case-1.golden create mode 100644 pkg/generator/pbflint/error.go create mode 100644 pkg/generator/pbflint/pbflint.go create mode 100644 pkg/generator/pbflint/pbflint_test.go create mode 100644 pkg/generator/pbflint/template.go create mode 100644 pkg/generator/pbflint/testdata/workflow/case-0.golden create mode 100644 pkg/generator/pbflint/testdata/workflow/case-1.golden create mode 100644 pkg/generator/pbfts/error.go create mode 100644 pkg/generator/pbfts/pbfts.go create mode 100644 pkg/generator/pbfts/pbfts_test.go create mode 100644 pkg/generator/pbfts/template.go create mode 100644 pkg/generator/pbfts/testdata/workflow/case-0.golden create mode 100644 pkg/generator/pbfts/testdata/workflow/case-1.golden create mode 100644 pkg/parser/command/command.go create mode 100644 pkg/parser/command/command_test.go create mode 100644 pkg/parser/command/error.go create mode 100644 pkg/parser/command/testdata/parse/case-0.golden create mode 100644 pkg/parser/command/testdata/parse/case-1.golden create mode 100644 pkg/parser/interface.go create mode 100644 pkg/project/project.go create mode 100644 pkg/repo/current.go create mode 100644 pkg/repo/current_test.go create mode 100644 pkg/version/checkout.go create mode 100644 pkg/version/golang.go create mode 100644 pkg/version/golang_ci_lint.go create mode 100644 pkg/version/node.go create mode 100644 pkg/version/protoc.go create mode 100644 pkg/version/setup_go.go create mode 100644 pkg/version/setup_node.go create mode 100644 pkg/version/version.go diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..039c0f6 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,35 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create dependabot -b master -r xh3b4sd +# + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch"] + open-pull-requests-limit: 10 + reviewers: + - "xh3b4sd" + schedule: + interval: "daily" + time: "04:00" + target-branch: "master" + + - package-ecosystem: "gomod" + directory: "/" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch"] + open-pull-requests-limit: 10 + reviewers: + - "xh3b4sd" + schedule: + interval: "daily" + time: "04:00" + target-branch: "master" diff --git a/.github/workflows/go-build.yaml b/.github/workflows/go-build.yaml new file mode 100644 index 0000000..871343e --- /dev/null +++ b/.github/workflows/go-build.yaml @@ -0,0 +1,43 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create golang +# + +name: "go-build" + +on: "push" + +jobs: + go-build: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Go Env" + uses: "actions/setup-go@v4" + with: + cache: true + go-version: "1.21.1" + + - name: "Check Go Dependencies" + run: | + go mod tidy + git diff --exit-code + + - name: "Check Go Tests" + run: | + go test ./... -race + + - name: "Check Go Formatting" + run: | + test -z $(gofmt -l -s .) + + - name: "Check Go Linters" + run: | + curl -LOs https://github.com/golangci/golangci-lint/releases/download/v1.54.2/golangci-lint-1.54.2-linux-amd64.tar.gz + tar -xzf golangci-lint-1.54.2-linux-amd64.tar.gz + ./golangci-lint-1.54.2-linux-amd64/golangci-lint run diff --git a/.github/workflows/go-release.yaml b/.github/workflows/go-release.yaml new file mode 100644 index 0000000..5f9e897 --- /dev/null +++ b/.github/workflows/go-release.yaml @@ -0,0 +1,38 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create releasego -n workflow -s gitSHA -t version +# + +name: "go-release" + +on: + push: + tags: + - "v*.*.*" + +jobs: + release: + runs-on: "ubuntu-latest" + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Go Env" + uses: "actions/setup-go@v4" + with: + cache: true + go-version: "1.21.1" + + - name: "Cross Compile Binaries" + run: | + GOOS=darwin GOARCH=amd64 go build -o workflow-darwin-amd64 -ldflags="-X 'github.com/${{ github.repository_owner }}/workflow/pkg/project.gitSHA=${{ github.sha }}' -X 'github.com/${{ github.repository_owner }}/workflow/pkg/project.version=${{ github.ref_name }}'" + GOOS=linux GOARCH=amd64 go build -o workflow-linux-amd64 -ldflags="-X 'github.com/${{ github.repository_owner }}/workflow/pkg/project.gitSHA=${{ github.sha }}' -X 'github.com/${{ github.repository_owner }}/workflow/pkg/project.version=${{ github.ref_name }}'" + + - name: "Upload To Github" + uses: "softprops/action-gh-release@v1" + with: + files: | + workflow-darwin-amd64 + workflow-linux-amd64 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..393837f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vscode/ +/.DS_Store +/workflow +!pkg/** diff --git a/README.md b/README.md new file mode 100644 index 0000000..33b336e --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# workflow + +Command line tool for generating github workflows. Certain optinionated design +decisions have been made which limits the workflow generation and other tooling +to the github ecosystem. All commands generating workflows must be executed +within the root directory of the repository the desired workflows should be +generated for. + + + +### Create Workflows + +``` +$ workflow create -h +Create github workflows and config files. + +Usage: + workflow create [flags] + workflow create [command] + +Available Commands: + cfmtest Create a conformance workflow for e.g. running tests. + dependabot Create a dependabot workflow for e.g. golang and docker. + dockergo Create a docker workflow for building and pushing docker images of golang apps. + dockerts Create a docker workflow for building and pushing docker images of typescript apps. + dsmupdate Create a mutating workflow for e.g. app versions. + dsmverify Create a validation workflow for e.g. checking consistency. + golang Create a golang workflow for e.g. running tests and checking formatting. + npm Create a npm workflow for e.g. building and publishing npm packages. + pbfgo Create a protocol buffer workflow for golang code generation. + pbflint Create a protocol buffer workflow for schema validation. + pbfts Create a protocol buffer workflow for typescript code generation. + redigo Create a golang workflow for e.g. running redis conformance tests. + releasego Create a golang workflow for e.g. uploading cross compiled release assets. + releases3 Create a golang workflow for e.g. uploading cross compiled release assets. + typescript Create a typescript workflow for e.g. building and formatting typescript code. + +Flags: + -h, --help help for create + +Use "workflow create [command] --help" for more information about a command. +``` + +``` +$ workflow create dependabot -h +Create a dependabot workflow for e.g. golang and docker. + +Usage: + workflow create dependabot [flags] + +Flags: + -b, --branch string Dependabort target branch to merge pull requests into. (default "main") + -h, --help help for dependabot + -r, --reviewers strings Reviewers assigned to dependabot PRs, e.g. xh3b4sd. Works with github usernames and teams. + -g, --version-golang string Golang version to use in, e.g. workflow files. (default "1.15.2") +``` + + + +### Update Workflows + + +``` +$ workflow update all -h +Update all github workflows to the latest version. When creating a new +workflow file the original command instruction in form of os.Args is written +to the header of the workflow file. A typical workflow file header looks like +the following. + + # + # Do not edit. This file was generated via the "workflow" command line tool. + # More information about the tool can be found at github.com/xh3b4sd/workflow. + # + # workflow create dependabot -r xh3b4sd + # + +This information of the executable command is used to make workflow updates +reproducible. All workflow files within the github specific workflow +directory are inspected when collecting command instructions. Once all +commands are known they are executed dynamically while new behaviour is +applied. + + .github/workflows/ + +Usage: + workflow update all [flags] + +Flags: + -h, --help help for all +``` diff --git a/cmd/command.go b/cmd/command.go new file mode 100644 index 0000000..1224263 --- /dev/null +++ b/cmd/command.go @@ -0,0 +1,106 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/cmd/completion" + "github.com/xh3b4sd/workflow/cmd/create" + "github.com/xh3b4sd/workflow/cmd/update" + "github.com/xh3b4sd/workflow/cmd/version" + "github.com/xh3b4sd/workflow/pkg/project" +) + +var ( + name = project.Name() + short = project.Description() + long = project.Description() +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var err error + + var completionCmd *cobra.Command + { + c := completion.Config{ + Logger: config.Logger, + } + + completionCmd, err = completion.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var createCmd *cobra.Command + { + c := create.Config{ + Logger: config.Logger, + } + + createCmd, err = create.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var versionCmd *cobra.Command + { + c := version.Config{ + Logger: config.Logger, + } + + versionCmd, err = version.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var updateCmd *cobra.Command + { + c := update.Config{ + Logger: config.Logger, + } + + updateCmd, err = update.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var c *cobra.Command + { + r := &runner{ + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + // We slience errors because we do not want to see spf13/cobra printing. + // The errors returned by the commands will be propagated to the main.go + // anyway, where we have custom error printing for the command line + // tool. + SilenceErrors: true, + SilenceUsage: true, + } + + c.AddCommand(completionCmd) + c.AddCommand(createCmd) + c.AddCommand(updateCmd) + c.AddCommand(versionCmd) + } + + return c, nil +} diff --git a/cmd/completion/command.go b/cmd/completion/command.go new file mode 100644 index 0000000..19b21c2 --- /dev/null +++ b/cmd/completion/command.go @@ -0,0 +1,55 @@ +package completion + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "completion" + short = "Generate shell completions." + long = `Supported positional arguments and respective shell completions are +as follows. + + bash + fish + powershell + zsh + +Generating zsh completion for Oh My Zsh can be done by writing the +generated completion to the custom plugin folder. + + mkdir -p ~/.oh-my-zsh/custom/plugins/workflow && workflow completion zsh > ~/.oh-my-zsh/custom/plugins/workflow/_workflow + + ` +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + r := &runner{ + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "fish", "powershell", "zsh"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: r.Run, + } + } + + return c, nil +} diff --git a/cmd/completion/error.go b/cmd/completion/error.go new file mode 100644 index 0000000..4a7fe0a --- /dev/null +++ b/cmd/completion/error.go @@ -0,0 +1,15 @@ +package completion + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/cmd/completion/runner.go b/cmd/completion/runner.go new file mode 100644 index 0000000..310e754 --- /dev/null +++ b/cmd/completion/runner.go @@ -0,0 +1,53 @@ +package completion + +import ( + "context" + "os" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +type runner struct { + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + var err error + switch args[0] { + case "bash": + err = cmd.Root().GenBashCompletion(os.Stdout) + if err != nil { + return tracer.Mask(err) + } + case "zsh": + err = cmd.Root().GenZshCompletion(os.Stdout) + if err != nil { + return tracer.Mask(err) + } + case "fish": + err = cmd.Root().GenFishCompletion(os.Stdout, true) + if err != nil { + return tracer.Mask(err) + } + case "powershell": + err = cmd.Root().GenPowerShellCompletion(os.Stdout) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/cfmtest/command.go b/cmd/create/cfmtest/command.go new file mode 100644 index 0000000..870bc8b --- /dev/null +++ b/cmd/create/cfmtest/command.go @@ -0,0 +1,44 @@ +package cfmtest + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "cfmtest" + short = "Create a conformance workflow for e.g. running tests." + long = "Create a conformance workflow for e.g. running tests." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/cfmtest/error.go b/cmd/create/cfmtest/error.go new file mode 100644 index 0000000..9ce50bf --- /dev/null +++ b/cmd/create/cfmtest/error.go @@ -0,0 +1,23 @@ +package cfmtest + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/cfmtest/flag.go b/cmd/create/cfmtest/flag.go new file mode 100644 index 0000000..70cdc48 --- /dev/null +++ b/cmd/create/cfmtest/flag.go @@ -0,0 +1,67 @@ +package cfmtest + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +var ( + repositories = []string{ + "apiserver", + "apiworker", + "cfm", + "flux", + } +) + +type flag struct { + Repository struct { + Name string + } + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Repository.Name, "repository-name", "r", "", "Repository name to generate the workflow for, e.g. flux or apiworker.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Repository.Name == "" { + return tracer.Maskf(invalidFlagError, "-r/--repository-name must not be empty") + } + if !contains(repositories, f.Repository.Name) { + return tracer.Maskf(invalidFlagError, "-r/--repository-name must not be one of %s", strings.Join(repositories, ", ")) + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} + +func contains(l []string, s string) bool { + for _, e := range l { + if e == s { + return true + } + } + + return false +} diff --git a/cmd/create/cfmtest/runner.go b/cmd/create/cfmtest/runner.go new file mode 100644 index 0000000..e9e3553 --- /dev/null +++ b/cmd/create/cfmtest/runner.go @@ -0,0 +1,101 @@ +package cfmtest + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + SetupGo: version.SetupGo, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + var templateWorkflow string + { + if r.flag.Repository.Name == "apiserver" { + templateWorkflow = templateApiServer + } + if r.flag.Repository.Name == "apiworker" { + templateWorkflow = templateApiWorker + } + if r.flag.Repository.Name == "cfm" { + templateWorkflow = templateCfm + } + if r.flag.Repository.Name == "flux" { + templateWorkflow = templateFlux + } + } + + { + p := ".github/workflows/cfm-test.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/cfmtest/template.go b/cmd/create/cfmtest/template.go new file mode 100644 index 0000000..e5c1904 --- /dev/null +++ b/cmd/create/cfmtest/template.go @@ -0,0 +1,297 @@ +package cfmtest + +const templateApiServer = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "cfm-test" + +on: "push" + +jobs: + cfm-test: + runs-on: "ubuntu-latest" + + services: + redis: + image: "redis" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/apiserver" + repository: "venturemark/apiserver" + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/cfm" + repository: "venturemark/cfm" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Test Dependency" + run: | + sudo apt update + sudo apt install gcc -y + + - name: "Install Test Dependency" + run: | + cd ./venturemark/apiserver && go install . + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/apiworker@$(git ls-remote git://github.com/venturemark/apiworker.git HEAD | awk '{print $1;}') + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/cfm@$(git ls-remote git://github.com/venturemark/cfm.git HEAD | awk '{print $1;}') + + - name: "Check Conformance Tests" + env: + CGO_ENABLED: "1" + APIWORKER_POSTMARK_TOKEN_ACCOUNT: "foo" + APIWORKER_POSTMARK_TOKEN_SERVER: "foo" + run: | + apiserver daemon --metrics-port 8081 & + apiworker daemon --metrics-port 8082 & + cd ./venturemark/cfm && go test ./... -race -tags conformance +` + +const templateApiWorker = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "cfm-test" + +on: "push" + +jobs: + cfm-test: + runs-on: "ubuntu-latest" + + services: + redis: + image: "redis" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/apiworker" + repository: "venturemark/apiworker" + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/cfm" + repository: "venturemark/cfm" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Test Dependency" + run: | + sudo apt update + sudo apt install gcc -y + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/apiserver@$(git ls-remote git://github.com/venturemark/apiserver.git HEAD | awk '{print $1;}') + + - name: "Install Test Dependency" + run: | + cd ./venturemark/apiworker && go install . + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/cfm@$(git ls-remote git://github.com/venturemark/cfm.git HEAD | awk '{print $1;}') + + - name: "Check Conformance Tests" + env: + CGO_ENABLED: "1" + APIWORKER_POSTMARK_TOKEN_ACCOUNT: "foo" + APIWORKER_POSTMARK_TOKEN_SERVER: "foo" + run: | + apiserver daemon --metrics-port 8081 & + apiworker daemon --metrics-port 8082 & + cd ./venturemark/cfm && go test ./... -race -tags conformance +` + +const templateCfm = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "cfm-test" + +on: "push" + +jobs: + cfm-test: + runs-on: "ubuntu-latest" + + services: + redis: + image: "redis" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/cfm" + repository: "venturemark/cfm" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Test Dependency" + run: | + sudo apt update + sudo apt install gcc -y + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/apiserver@$(git ls-remote git://github.com/venturemark/apiserver.git HEAD | awk '{print $1;}') + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/apiworker@$(git ls-remote git://github.com/venturemark/apiworker.git HEAD | awk '{print $1;}') + + - name: "Install Test Dependency" + env: + GO111MODULE: "on" + run: | + # TODO this needs to be "go install ." again once cfm can be compiled + # into an executable like apiserver and apiworker. + cd ./venturemark/cfm && go test ./... + + - name: "Check Conformance Tests" + env: + CGO_ENABLED: "1" + APIWORKER_POSTMARK_TOKEN_ACCOUNT: "foo" + APIWORKER_POSTMARK_TOKEN_SERVER: "foo" + run: | + apiserver daemon --metrics-port 8081 & + apiworker daemon --metrics-port 8082 & + cd ./venturemark/cfm && go test ./... -race -tags conformance +` + +const templateFlux = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "cfm-test" + +on: "push" + +jobs: + cfm-test: + runs-on: "ubuntu-latest" + + services: + redis: + image: "redis" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/cfm" + repository: "venturemark/cfm" + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + with: + path: "venturemark/flux" + repository: "venturemark/flux" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Test Dependency" + run: | + sudo apt update + sudo apt install gcc -y + + - name: "Install Test Dependency" + env: + GO111MODULE: "on" + run: | + go get -u github.com/xh3b4sd/dsm + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/apiserver@$(dsm search -r HelmRelease -n apiserver -k spec.values.image.tag | head -n 1) + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/apiworker@$(dsm search -r HelmRelease -n apiworker -k spec.values.image.tag | head -n 1) + + - name: "Install Test Dependency" + run: | + go get -u github.com/venturemark/cfm@$(git ls-remote git://github.com/venturemark/cfm.git HEAD | awk '{print $1;}') + + - name: "Check Conformance Tests" + env: + CGO_ENABLED: "1" + APIWORKER_POSTMARK_TOKEN_ACCOUNT: "foo" + APIWORKER_POSTMARK_TOKEN_SERVER: "foo" + run: | + apiserver daemon --metrics-port 8081 & + apiworker daemon --metrics-port 8082 & + cd ./venturemark/cfm && go test ./... -race -tags conformance +` diff --git a/cmd/create/command.go b/cmd/create/command.go new file mode 100644 index 0000000..c709cf9 --- /dev/null +++ b/cmd/create/command.go @@ -0,0 +1,253 @@ +package create + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/cmd/create/cfmtest" + "github.com/xh3b4sd/workflow/cmd/create/dependabot" + "github.com/xh3b4sd/workflow/cmd/create/dockergo" + "github.com/xh3b4sd/workflow/cmd/create/dockerts" + "github.com/xh3b4sd/workflow/cmd/create/dsmupdate" + "github.com/xh3b4sd/workflow/cmd/create/dsmverify" + "github.com/xh3b4sd/workflow/cmd/create/golang" + "github.com/xh3b4sd/workflow/cmd/create/npm" + "github.com/xh3b4sd/workflow/cmd/create/pbfgo" + "github.com/xh3b4sd/workflow/cmd/create/pbflint" + "github.com/xh3b4sd/workflow/cmd/create/pbfts" + "github.com/xh3b4sd/workflow/cmd/create/redigo" + "github.com/xh3b4sd/workflow/cmd/create/releasego" + "github.com/xh3b4sd/workflow/cmd/create/releases3" + "github.com/xh3b4sd/workflow/cmd/create/typescript" +) + +const ( + name = "create" + short = "Create github workflows and config files." + long = "Create github workflows and config files." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var err error + + var cfmTestCmd *cobra.Command + { + c := cfmtest.Config{ + Logger: config.Logger, + } + + cfmTestCmd, err = cfmtest.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var dependabotCmd *cobra.Command + { + c := dependabot.Config{ + Logger: config.Logger, + } + + dependabotCmd, err = dependabot.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var dockergoCmd *cobra.Command + { + c := dockergo.Config{ + Logger: config.Logger, + } + + dockergoCmd, err = dockergo.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var dockertsCmd *cobra.Command + { + c := dockerts.Config{ + Logger: config.Logger, + } + + dockertsCmd, err = dockerts.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var dsmUpdateCmd *cobra.Command + { + c := dsmupdate.Config{ + Logger: config.Logger, + } + + dsmUpdateCmd, err = dsmupdate.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var dsmVerifyCmd *cobra.Command + { + c := dsmverify.Config{ + Logger: config.Logger, + } + + dsmVerifyCmd, err = dsmverify.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var golangCmd *cobra.Command + { + c := golang.Config{ + Logger: config.Logger, + } + + golangCmd, err = golang.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var npmCmd *cobra.Command + { + c := npm.Config{ + Logger: config.Logger, + } + + npmCmd, err = npm.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var pbfgoCmd *cobra.Command + { + c := pbfgo.Config{ + Logger: config.Logger, + } + + pbfgoCmd, err = pbfgo.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var pbflintCmd *cobra.Command + { + c := pbflint.Config{ + Logger: config.Logger, + } + + pbflintCmd, err = pbflint.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var pbftsCmd *cobra.Command + { + c := pbfts.Config{ + Logger: config.Logger, + } + + pbftsCmd, err = pbfts.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var redigoCmd *cobra.Command + { + c := redigo.Config{ + Logger: config.Logger, + } + + redigoCmd, err = redigo.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var releasegoCmd *cobra.Command + { + c := releasego.Config{ + Logger: config.Logger, + } + + releasegoCmd, err = releasego.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var releases3Cmd *cobra.Command + { + c := releases3.Config{ + Logger: config.Logger, + } + + releases3Cmd, err = releases3.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var typescriptCmd *cobra.Command + { + c := typescript.Config{ + Logger: config.Logger, + } + + typescriptCmd, err = typescript.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var c *cobra.Command + { + r := &runner{ + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + c.AddCommand(cfmTestCmd) + c.AddCommand(dependabotCmd) + c.AddCommand(dockergoCmd) + c.AddCommand(dockertsCmd) + c.AddCommand(dsmUpdateCmd) + c.AddCommand(dsmVerifyCmd) + c.AddCommand(golangCmd) + c.AddCommand(npmCmd) + c.AddCommand(pbfgoCmd) + c.AddCommand(pbflintCmd) + c.AddCommand(pbftsCmd) + c.AddCommand(redigoCmd) + c.AddCommand(releasegoCmd) + c.AddCommand(releases3Cmd) + c.AddCommand(typescriptCmd) + } + + return c, nil +} diff --git a/cmd/create/dependabot/command.go b/cmd/create/dependabot/command.go new file mode 100644 index 0000000..e9084ad --- /dev/null +++ b/cmd/create/dependabot/command.go @@ -0,0 +1,44 @@ +package dependabot + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "dependabot" + short = "Create a dependabot workflow for e.g. golang and docker." + long = "Create a dependabot workflow for e.g. golang and docker." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/dependabot/error.go b/cmd/create/dependabot/error.go new file mode 100644 index 0000000..7b0ff8b --- /dev/null +++ b/cmd/create/dependabot/error.go @@ -0,0 +1,23 @@ +package dependabot + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/dependabot/flag.go b/cmd/create/dependabot/flag.go new file mode 100644 index 0000000..43ccf7c --- /dev/null +++ b/cmd/create/dependabot/flag.go @@ -0,0 +1,51 @@ +package dependabot + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Branch string + Reviewers []string + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Branch, "branch", "b", "main", "Dependabort target branch to merge pull requests into.") + cmd.Flags().StringSliceVarP(&f.Reviewers, "reviewers", "r", []string{}, "Reviewers assigned to dependabot PRs, e.g. xh3b4sd. Works with github usernames and teams.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Branch != "main" && f.Branch != "master" { + return tracer.Maskf(invalidFlagError, "-b/--branch must either be main or master") + } + } + + { + if len(f.Reviewers) == 0 { + return tracer.Maskf(invalidFlagError, "-r/--reviewers must not be empty") + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/dependabot/runner.go b/cmd/create/dependabot/runner.go new file mode 100644 index 0000000..b852a9c --- /dev/null +++ b/cmd/create/dependabot/runner.go @@ -0,0 +1,114 @@ +package dependabot + +import ( + "bytes" + "context" + "html/template" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/file" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) dependabotData() interface{} { + type Ecosystem struct { + Branch string + Name string + Reviewers []string + } + + type Data struct { + Command string + Ecosystems []Ecosystem + } + + var ecosystems []Ecosystem + { + if file.Exists("Dockerfile") { + ecosystems = append(ecosystems, Ecosystem{ + Branch: r.flag.Branch, + Name: "docker", + Reviewers: r.flag.Reviewers, + }) + } + + { + ecosystems = append(ecosystems, Ecosystem{ + Branch: r.flag.Branch, + Name: "github-actions", + Reviewers: r.flag.Reviewers, + }) + } + + if file.Exists("go.mod") && file.Exists("go.sum") { + ecosystems = append(ecosystems, Ecosystem{ + Branch: r.flag.Branch, + Name: "gomod", + Reviewers: r.flag.Reviewers, + }) + } + } + + return Data{ + Command: strings.Join(os.Args, " "), + Ecosystems: ecosystems, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/dependabot.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.dependabotData()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/dependabot/template.go b/cmd/create/dependabot/template.go new file mode 100644 index 0000000..1d23a07 --- /dev/null +++ b/cmd/create/dependabot/template.go @@ -0,0 +1,27 @@ +package dependabot + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +version: 2 +updates: +{{ range $e := .Ecosystems }} + - package-ecosystem: "{{ $e.Name }}" + directory: "/" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch"] + open-pull-requests-limit: 10 + reviewers: +{{- range $r := $e.Reviewers }} + - "{{ $r }}" +{{- end }} + schedule: + interval: "daily" + time: "04:00" + target-branch: "{{ $e.Branch }}" +{{ end }}` diff --git a/cmd/create/dockergo/command.go b/cmd/create/dockergo/command.go new file mode 100644 index 0000000..79e08b9 --- /dev/null +++ b/cmd/create/dockergo/command.go @@ -0,0 +1,44 @@ +package dockergo + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "dockergo" + short = "Create a docker workflow for building and pushing docker images of golang apps." + long = "Create a docker workflow for building and pushing docker images of golang apps." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/dockergo/error.go b/cmd/create/dockergo/error.go new file mode 100644 index 0000000..dbb4379 --- /dev/null +++ b/cmd/create/dockergo/error.go @@ -0,0 +1,23 @@ +package dockergo + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/dockergo/flag.go b/cmd/create/dockergo/flag.go new file mode 100644 index 0000000..22eb580 --- /dev/null +++ b/cmd/create/dockergo/flag.go @@ -0,0 +1,35 @@ +package dockergo + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/dockergo/runner.go b/cmd/create/dockergo/runner.go new file mode 100644 index 0000000..f010963 --- /dev/null +++ b/cmd/create/dockergo/runner.go @@ -0,0 +1,85 @@ +package dockergo + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + SetupGo: version.SetupGo, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/docker-go.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/dockergo/template.go b/cmd/create/dockergo/template.go new file mode 100644 index 0000000..8572aea --- /dev/null +++ b/cmd/create/dockergo/template.go @@ -0,0 +1,50 @@ +package dockergo + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "docker-go" + +on: "push" + +jobs: + docker-go: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Build Go Binary" + env: + CGO_ENABLED: "0" + run: | + go build . + + - name: "Setup Docker Buildx" + uses: "docker/setup-buildx-action@v1.3.0" + + - name: "Login Container Registry" + uses: "docker/login-action@v1.9.0" + with: + registry: "ghcr.io" + username: "${{ "{{" }} github.repository_owner {{ "}}" }}" + password: "${{ "{{" }} secrets.CONTAINER_REGISTRY_TOKEN {{ "}}" }}" + + - name: "Build Docker Image" + uses: "docker/build-push-action@v2.5.0" + with: + context: "." + push: true + tags: "ghcr.io/${{ "{{" }} github.repository {{ "}}" }}:${{ "{{" }} github.sha {{ "}}" }}" +` diff --git a/cmd/create/dockerts/command.go b/cmd/create/dockerts/command.go new file mode 100644 index 0000000..a651223 --- /dev/null +++ b/cmd/create/dockerts/command.go @@ -0,0 +1,44 @@ +package dockerts + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "dockerts" + short = "Create a docker workflow for building and pushing docker images of typescript apps." + long = "Create a docker workflow for building and pushing docker images of typescript apps." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/dockerts/error.go b/cmd/create/dockerts/error.go new file mode 100644 index 0000000..d356759 --- /dev/null +++ b/cmd/create/dockerts/error.go @@ -0,0 +1,23 @@ +package dockerts + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/dockerts/flag.go b/cmd/create/dockerts/flag.go new file mode 100644 index 0000000..07f9a7b --- /dev/null +++ b/cmd/create/dockerts/flag.go @@ -0,0 +1,35 @@ +package dockerts + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Node string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Node, "version-node", "n", version.Node, "Node version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Node == "" { + return tracer.Maskf(invalidFlagError, "-n/--version-node must not be empty") + } + + s := strings.Split(f.Version.Node, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-n/--version-node must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/dockerts/runner.go b/cmd/create/dockerts/runner.go new file mode 100644 index 0000000..32c3129 --- /dev/null +++ b/cmd/create/dockerts/runner.go @@ -0,0 +1,85 @@ +package dockerts + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Node: r.flag.Version.Node, + SetupNode: version.SetupNode, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/docker-ts.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/dockerts/template.go b/cmd/create/dockerts/template.go new file mode 100644 index 0000000..041ae86 --- /dev/null +++ b/cmd/create/dockerts/template.go @@ -0,0 +1,51 @@ +package dockerts + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "docker-ts" + +on: "push" + +jobs: + docker-ts: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Typescript Env" + uses: "actions/setup-node@v{{ .Version.SetupNode }}" + with: + node-version: "{{ .Version.Node }}" + + - name: "Install Typescript Dependencies" + run: | + npm install + + - name: "Build Typescript Project" + run: | + npm run build + + - name: "Setup Docker Buildx" + uses: "docker/setup-buildx-action@v1.3.0" + + - name: "Login Container Registry" + uses: "docker/login-action@v1.9.0" + with: + registry: "ghcr.io" + username: "${{ "{{" }} github.repository_owner {{ "}}" }}" + password: "${{ "{{" }} secrets.CONTAINER_REGISTRY_TOKEN {{ "}}" }}" + + - name: "Build Docker Image" + uses: "docker/build-push-action@v2.5.0" + with: + context: "." + push: true + tags: "ghcr.io/${{ "{{" }} github.repository {{ "}}" }}:${{ "{{" }} github.sha {{ "}}" }}" +` diff --git a/cmd/create/dsmupdate/command.go b/cmd/create/dsmupdate/command.go new file mode 100644 index 0000000..7f321f0 --- /dev/null +++ b/cmd/create/dsmupdate/command.go @@ -0,0 +1,44 @@ +package dsmupdate + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "dsmupdate" + short = "Create a mutating workflow for e.g. app versions." + long = "Create a mutating workflow for e.g. app versions." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/dsmupdate/error.go b/cmd/create/dsmupdate/error.go new file mode 100644 index 0000000..191a217 --- /dev/null +++ b/cmd/create/dsmupdate/error.go @@ -0,0 +1,23 @@ +package dsmupdate + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/dsmupdate/flag.go b/cmd/create/dsmupdate/flag.go new file mode 100644 index 0000000..347c0a9 --- /dev/null +++ b/cmd/create/dsmupdate/flag.go @@ -0,0 +1,45 @@ +package dsmupdate + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Repository struct { + Name string + } + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Repository.Name, "repository-name", "r", "", "Repository name to generate the workflow for, e.g. apiworker or webclient.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Repository.Name == "" { + return tracer.Maskf(invalidFlagError, "-r/--repository-name must not be empty") + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/dsmupdate/runner.go b/cmd/create/dsmupdate/runner.go new file mode 100644 index 0000000..7cbce48 --- /dev/null +++ b/cmd/create/dsmupdate/runner.go @@ -0,0 +1,93 @@ +package dsmupdate + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Repository struct { + Name string + } + + type Data struct { + Command string + Repository Repository + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Repository: Repository{ + Name: r.flag.Repository.Name, + }, + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + SetupGo: version.SetupGo, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/dsm-update.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/dsmupdate/template.go b/cmd/create/dsmupdate/template.go new file mode 100644 index 0000000..b188585 --- /dev/null +++ b/cmd/create/dsmupdate/template.go @@ -0,0 +1,82 @@ +package dsmupdate + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "dsm-update" + +on: + push: + branches: + - "main" + - "master" + +jobs: + dsm-update: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Decrypt Private Key" + run: | + go get github.com/xh3b4sd/red + red decrypt -i .github/asset/venturemark/flux/id_rsa.enc -o .github/asset/venturemark/flux/id_rsa -p '${{ "{{" }} secrets.RED_GPG_PASS_VENTUREMARK_FLUX {{ "}}" }}' + + - name: "Setup SSH Agent" + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + run: | + mkdir -p ~/.ssh + ssh-keyscan github.com >> ~/.ssh/known_hosts + ssh-agent -a ${SSH_AUTH_SOCK} > /dev/null + chmod 0600 .github/asset/venturemark/flux/id_rsa + ssh-add .github/asset/venturemark/flux/id_rsa + + - name: "Clone Go Code" + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + run: git clone git@github.com:venturemark/flux.git "${{ "{{" }} runner.temp {{ "}}" }}/venturemark/flux/" + + - name: "Setup Git Config" + working-directory: "${{ "{{" }} runner.temp {{ "}}" }}/venturemark/flux/" + run: | + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git remote set-url origin git@github.com:venturemark/flux.git + + - name: "Update Project Version" + working-directory: "${{ "{{" }} runner.temp {{ "}}" }}/venturemark/flux/" + run: | + go get github.com/xh3b4sd/dsm + dsm update -r HelmRelease -n {{ .Repository.Name }} -k spec.values.image.tag -v ${{ "{{" }} github.sha {{ "}}" }} + + - name: "Commit And Push" + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + working-directory: "${{ "{{" }} runner.temp {{ "}}" }}/venturemark/flux/" + run: | + if [[ $(git status --porcelain) ]]; then + git add . + git commit -m 'update {{ .Repository.Name }} version' + git push + fi + + - name: "Cleanup Build Container" + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + run: | + ssh-add -D + rm -Rf * +` diff --git a/cmd/create/dsmverify/command.go b/cmd/create/dsmverify/command.go new file mode 100644 index 0000000..9a24239 --- /dev/null +++ b/cmd/create/dsmverify/command.go @@ -0,0 +1,44 @@ +package dsmverify + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "dsmverify" + short = "Create a validation workflow for e.g. checking consistency." + long = "Create a validation workflow for e.g. checking consistency." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/dsmverify/error.go b/cmd/create/dsmverify/error.go new file mode 100644 index 0000000..542d09e --- /dev/null +++ b/cmd/create/dsmverify/error.go @@ -0,0 +1,23 @@ +package dsmverify + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/dsmverify/flag.go b/cmd/create/dsmverify/flag.go new file mode 100644 index 0000000..fd81592 --- /dev/null +++ b/cmd/create/dsmverify/flag.go @@ -0,0 +1,35 @@ +package dsmverify + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/dsmverify/runner.go b/cmd/create/dsmverify/runner.go new file mode 100644 index 0000000..82083e1 --- /dev/null +++ b/cmd/create/dsmverify/runner.go @@ -0,0 +1,85 @@ +package dsmverify + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + SetupGo: version.SetupGo, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/dsm-verify.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/dsmverify/template.go b/cmd/create/dsmverify/template.go new file mode 100644 index 0000000..a58e4e1 --- /dev/null +++ b/cmd/create/dsmverify/template.go @@ -0,0 +1,43 @@ +package dsmverify + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "dsm-verify" + +on: "push" + +jobs: + dsm-verify: + runs-on: "ubuntu-latest" + + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Test Dependency" + run: | + go get github.com/xh3b4sd/dsm + + - name: "Check ApiServer Version" + run: | + dsm verify -r HelmRelease -n apiserver -k spec.values.image.tag + + - name: "Check ApiWorker Version" + run: | + dsm verify -r HelmRelease -n apiworker -k spec.values.image.tag + + - name: "Check WebClient Version" + run: | + dsm verify -r HelmRelease -n webclient -k spec.values.image.tag +` diff --git a/cmd/create/error.go b/cmd/create/error.go new file mode 100644 index 0000000..743cef6 --- /dev/null +++ b/cmd/create/error.go @@ -0,0 +1,15 @@ +package create + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/cmd/create/golang/command.go b/cmd/create/golang/command.go new file mode 100644 index 0000000..4ea4df0 --- /dev/null +++ b/cmd/create/golang/command.go @@ -0,0 +1,44 @@ +package golang + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "golang" + short = "Create a golang workflow for e.g. running tests and checking formatting." + long = "Create a golang workflow for e.g. running tests and checking formatting." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/golang/error.go b/cmd/create/golang/error.go new file mode 100644 index 0000000..51ee67b --- /dev/null +++ b/cmd/create/golang/error.go @@ -0,0 +1,23 @@ +package golang + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/golang/flag.go b/cmd/create/golang/flag.go new file mode 100644 index 0000000..7898613 --- /dev/null +++ b/cmd/create/golang/flag.go @@ -0,0 +1,48 @@ +package golang + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Private string + User string + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Private, "private", "p", "", "GOPRIVATE string, e.g. github.com/org/rep.") + cmd.Flags().StringVarP(&f.User, "user", "u", "", "Github user for GOPRIVATE authentication, e.g. xh3b4sd.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Private != "" && f.User == "" { + return tracer.Maskf(invalidFlagError, "-p/--private and -u/--user must be given together") + } + if f.Private == "" && f.User != "" { + return tracer.Maskf(invalidFlagError, "-p/--private and -u/--user must be given together") + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/golang/runner.go b/cmd/create/golang/runner.go new file mode 100644 index 0000000..52c214a --- /dev/null +++ b/cmd/create/golang/runner.go @@ -0,0 +1,144 @@ +package golang + +import ( + "bytes" + "context" + "fmt" + "os" + "regexp" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/env" + "github.com/xh3b4sd/workflow/pkg/version" +) + +var ( + versionExpression = regexp.MustCompile(`go ([0-9]+\.[0-9]+)`) +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/go-build.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := "go.mod" + + b, err := os.ReadFile(p) + if os.IsNotExist(err) { + // Just fall through since it can happen that repositories do not + // always have a go.mod file. + } else if err != nil { + return tracer.Mask(err) + } else { + c := strings.Replace(string(b), currentVersion(b), desiredVersion(r.flag.Version.Golang), -1) + + err = os.WriteFile(p, []byte(c), 0600) + if err != nil { + return tracer.Mask(err) + } + } + } + + return nil +} + +func currentVersion(b []byte) string { + r := versionExpression.FindSubmatch(b) + if len(r) != 2 { + // FindSubmatch returns the full match and the capturing group. As such + // we expect 2 results, of which the first is the current version string + // we are interested in. + // + // ["go 1.15" "1.15"] + // + panic("must find two results") + } + + return string(r[0]) +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Env map[string]string + Private string + User string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Env: env.Env(), + Private: r.flag.Private, + User: r.flag.User, + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + GolangCiLint: version.GolangCiLint, + SetupGo: version.SetupGo, + }, + } +} + +func desiredVersion(v string) string { + s := strings.Split(v, ".") + if len(s) != 3 { + // The given version must be something like 1.15.2 which implies 3 parts + // when splitting at the period. + panic("must have 3 parts") + } + + return fmt.Sprintf("go %s.%s", s[0], s[1]) +} diff --git a/cmd/create/golang/template.go b/cmd/create/golang/template.go new file mode 100644 index 0000000..477ba41 --- /dev/null +++ b/cmd/create/golang/template.go @@ -0,0 +1,63 @@ +package golang + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "go-build" + +on: "push" + +jobs: + go-build: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" +{{ if .Private }} + - name: "Setup Private Dependencies" + env: + PAT: "${{ "{{" }} secrets.GOPRIVATE_PAT {{ "}}" }}" + run: | + git config --global url."https://{{ .User }}:${PAT}@github.com".insteadOf "https://github.com" +{{ end }} + - name: "Check Go Dependencies" +{{- if .Private }} + env: + GOPRIVATE: "{{ .Private }}" + PAT: "${{ "{{" }} secrets.GOPRIVATE_PAT {{ "}}" }}" +{{- end }} + run: | + go mod tidy + git diff --exit-code + + - name: "Check Go Tests" +{{- if .Env }} + env: +{{- end }} +{{- range $k, $v := .Env }} + {{ $k }}: "{{ $v }}" +{{- end }} + run: | + go test ./... -race + + - name: "Check Go Formatting" + run: | + test -z $(gofmt -l -s .) + + - name: "Check Go Linters" + run: | + curl -LOs https://github.com/golangci/golangci-lint/releases/download/v{{ .Version.GolangCiLint }}/golangci-lint-{{ .Version.GolangCiLint }}-linux-amd64.tar.gz + tar -xzf golangci-lint-{{ .Version.GolangCiLint }}-linux-amd64.tar.gz + ./golangci-lint-{{ .Version.GolangCiLint }}-linux-amd64/golangci-lint run +` diff --git a/cmd/create/npm/command.go b/cmd/create/npm/command.go new file mode 100644 index 0000000..107b4bc --- /dev/null +++ b/cmd/create/npm/command.go @@ -0,0 +1,44 @@ +package npm + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "npm" + short = "Create a npm workflow for e.g. building and publishing npm packages." + long = "Create a npm workflow for e.g. building and publishing npm packages." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/npm/error.go b/cmd/create/npm/error.go new file mode 100644 index 0000000..ca6e04a --- /dev/null +++ b/cmd/create/npm/error.go @@ -0,0 +1,23 @@ +package npm + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/npm/flag.go b/cmd/create/npm/flag.go new file mode 100644 index 0000000..0f5ef3a --- /dev/null +++ b/cmd/create/npm/flag.go @@ -0,0 +1,35 @@ +package npm + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Node string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Node, "version-node", "n", version.Node, "Node version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Node == "" { + return tracer.Maskf(invalidFlagError, "-n/--version-node must not be empty") + } + + s := strings.Split(f.Version.Node, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-n/--version-node must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/npm/runner.go b/cmd/create/npm/runner.go new file mode 100644 index 0000000..f6abbf7 --- /dev/null +++ b/cmd/create/npm/runner.go @@ -0,0 +1,85 @@ +package npm + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Node: r.flag.Version.Node, + SetupNode: version.SetupNode, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/npm-publish.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/npm/template.go b/cmd/create/npm/template.go new file mode 100644 index 0000000..33bc842 --- /dev/null +++ b/cmd/create/npm/template.go @@ -0,0 +1,57 @@ +package npm + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# +# Note that this workflow has several requirements in order to function +# correctly throughout the development process. See the desired sequence of +# steps below. +# +# 1. A developer bumps the semver version within the package.json. +# 2. A developer runs "npm install" to update package-lock.json. +# 3. A developer creates and merges a pull request for the version changes. +# 4. A developer creates a new github release according to step 1. +# 5. A Github action builds and publishes the new npm package into the Github package registry. +# + +name: "npm-publish" + +on: + release: + types: + - "created" + +jobs: + npm-publish: + permissions: + packages: write + + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Typescript Env" + uses: "actions/setup-node@v{{ .Version.SetupNode }}" + with: + node-version: "{{ .Version.Node }}" + registry-url: "https://npm.pkg.github.com" + + - name: "Install Typescript Dependencies" + run: | + npm install + + - name: "Build Typescript Project" + run: | + npm run build + + - name: "Publish NPM Package" + env: + NODE_AUTH_TOKEN: "${{ "{{" }} secrets.GITHUB_TOKEN {{ "}}" }}" + run: | + npm publish +` diff --git a/cmd/create/pbfgo/command.go b/cmd/create/pbfgo/command.go new file mode 100644 index 0000000..b0b3f5a --- /dev/null +++ b/cmd/create/pbfgo/command.go @@ -0,0 +1,57 @@ +package pbfgo + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "pbfgo" + short = "Create a protocol buffer workflow for golang code generation." + long = `Create a protocol buffer workflow for golang code generation. The workflow +generated here works in a setup of two Github repositories. Call them apischema +and gocode. The workflow generated with the following command is added to the +apischema repository. + + workflow create pbfgo -o xh3b4sd -r gocode + +In order to make the workflow function correctly a deploy key must be generated +and distributed. The public key is added as deploy key with write access to the +gocode repository. The private key is added as Github Action Secret to the +apischema repository. See the link below for more information. + + https://github.com/cpina/github-action-push-to-another-repository +` +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/pbfgo/error.go b/cmd/create/pbfgo/error.go new file mode 100644 index 0000000..308cb7d --- /dev/null +++ b/cmd/create/pbfgo/error.go @@ -0,0 +1,23 @@ +package pbfgo + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/pbfgo/flag.go b/cmd/create/pbfgo/flag.go new file mode 100644 index 0000000..18dceda --- /dev/null +++ b/cmd/create/pbfgo/flag.go @@ -0,0 +1,58 @@ +package pbfgo + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Github struct { + Organization string + Repository string + } + Version struct { + Golang string + Protoc string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Github.Organization, "github-organization", "o", "", "Github organization to generate code for.") + cmd.Flags().StringVarP(&f.Github.Repository, "github-repository", "r", "", "Github repository to generate code for.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") + cmd.Flags().StringVarP(&f.Version.Protoc, "version-protoc", "p", version.Protoc, "Protoc version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Github.Organization == "" { + return tracer.Maskf(invalidFlagError, "-o/--github-organization must not be empty") + } + if f.Github.Repository == "" { + return tracer.Maskf(invalidFlagError, "-r/--github-repository must not be empty") + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + { + if f.Version.Protoc == "" { + return tracer.Maskf(invalidFlagError, "-p/--version-protoc must not be empty") + } + } + + return nil +} diff --git a/cmd/create/pbfgo/runner.go b/cmd/create/pbfgo/runner.go new file mode 100644 index 0000000..017eb7c --- /dev/null +++ b/cmd/create/pbfgo/runner.go @@ -0,0 +1,83 @@ +package pbfgo + +import ( + "context" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/generator" + "github.com/xh3b4sd/workflow/pkg/generator/pbfgo" +) + +const ( + path = ".github/workflows/pbf-go.yaml" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + var err error + + var g generator.Interface + { + c := pbfgo.Config{ + Command: strings.Join(os.Args, " "), + FilePath: path, + GithubOrganization: r.flag.Github.Organization, + GithubRepository: r.flag.Github.Repository, + VersionGolang: r.flag.Version.Golang, + VersionProtoc: r.flag.Version.Protoc, + } + + g, err = pbfgo.New(c) + if err != nil { + return tracer.Mask(err) + } + } + + var b []byte + { + b, err = g.Workflow() + if err != nil { + return tracer.Mask(err) + } + } + + { + err := os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(path, b, 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/pbflint/command.go b/cmd/create/pbflint/command.go new file mode 100644 index 0000000..b6465bf --- /dev/null +++ b/cmd/create/pbflint/command.go @@ -0,0 +1,44 @@ +package pbflint + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "pbflint" + short = "Create a protocol buffer workflow for schema validation." + long = "Create a protocol buffer workflow for schema validation." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/pbflint/error.go b/cmd/create/pbflint/error.go new file mode 100644 index 0000000..5440495 --- /dev/null +++ b/cmd/create/pbflint/error.go @@ -0,0 +1,23 @@ +package pbflint + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/pbflint/flag.go b/cmd/create/pbflint/flag.go new file mode 100644 index 0000000..af5e29c --- /dev/null +++ b/cmd/create/pbflint/flag.go @@ -0,0 +1,43 @@ +package pbflint + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Golang string + Protoc string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") + cmd.Flags().StringVarP(&f.Version.Protoc, "version-protoc", "p", version.Protoc, "Protoc version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + { + if f.Version.Protoc == "" { + return tracer.Maskf(invalidFlagError, "-p/--version-protoc must not be empty") + } + } + + return nil +} diff --git a/cmd/create/pbflint/runner.go b/cmd/create/pbflint/runner.go new file mode 100644 index 0000000..4c2a68e --- /dev/null +++ b/cmd/create/pbflint/runner.go @@ -0,0 +1,81 @@ +package pbflint + +import ( + "context" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/generator" + "github.com/xh3b4sd/workflow/pkg/generator/pbflint" +) + +const ( + path = ".github/workflows/pbf-lint.yaml" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + var err error + + var g generator.Interface + { + c := pbflint.Config{ + Command: strings.Join(os.Args, " "), + FilePath: path, + VersionGolang: r.flag.Version.Golang, + VersionProtoc: r.flag.Version.Protoc, + } + + g, err = pbflint.New(c) + if err != nil { + return tracer.Mask(err) + } + } + + var b []byte + { + b, err = g.Workflow() + if err != nil { + return tracer.Mask(err) + } + } + + { + err := os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(path, b, 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/pbfts/command.go b/cmd/create/pbfts/command.go new file mode 100644 index 0000000..81f2cda --- /dev/null +++ b/cmd/create/pbfts/command.go @@ -0,0 +1,57 @@ +package pbfts + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "pbfts" + short = "Create a protocol buffer workflow for typescript code generation." + long = `Create a protocol buffer workflow for typescript code generation. The workflow +generated here works in a setup of two Github repositories. Call them apischema +and tscode. The workflow generated with the following command is added to the +apischema repository. + + workflow create pbfts -o xh3b4sd -r tscode + +In order to make the workflow function correctly a deploy key must be generated +and distributed. The public key is added as deploy key with write access to the +gocode repository. The private key is added as Github Action Secret to the +apischema repository. See the link below for more information. + + https://github.com/cpina/github-action-push-to-another-repository +` +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/pbfts/error.go b/cmd/create/pbfts/error.go new file mode 100644 index 0000000..feb04c6 --- /dev/null +++ b/cmd/create/pbfts/error.go @@ -0,0 +1,23 @@ +package pbfts + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/pbfts/flag.go b/cmd/create/pbfts/flag.go new file mode 100644 index 0000000..a1c1da7 --- /dev/null +++ b/cmd/create/pbfts/flag.go @@ -0,0 +1,58 @@ +package pbfts + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Github struct { + Organization string + Repository string + } + Version struct { + Node string + Protoc string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Github.Organization, "github-organization", "o", "", "Github organization to generate code for.") + cmd.Flags().StringVarP(&f.Github.Repository, "github-repository", "r", "", "Github repository to generate code for.") + cmd.Flags().StringVarP(&f.Version.Node, "version-node", "n", version.Node, "Node version to use in, e.g. workflow files.") + cmd.Flags().StringVarP(&f.Version.Protoc, "version-protoc", "p", version.Protoc, "Protoc version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Github.Organization == "" { + return tracer.Maskf(invalidFlagError, "-o/--github-organization must not be empty") + } + if f.Github.Repository == "" { + return tracer.Maskf(invalidFlagError, "-r/--github-repository must not be empty") + } + } + + { + if f.Version.Node == "" { + return tracer.Maskf(invalidFlagError, "-n/--version-node must not be empty") + } + + s := strings.Split(f.Version.Node, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-n/--version-node must have 3 parts like 1.15.2") + } + } + + { + if f.Version.Protoc == "" { + return tracer.Maskf(invalidFlagError, "-p/--version-protoc must not be empty") + } + } + + return nil +} diff --git a/cmd/create/pbfts/runner.go b/cmd/create/pbfts/runner.go new file mode 100644 index 0000000..bd0a271 --- /dev/null +++ b/cmd/create/pbfts/runner.go @@ -0,0 +1,83 @@ +package pbfts + +import ( + "context" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/generator" + "github.com/xh3b4sd/workflow/pkg/generator/pbfts" +) + +const ( + path = ".github/workflows/pbf-ts.yaml" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + var err error + + var g generator.Interface + { + c := pbfts.Config{ + Command: strings.Join(os.Args, " "), + FilePath: path, + GithubOrganization: r.flag.Github.Organization, + GithubRepository: r.flag.Github.Repository, + VersionNode: r.flag.Version.Node, + VersionProtoc: r.flag.Version.Protoc, + } + + g, err = pbfts.New(c) + if err != nil { + return tracer.Mask(err) + } + } + + var b []byte + { + b, err = g.Workflow() + if err != nil { + return tracer.Mask(err) + } + } + + { + err := os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(path, b, 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/redigo/command.go b/cmd/create/redigo/command.go new file mode 100644 index 0000000..6680dda --- /dev/null +++ b/cmd/create/redigo/command.go @@ -0,0 +1,44 @@ +package redigo + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "redigo" + short = "Create a golang workflow for e.g. running redis conformance tests." + long = "Create a golang workflow for e.g. running redis conformance tests." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/redigo/error.go b/cmd/create/redigo/error.go new file mode 100644 index 0000000..3beb875 --- /dev/null +++ b/cmd/create/redigo/error.go @@ -0,0 +1,23 @@ +package redigo + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/redigo/flag.go b/cmd/create/redigo/flag.go new file mode 100644 index 0000000..ac2403c --- /dev/null +++ b/cmd/create/redigo/flag.go @@ -0,0 +1,35 @@ +package redigo + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/redigo/runner.go b/cmd/create/redigo/runner.go new file mode 100644 index 0000000..7af837a --- /dev/null +++ b/cmd/create/redigo/runner.go @@ -0,0 +1,95 @@ +package redigo + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Golang: desiredVersion(r.flag.Version.Golang), + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/go-redis.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} + +func desiredVersion(v string) string { + s := strings.Split(v, ".") + if len(s) != 3 { + // The given version must be something like 1.15.2 which implies 3 parts + // when splitting at the period. + panic("must have 3 parts") + } + + return s[0] + "." + s[1] +} diff --git a/cmd/create/redigo/template.go b/cmd/create/redigo/template.go new file mode 100644 index 0000000..765af96 --- /dev/null +++ b/cmd/create/redigo/template.go @@ -0,0 +1,53 @@ +package redigo + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "go-redis" + +on: "push" + +jobs: + go-redis: + runs-on: "ubuntu-latest" + container: "golang:{{ .Version.Golang }}-alpine" + + services: + redis: + image: "redis" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + redis-sentinel: + image: "bitnami/redis-sentinel:6.0" + ports: + - "26379:26379" + env: + REDIS_SENTINEL_QUORUM: "1" + + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Install Race Dependency" + run: | + apk add gcc + apk add musl-dev + + - name: "Check Go Tests" + env: + CGO_ENABLED: "1" + REDIS_HOST: "redis" + REDIS_PORT: "6379" + REDIS_SENTINEL_HOST: "redis-sentinel" + REDIS_SENTINEL_PORT: "26379" + run: | + go test ./... -race -tags single,sentinel +` diff --git a/cmd/create/releasego/command.go b/cmd/create/releasego/command.go new file mode 100644 index 0000000..2db4833 --- /dev/null +++ b/cmd/create/releasego/command.go @@ -0,0 +1,44 @@ +package releasego + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "releasego" + short = "Create a golang workflow for e.g. uploading cross compiled release assets." + long = "Create a golang workflow for e.g. uploading cross compiled release assets." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/releasego/error.go b/cmd/create/releasego/error.go new file mode 100644 index 0000000..ea71859 --- /dev/null +++ b/cmd/create/releasego/error.go @@ -0,0 +1,23 @@ +package releasego + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/releasego/flag.go b/cmd/create/releasego/flag.go new file mode 100644 index 0000000..3c6a6ef --- /dev/null +++ b/cmd/create/releasego/flag.go @@ -0,0 +1,76 @@ +package releasego + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Release struct { + Assets string + } + Repository struct { + Name string + Path string + } + Variable struct { + GitSha string + GitTag string + } + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Release.Assets, "release-assets", "a", "darwin/amd64,linux/amd64", "Binary architectures to compile.") + cmd.Flags().StringVarP(&f.Repository.Name, "repository-name", "n", "", "Repository name to generate the workflow for, e.g. apiworker or webclient.") + cmd.Flags().StringVarP(&f.Repository.Path, "repository-path", "p", "pkg/project", "Repository path to compile linker flags into.") + cmd.Flags().StringVarP(&f.Variable.GitSha, "variable-gitsha", "s", "sha", "The variable name receiving the binary's git sha.") + cmd.Flags().StringVarP(&f.Variable.GitTag, "variable-gittag", "t", "tag", "The variable name receiving the binary's git tag.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Release.Assets == "" { + return tracer.Maskf(invalidFlagError, "-a/--release-assets must not be empty") + } + } + + { + if f.Repository.Name == "" { + return tracer.Maskf(invalidFlagError, "-n/--repository-name must not be empty") + } + if f.Repository.Path == "" { + return tracer.Maskf(invalidFlagError, "-p/--repository-path must not be empty") + } + } + + { + if f.Variable.GitSha == "" { + return tracer.Maskf(invalidFlagError, "-s/--variable-gitsha must not be empty") + } + + if f.Variable.GitTag == "" { + return tracer.Maskf(invalidFlagError, "-t/--variable-gittag must not be empty") + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/releasego/runner.go b/cmd/create/releasego/runner.go new file mode 100644 index 0000000..3edc973 --- /dev/null +++ b/cmd/create/releasego/runner.go @@ -0,0 +1,124 @@ +package releasego + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Release struct { + Assets map[string]string + } + + type Repository struct { + Name string + Path string + } + + type Variable struct { + GitSha string + GitTag string + } + + type Data struct { + Command string + Release Release + Repository Repository + Variable Variable + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Release: Release{ + Assets: assets(r.flag.Release.Assets), + }, + Repository: Repository{ + Name: r.flag.Repository.Name, + Path: r.flag.Repository.Path, + }, + Variable: Variable{ + GitSha: r.flag.Variable.GitSha, + GitTag: r.flag.Variable.GitTag, + }, + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + SetupGo: version.SetupGo, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/go-release.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} + +func assets(str string) map[string]string { + ass := map[string]string{} + + for _, s := range strings.Split(str, ",") { + spl := strings.Split(s, "/") + ass[spl[0]] = spl[1] + } + + return ass +} diff --git a/cmd/create/releasego/template.go b/cmd/create/releasego/template.go new file mode 100644 index 0000000..986629d --- /dev/null +++ b/cmd/create/releasego/template.go @@ -0,0 +1,43 @@ +package releasego + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "go-release" + +on: + push: + tags: + - "v*.*.*" + +jobs: + release: + runs-on: "ubuntu-latest" + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Cross Compile Binaries" + run: | +{{- range $k, $v := .Release.Assets }} + GOOS={{ $k }} GOARCH={{ $v }} go build -o {{ $.Repository.Name }}-{{ $k }}-{{ $v }} -ldflags="-X 'github.com/${{ "{{" }} github.repository_owner {{ "}}" }}/{{ $.Repository.Name }}/{{ $.Repository.Path }}.{{ $.Variable.GitSha }}=${{ "{{" }} github.sha {{ "}}" }}' -X 'github.com/${{ "{{" }} github.repository_owner {{ "}}" }}/{{ $.Repository.Name }}/{{ $.Repository.Path }}.{{ $.Variable.GitTag }}=${{ "{{" }} github.ref_name {{ "}}" }}'" +{{- end }} + + - name: "Upload To Github" + uses: "softprops/action-gh-release@v1" + with: + files: | +{{- range $k, $v := .Release.Assets }} + {{ $.Repository.Name }}-{{ $k }}-{{ $v }} +{{- end }} +` diff --git a/cmd/create/releases3/command.go b/cmd/create/releases3/command.go new file mode 100644 index 0000000..eb8306c --- /dev/null +++ b/cmd/create/releases3/command.go @@ -0,0 +1,78 @@ +package releases3 + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "releases3" + short = "Create a golang workflow for e.g. uploading cross compiled release assets." + long = `Create a golang workflow for e.g. uploading cross compiled release assets. +Uploading release assets to Github is straight forward and works out of the box. +Uploading release assets to AWS S3 requires some configurations in the +respective Github repository and AWS account. + +The respective Github repository MUST have AWS typical credentials configured +via repository secrets. The required secret names must be set as follows. + + AWS_ACCESS_KEY + AWS_SECRET_KEY + +The respective AWS account SHOULD use a restricted IAM policy based on the least +priviledged principle. This IAM policy would be attached to the AWS credentials +used in this workflow. A working example of the minimal required IAM policy +might look like shown below. + + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutBucketOwnershipControls", + "s3:CreateBucket" + ], + "Resource": [ + "arn:aws:s3:::", + "arn:aws:s3:::/*" + ] + } + ] + } +` +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/releases3/error.go b/cmd/create/releases3/error.go new file mode 100644 index 0000000..d5b2fe1 --- /dev/null +++ b/cmd/create/releases3/error.go @@ -0,0 +1,23 @@ +package releases3 + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/releases3/flag.go b/cmd/create/releases3/flag.go new file mode 100644 index 0000000..425b2e2 --- /dev/null +++ b/cmd/create/releases3/flag.go @@ -0,0 +1,75 @@ +package releases3 + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + AWS struct { + Bucket string + Region string + } + Release struct { + Assets string + } + Repository struct { + Name string + Path string + } + Version struct { + Golang string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.AWS.Bucket, "aws-bucket", "b", "", "AWS S3 bucket to upload to.") + cmd.Flags().StringVarP(&f.AWS.Region, "aws-region", "r", "eu-central-1", "AWS S3 region to upload to.") + cmd.Flags().StringVarP(&f.Release.Assets, "release-assets", "a", "darwin/amd64,linux/amd64", "Binary architectures to compile.") + cmd.Flags().StringVarP(&f.Repository.Name, "repository-name", "n", "", "Repository name to generate the workflow for, e.g. apiworker or webclient.") + cmd.Flags().StringVarP(&f.Repository.Path, "repository-path", "p", "pkg/project", "Repository path to compile linker flags into.") + cmd.Flags().StringVarP(&f.Version.Golang, "version-golang", "g", version.Golang, "Golang version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.AWS.Bucket == "" { + return tracer.Maskf(invalidFlagError, "-b/--aws-bucket must not be empty") + } + if f.AWS.Region == "" { + return tracer.Maskf(invalidFlagError, "-r/--aws-region must not be empty") + } + } + + { + if f.Release.Assets == "" { + return tracer.Maskf(invalidFlagError, "-a/--release-assets must not be empty") + } + } + + { + if f.Repository.Name == "" { + return tracer.Maskf(invalidFlagError, "-n/--repository-name must not be empty") + } + if f.Repository.Path == "" { + return tracer.Maskf(invalidFlagError, "-p/--repository-path must not be empty") + } + } + + { + if f.Version.Golang == "" { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must not be empty") + } + + s := strings.Split(f.Version.Golang, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-g/--version-golang must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/releases3/runner.go b/cmd/create/releases3/runner.go new file mode 100644 index 0000000..476ef2a --- /dev/null +++ b/cmd/create/releases3/runner.go @@ -0,0 +1,124 @@ +package releases3 + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type AWS struct { + Bucket string + Region string + } + + type Release struct { + Assets map[string]string + } + + type Repository struct { + Name string + Path string + } + + type Data struct { + AWS AWS + Command string + Release Release + Repository Repository + Version version.Version + } + + return Data{ + AWS: AWS{ + Bucket: r.flag.AWS.Bucket, + Region: r.flag.AWS.Region, + }, + Command: strings.Join(os.Args, " "), + Release: Release{ + Assets: assets(r.flag.Release.Assets), + }, + Repository: Repository{ + Name: r.flag.Repository.Name, + Path: r.flag.Repository.Path, + }, + Version: version.Version{ + Checkout: version.Checkout, + Golang: r.flag.Version.Golang, + SetupGo: version.SetupGo, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/s3-release.yaml" + + t, err := template.New(p).Parse(templateWorkflow) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} + +func assets(str string) map[string]string { + ass := map[string]string{} + + for _, s := range strings.Split(str, ",") { + spl := strings.Split(s, "/") + ass[spl[0]] = spl[1] + } + + return ass +} diff --git a/cmd/create/releases3/template.go b/cmd/create/releases3/template.go new file mode 100644 index 0000000..a97d76b --- /dev/null +++ b/cmd/create/releases3/template.go @@ -0,0 +1,52 @@ +package releases3 + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "s3-release" + +on: + push: + tags: + - "v*.*.*" + +jobs: + release: + runs-on: "ubuntu-latest" + steps: + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Cross Compile Binaries" + run: | +{{- range $k, $v := .Release.Assets }} + GOOS={{ $k }} GOARCH={{ $v }} go build -o {{ $.Repository.Name }}-{{ $k }}-{{ $v }} -ldflags="-X 'github.com/${{ "{{" }} github.repository_owner {{ "}}" }}/{{ $.Repository.Name }}/{{ $.Repository.Path }}.gitSHA=${{ "{{" }} github.sha {{ "}}" }}' -X 'github.com/${{ "{{" }} github.repository_owner {{ "}}" }}/{{ $.Repository.Name }}/{{ $.Repository.Path }}.version=${{ "{{" }} github.ref_name {{ "}}" }}'" +{{- end }} + + - name: "Configure AWS Credentials" + uses: "aws-actions/configure-aws-credentials@v1" + with: + aws-access-key-id: "${{ "{{" }} secrets.AWS_ACCESS_KEY {{ "}}" }}" + aws-secret-access-key: "${{ "{{" }} secrets.AWS_SECRET_KEY {{ "}}" }}" + aws-region: "{{ .AWS.Region }}" + + - name: "Ensure S3 Bucket" + run: | + aws s3api create-bucket --bucket {{ .AWS.Bucket }} --create-bucket-configuration '{"LocationConstraint": "{{ .AWS.Region }}"}' || true + + - name: "Upload To S3" + run: | +{{- range $k, $v := .Release.Assets }} + aws s3 cp {{ $.Repository.Name }}-{{ $k }}-{{ $v }} s3://{{ $.AWS.Bucket }}/{{ $.Repository.Name }}/${{ "{{" }} github.ref_name {{ "}}" }}/{{ $.Repository.Name }}-{{ $k }}-{{ $v }} +{{- end }} +` diff --git a/cmd/create/runner.go b/cmd/create/runner.go new file mode 100644 index 0000000..3025b76 --- /dev/null +++ b/cmd/create/runner.go @@ -0,0 +1,33 @@ +package create + +import ( + "context" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +type runner struct { + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + err := cmd.Help() + if err != nil { + return tracer.Mask(err) + } + + return nil +} diff --git a/cmd/create/typescript/command.go b/cmd/create/typescript/command.go new file mode 100644 index 0000000..66788a6 --- /dev/null +++ b/cmd/create/typescript/command.go @@ -0,0 +1,44 @@ +package typescript + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "typescript" + short = "Create a typescript workflow for e.g. building and formatting typescript code." + long = "Create a typescript workflow for e.g. building and formatting typescript code." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + f := &flag{} + + r := &runner{ + flag: f, + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + f.Init(c) + } + + return c, nil +} diff --git a/cmd/create/typescript/error.go b/cmd/create/typescript/error.go new file mode 100644 index 0000000..95caf25 --- /dev/null +++ b/cmd/create/typescript/error.go @@ -0,0 +1,23 @@ +package typescript + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} + +var invalidFlagError = &tracer.Error{ + Kind: "invalidFlagError", +} + +func IsInvalidFlag(err error) bool { + return errors.Is(err, invalidFlagError) +} diff --git a/cmd/create/typescript/flag.go b/cmd/create/typescript/flag.go new file mode 100644 index 0000000..d2184e7 --- /dev/null +++ b/cmd/create/typescript/flag.go @@ -0,0 +1,35 @@ +package typescript + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type flag struct { + Version struct { + Node string + } +} + +func (f *flag) Init(cmd *cobra.Command) { + cmd.Flags().StringVarP(&f.Version.Node, "version-node", "n", version.Node, "Node version to use in, e.g. workflow files.") +} + +func (f *flag) Validate() error { + { + if f.Version.Node == "" { + return tracer.Maskf(invalidFlagError, "-n/--version-node must not be empty") + } + + s := strings.Split(f.Version.Node, ".") + if len(s) != 3 { + return tracer.Maskf(invalidFlagError, "-n/--version-node must have 3 parts like 1.15.2") + } + } + + return nil +} diff --git a/cmd/create/typescript/runner.go b/cmd/create/typescript/runner.go new file mode 100644 index 0000000..c91e919 --- /dev/null +++ b/cmd/create/typescript/runner.go @@ -0,0 +1,85 @@ +package typescript + +import ( + "bytes" + "context" + "os" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/version" +) + +type runner struct { + flag *flag + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.flag.Validate() + if err != nil { + return tracer.Mask(err) + } + + err = r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: strings.Join(os.Args, " "), + Version: version.Version{ + Checkout: version.Checkout, + Node: r.flag.Version.Node, + SetupNode: version.SetupNode, + }, + } +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + { + p := ".github/workflows/" + + err := os.MkdirAll(p, os.ModePerm) + if err != nil { + return tracer.Mask(err) + } + } + + { + p := ".github/workflows/typescript.yaml" + + t, err := template.New(p).Parse(templateTypescript) + if err != nil { + return tracer.Mask(err) + } + + var b bytes.Buffer + err = t.ExecuteTemplate(&b, p, r.data()) + if err != nil { + return tracer.Mask(err) + } + + err = os.WriteFile(p, b.Bytes(), 0600) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} diff --git a/cmd/create/typescript/template.go b/cmd/create/typescript/template.go new file mode 100644 index 0000000..f1ca3aa --- /dev/null +++ b/cmd/create/typescript/template.go @@ -0,0 +1,54 @@ +package typescript + +const templateTypescript = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "typescript" + +on: "push" + +jobs: + typescript: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Typescript Env" + uses: "actions/setup-node@v{{ .Version.SetupNode }}" + with: + node-version: "{{ .Version.Node }}" + + - name: "Install Typescript Dependencies" + run: | + npm install + npm install prettier --global + + - name: "Check Git Project" + run: | + if [[ $(git status --porcelain) ]]; then + echo "found dirty files" + exit 1 + fi + + - name: "Check Typescript Formatting" + run: | + prettier -c $(find ./src -name "*.ts" -o -name "*.tsx") + + - name: "Check Typescript Tests" + run: | + npm run test --if-present + + - name: "Check Typescript Coverage" + run: | + npm run cover --if-present + + - name: "Build Typescript Project" + run: | + npm run build --if-present +` diff --git a/cmd/error.go b/cmd/error.go new file mode 100644 index 0000000..42fd481 --- /dev/null +++ b/cmd/error.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/cmd/runner.go b/cmd/runner.go new file mode 100644 index 0000000..440e038 --- /dev/null +++ b/cmd/runner.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +type runner struct { + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + err := cmd.Help() + if err != nil { + return tracer.Mask(err) + } + + return nil +} diff --git a/cmd/update/all/command.go b/cmd/update/all/command.go new file mode 100644 index 0000000..36b347d --- /dev/null +++ b/cmd/update/all/command.go @@ -0,0 +1,59 @@ +package all + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "all" + short = "Update all github workflows to the latest version." + long = `Update all github workflows to the latest version. When creating a new +workflow file the original command instruction in form of os.Args is written +to the header of the workflow file. A typical workflow file header looks like +the following. + + # + # Do not edit. This file was generated via the "workflow" command line tool. + # More information about the tool can be found at github.com/xh3b4sd/workflow. + # + # workflow create dependabot -r xh3b4sd + # + +This information of the executable command is used to make workflow updates +reproducible. All workflow files within the github specific workflow +directory are inspected when collecting command instructions. Once all +commands are known they are executed dynamically while new behaviour is +applied. + + .github/workflows/ + +` +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var c *cobra.Command + { + r := &runner{ + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + } + + return c, nil +} diff --git a/cmd/update/all/error.go b/cmd/update/all/error.go new file mode 100644 index 0000000..1b36ffe --- /dev/null +++ b/cmd/update/all/error.go @@ -0,0 +1,23 @@ +package all + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var commandNotFoundError = &tracer.Error{ + Kind: "commandNotFoundError", +} + +func IsCommandNotFound(err error) bool { + return errors.Is(err, commandNotFoundError) +} + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/cmd/update/all/runner.go b/cmd/update/all/runner.go new file mode 100644 index 0000000..611a345 --- /dev/null +++ b/cmd/update/all/runner.go @@ -0,0 +1,117 @@ +package all + +import ( + "context" + "os" + + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/parser" + "github.com/xh3b4sd/workflow/pkg/parser/command" +) + +type runner struct { + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + var err error + + var p parser.Interface + { + c := command.Config{ + FileSystem: afero.NewOsFs(), + + WorkflowPath: ".github/", + } + + p, err = command.New(c) + if err != nil { + return tracer.Mask(err) + } + } + + var l [][]string + { + l, err = p.Parse() + if err != nil { + return tracer.Mask(err) + } + } + + // We are going to mess with the command line args of the program's process + // during the update command execution. In order to be "good" citizens we + // set the os.Args back to the value they had before once we are done with + // the execution of other commands. + { + args := os.Args + defer func() { + os.Args = args + }() + } + + for _, a := range l { + c := cmd.Root() + + c, err = commandFor("create", c.Commands()) + if err != nil { + return tracer.Mask(err) + } + + // At this point we have the dynamic command. The implementation of the + // parser interface guarantees that the third element in the argument + // list is the workflow command we can execute dynamically. + // + // workflow create dependabot + // workflow create golang + // + c, err = commandFor(a[2], c.Commands()) + if err != nil { + return tracer.Mask(err) + } + + // We need to overwrite the os.Args because the commands we are going to + // execute dynamically use os.Args to write the proper command of the + // workflow creation to the header of the workflow file. Only due to + // this mechanism we enable reproducible workflow updates. + os.Args = a + + // The command we execute dynamically needs to parse its own flags in + // order to function correctly. + err = c.Flags().Parse(a[1:]) + if err != nil { + return tracer.Mask(err) + } + + err = c.RunE(c, nil) + if err != nil { + return tracer.Mask(err) + } + } + + return nil +} + +func commandFor(action string, cmds []*cobra.Command) (*cobra.Command, error) { + for _, c := range cmds { + if c.Name() == action { + return c, nil + } + } + + return nil, tracer.Maskf(commandNotFoundError, action) +} diff --git a/cmd/update/command.go b/cmd/update/command.go new file mode 100644 index 0000000..89ca630 --- /dev/null +++ b/cmd/update/command.go @@ -0,0 +1,57 @@ +package update + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/cmd/update/all" +) + +const ( + name = "update" + short = "Update github workflows and config files." + long = "Update github workflows and config files." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + var err error + + var allCmd *cobra.Command + { + c := all.Config{ + Logger: config.Logger, + } + + allCmd, err = all.New(c) + if err != nil { + return nil, tracer.Mask(err) + } + } + + var c *cobra.Command + { + r := &runner{ + logger: config.Logger, + } + + c = &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + c.AddCommand(allCmd) + } + + return c, nil +} diff --git a/cmd/update/error.go b/cmd/update/error.go new file mode 100644 index 0000000..d3a1451 --- /dev/null +++ b/cmd/update/error.go @@ -0,0 +1,15 @@ +package update + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/cmd/update/runner.go b/cmd/update/runner.go new file mode 100644 index 0000000..9990e73 --- /dev/null +++ b/cmd/update/runner.go @@ -0,0 +1,33 @@ +package update + +import ( + "context" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +type runner struct { + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + err := cmd.Help() + if err != nil { + return tracer.Mask(err) + } + + return nil +} diff --git a/cmd/version/command.go b/cmd/version/command.go new file mode 100644 index 0000000..653e16d --- /dev/null +++ b/cmd/version/command.go @@ -0,0 +1,36 @@ +package version + +import ( + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" +) + +const ( + name = "version" + short = "Print version information of this command line tool." + long = "Print version information of this command line tool." +) + +type Config struct { + Logger logger.Interface +} + +func New(config Config) (*cobra.Command, error) { + if config.Logger == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.Logger must not be empty", config) + } + + r := &runner{ + logger: config.Logger, + } + + c := &cobra.Command{ + Use: name, + Short: short, + Long: long, + RunE: r.Run, + } + + return c, nil +} diff --git a/cmd/version/error.go b/cmd/version/error.go new file mode 100644 index 0000000..c9ea3b8 --- /dev/null +++ b/cmd/version/error.go @@ -0,0 +1,15 @@ +package version + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/cmd/version/runner.go b/cmd/version/runner.go new file mode 100644 index 0000000..8b2eb36 --- /dev/null +++ b/cmd/version/runner.go @@ -0,0 +1,40 @@ +package version + +import ( + "context" + "fmt" + "os" + "runtime" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/pkg/project" +) + +type runner struct { + logger logger.Interface +} + +func (r *runner) Run(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + err := r.run(ctx, cmd, args) + if err != nil { + return tracer.Mask(err) + } + + return nil +} + +func (r *runner) run(ctx context.Context, cmd *cobra.Command, args []string) error { + fmt.Fprintf(os.Stdout, "Git Commit %s\n", project.GitSHA()) + fmt.Fprintf(os.Stdout, "Go Version %s\n", runtime.Version()) + fmt.Fprintf(os.Stdout, "Go Arch %s\n", runtime.GOARCH) + fmt.Fprintf(os.Stdout, "Go OS %s\n", runtime.GOOS) + fmt.Fprintf(os.Stdout, "Source %s\n", project.Source()) + fmt.Fprintf(os.Stdout, "Version %s\n", project.Version()) + + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..65b69b4 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/xh3b4sd/workflow + +go 1.21 + +require ( + github.com/google/go-cmp v0.5.9 + github.com/spf13/afero v1.10.0 + github.com/spf13/cobra v1.7.0 + github.com/xh3b4sd/logger v0.7.1 + github.com/xh3b4sd/tracer v0.10.1 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/text v0.4.0 // indirect +) + +retract [v0.0.0, v0.14.0] diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..71d34a7 --- /dev/null +++ b/go.sum @@ -0,0 +1,453 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xh3b4sd/logger v0.7.1 h1:3UebFmMB+KPhUOvO8iGm8FkKpixmG8alDL+IICVGfBs= +github.com/xh3b4sd/logger v0.7.1/go.mod h1:yGeKvRLw683dCu5TEziQAdJcsw1JGCTD9pNLCv9X1Bk= +github.com/xh3b4sd/tracer v0.10.1 h1:wuzVeQtqYsy51Nkb7P/XavZ8rQYptxxmlM1ivFJ8dXE= +github.com/xh3b4sd/tracer v0.10.1/go.mod h1:8nRbNXXhSSDmPfyoRRYgwQt/RpzAUH+zNsagP9InH8M= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..3b93fc7 --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + + "github.com/spf13/cobra" + "github.com/xh3b4sd/logger" + "github.com/xh3b4sd/tracer" + + "github.com/xh3b4sd/workflow/cmd" +) + +func main() { + err := mainE(context.Background()) + if err != nil { + tracer.Panic(err) + } +} + +func mainE(ctx context.Context) error { + var err error + + var l logger.Interface + { + c := logger.Config{} + + l, err = logger.New(c) + if err != nil { + return tracer.Mask(err) + } + } + + var r *cobra.Command + { + c := cmd.Config{ + Logger: l, + } + + r, err = cmd.New(c) + if err != nil { + return tracer.Mask(err) + } + } + + err = r.Execute() + if err != nil { + return tracer.Mask(err) + } + + return nil +} diff --git a/pkg/env/env.go b/pkg/env/env.go new file mode 100644 index 0000000..6c357e1 --- /dev/null +++ b/pkg/env/env.go @@ -0,0 +1,44 @@ +package env + +import ( + "os" + "path/filepath" + "strings" +) + +func Env() map[string]string { + var err error + + var pat string + { + pat = ".workflow/" + } + + var fil []os.DirEntry + { + fil, err = os.ReadDir(pat) + if os.IsNotExist(err) { + // fall through + } else if err != nil { + panic(err) + } + } + + env := map[string]string{} + { + for _, f := range fil { + if f.IsDir() { + continue + } + + byt, err := os.ReadFile(filepath.Join(pat, f.Name())) + if err != nil { + panic(err) + } + + env[f.Name()] = strings.TrimSpace(string(byt)) + } + } + + return env +} diff --git a/pkg/file/exists.go b/pkg/file/exists.go new file mode 100644 index 0000000..3cc9673 --- /dev/null +++ b/pkg/file/exists.go @@ -0,0 +1,14 @@ +package file + +import "os" + +func Exists(file string) bool { + _, err := os.Stat(file) + if os.IsNotExist(err) { + return false + } else if err != nil { + panic(err) + } + + return true +} diff --git a/pkg/generator/interface.go b/pkg/generator/interface.go new file mode 100644 index 0000000..fa7a2f5 --- /dev/null +++ b/pkg/generator/interface.go @@ -0,0 +1,5 @@ +package generator + +type Interface interface { + Workflow() ([]byte, error) +} diff --git a/pkg/generator/pbfgo/error.go b/pkg/generator/pbfgo/error.go new file mode 100644 index 0000000..f0f4173 --- /dev/null +++ b/pkg/generator/pbfgo/error.go @@ -0,0 +1,15 @@ +package pbfgo + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/pkg/generator/pbfgo/pbfgo.go b/pkg/generator/pbfgo/pbfgo.go new file mode 100644 index 0000000..86dd4ce --- /dev/null +++ b/pkg/generator/pbfgo/pbfgo.go @@ -0,0 +1,122 @@ +package pbfgo + +import ( + "bytes" + "strings" + "text/template" + + "github.com/xh3b4sd/tracer" + "github.com/xh3b4sd/workflow/pkg/version" +) + +type Config struct { + Command string + FilePath string + GithubOrganization string + GithubRepository string + VersionGolang string + VersionProtoc string +} + +type PbfGo struct { + command string + filePath string + githubOrganization string + githubRepository string + versionGolang string + versionProtoc string +} + +func New(config Config) (*PbfGo, error) { + if config.Command == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.Command must not be empty", config) + } + if config.FilePath == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.FilePath must not be empty", config) + } + if config.GithubOrganization == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.GithubOrganization must not be empty", config) + } + if config.GithubRepository == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.GithubRepository must not be empty", config) + } + if config.VersionGolang == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.VersionGolang must not be empty", config) + } + if config.VersionProtoc == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.VersionProtoc must not be empty", config) + } + + p := &PbfGo{ + command: config.Command, + filePath: config.FilePath, + githubOrganization: config.GithubOrganization, + githubRepository: config.GithubRepository, + versionGolang: config.VersionGolang, + versionProtoc: config.VersionProtoc, + } + + return p, nil +} + +func (p *PbfGo) Workflow() ([]byte, error) { + b, err := p.render(templateWorkflow) + if err != nil { + return nil, tracer.Mask(err) + } + + return b, nil +} + +func (p *PbfGo) data() interface{} { + type Github struct { + Organization string + Repository string + } + + type Data struct { + Command string + Github Github + Version version.Version + } + + return Data{ + Command: p.command, + Github: Github{ + Organization: p.githubOrganization, + Repository: p.githubRepository, + }, + Version: version.Version{ + Checkout: version.Checkout, + Golang: p.versionGolang, + Protoc: p.versionProtoc, + SetupGo: version.SetupGo, + }, + } +} + +func (p *PbfGo) render(t string) ([]byte, error) { + f := template.FuncMap{ + "ToUpper": func(s string) string { + n := s + + n = strings.ToUpper(n) + n = strings.ReplaceAll(n, "-", "") + + return n + }, + } + + s, err := template.New(p.filePath).Funcs(f).Parse(t) + if err != nil { + return nil, tracer.Mask(err) + } + + var b bytes.Buffer + err = s.ExecuteTemplate(&b, p.filePath, p.data()) + if err != nil { + return nil, tracer.Mask(err) + } + + return b.Bytes(), nil +} diff --git a/pkg/generator/pbfgo/pbfgo_test.go b/pkg/generator/pbfgo/pbfgo_test.go new file mode 100644 index 0000000..cf8e263 --- /dev/null +++ b/pkg/generator/pbfgo/pbfgo_test.go @@ -0,0 +1,104 @@ +package pbfgo + +import ( + "bytes" + "flag" + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/xh3b4sd/workflow/pkg/generator" +) + +var update = flag.Bool("update", false, "update .golden files") + +// Test_PbfGo_Workflow tests workflow generation for the protocol buffer Golang +// code generation workflow. The workflow template is quite complex and not +// easily readable. Considering input parameter like Github organization and +// Golang version we need a way to reliable verify the integrity of the YAML +// file rendering. +// +// go test ./pkg/generator/pbfgo -update +func Test_PbfGo_Workflow(t *testing.T) { + testCases := []struct { + command string + organization string + repository string + golang string + protoc string + }{ + // Case 0 ensures that a workflow file can be generated according to its + // configuration. + { + command: "workflow create pbfgo", + organization: "xh3b4sd", + repository: "gocode", + golang: "1.15.2", + protoc: "3.13.0", + }, + // Case 1 ensures that a workflow file can be generated according to its + // configuration. + { + command: "workflow create pbfgo --some argument", + organization: "some-org", + repository: "some-repo", + golang: "1.14.0", + protoc: "3.5.1", + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + var err error + + var g generator.Interface + { + c := Config{ + Command: tc.command, + FilePath: "workflow.yaml", + GithubOrganization: tc.organization, + GithubRepository: tc.repository, + VersionGolang: tc.golang, + VersionProtoc: tc.protoc, + } + + g, err = New(c) + if err != nil { + t.Fatal(err) + } + } + + var actual []byte + { + actual, err = g.Workflow() + if err != nil { + t.Fatal(err) + } + } + + p := filepath.Join("testdata/workflow", fileName(i)) + if *update { + err := os.WriteFile(p, []byte(actual), 0600) + if err != nil { + t.Fatal(err) + } + } + + expected, err := os.ReadFile(p) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(expected, []byte(actual)) { + t.Fatalf("\n\n%s\n", cmp.Diff(string(actual), string(expected))) + } + }) + } +} + +func fileName(i int) string { + return "case-" + strconv.Itoa(i) + ".golden" +} diff --git a/pkg/generator/pbfgo/template.go b/pkg/generator/pbfgo/template.go new file mode 100644 index 0000000..c3d9395 --- /dev/null +++ b/pkg/generator/pbfgo/template.go @@ -0,0 +1,89 @@ +package pbfgo + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "pbf-go" + +on: + push: + branches: + - "main" + - "master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-go: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Protoc Binary" + working-directory: "${{ "{{" }} runner.temp {{ "}}" }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v{{ .Version.Protoc }}/protoc-{{ .Version.Protoc }}-linux-x86_64.zip + unzip protoc-{{ .Version.Protoc }}-linux-x86_64.zip + echo "${{ "{{" }} runner.temp {{ "}}" }}/bin" >> $GITHUB_PATH + + - name: "Install Go Dependencies" + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3 + + - name: "Clone Go Code" + run: | + git clone https://github.com/{{ .Github.Organization }}/{{ .Github.Repository }}.git "${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/" + + - name: "Generate Go Code" + run: | + inp="./pbf" + out=${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/pkg + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + protoc --go_out=$out/$x --twirp_out=$out/$x ${lin[@]} + fi + done + + - name: "Go Mod Tidy" + working-directory: "${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/" + run: | + if [[ -e go.sum ]]; then + rm -f go.sum + go mod tidy + fi + + - name: "Commit And Push" + uses: "cpina/github-action-push-to-another-repository@v1.7.2" + env: + SSH_DEPLOY_KEY: "${{ "{{" }} secrets.SSH_DEPLOY_KEY_{{ .Github.Repository | ToUpper }} {{ "}}" }}" + with: + source-directory: "${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/" + destination-github-username: "{{ .Github.Organization }}" + destination-repository-name: "{{ .Github.Repository }}" + commit-message: "update generated code" + target-branch: "main" +` diff --git a/pkg/generator/pbfgo/testdata/workflow/case-0.golden b/pkg/generator/pbfgo/testdata/workflow/case-0.golden new file mode 100644 index 0000000..1907951 --- /dev/null +++ b/pkg/generator/pbfgo/testdata/workflow/case-0.golden @@ -0,0 +1,86 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create pbfgo +# + +name: "pbf-go" + +on: + push: + branches: + - "main" + - "master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-go: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Go Env" + uses: "actions/setup-go@v4" + with: + cache: true + go-version: "1.15.2" + + - name: "Install Protoc Binary" + working-directory: "${{ runner.temp }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip + unzip protoc-3.13.0-linux-x86_64.zip + echo "${{ runner.temp }}/bin" >> $GITHUB_PATH + + - name: "Install Go Dependencies" + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3 + + - name: "Clone Go Code" + run: | + git clone https://github.com/xh3b4sd/gocode.git "${{ github.sha }}/xh3b4sd/gocode/" + + - name: "Generate Go Code" + run: | + inp="./pbf" + out=${{ github.sha }}/xh3b4sd/gocode/pkg + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + protoc --go_out=$out/$x --twirp_out=$out/$x ${lin[@]} + fi + done + + - name: "Go Mod Tidy" + working-directory: "${{ github.sha }}/xh3b4sd/gocode/" + run: | + if [[ -e go.sum ]]; then + rm -f go.sum + go mod tidy + fi + + - name: "Commit And Push" + uses: "cpina/github-action-push-to-another-repository@v1.7.2" + env: + SSH_DEPLOY_KEY: "${{ secrets.SSH_DEPLOY_KEY_GOCODE }}" + with: + source-directory: "${{ github.sha }}/xh3b4sd/gocode/" + destination-github-username: "xh3b4sd" + destination-repository-name: "gocode" + commit-message: "update generated code" + target-branch: "main" diff --git a/pkg/generator/pbfgo/testdata/workflow/case-1.golden b/pkg/generator/pbfgo/testdata/workflow/case-1.golden new file mode 100644 index 0000000..286d029 --- /dev/null +++ b/pkg/generator/pbfgo/testdata/workflow/case-1.golden @@ -0,0 +1,86 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create pbfgo --some argument +# + +name: "pbf-go" + +on: + push: + branches: + - "main" + - "master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-go: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Go Env" + uses: "actions/setup-go@v4" + with: + cache: true + go-version: "1.14.0" + + - name: "Install Protoc Binary" + working-directory: "${{ runner.temp }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip + unzip protoc-3.5.1-linux-x86_64.zip + echo "${{ runner.temp }}/bin" >> $GITHUB_PATH + + - name: "Install Go Dependencies" + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3 + + - name: "Clone Go Code" + run: | + git clone https://github.com/some-org/some-repo.git "${{ github.sha }}/some-org/some-repo/" + + - name: "Generate Go Code" + run: | + inp="./pbf" + out=${{ github.sha }}/some-org/some-repo/pkg + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + protoc --go_out=$out/$x --twirp_out=$out/$x ${lin[@]} + fi + done + + - name: "Go Mod Tidy" + working-directory: "${{ github.sha }}/some-org/some-repo/" + run: | + if [[ -e go.sum ]]; then + rm -f go.sum + go mod tidy + fi + + - name: "Commit And Push" + uses: "cpina/github-action-push-to-another-repository@v1.7.2" + env: + SSH_DEPLOY_KEY: "${{ secrets.SSH_DEPLOY_KEY_SOMEREPO }}" + with: + source-directory: "${{ github.sha }}/some-org/some-repo/" + destination-github-username: "some-org" + destination-repository-name: "some-repo" + commit-message: "update generated code" + target-branch: "main" diff --git a/pkg/generator/pbflint/error.go b/pkg/generator/pbflint/error.go new file mode 100644 index 0000000..077d99f --- /dev/null +++ b/pkg/generator/pbflint/error.go @@ -0,0 +1,15 @@ +package pbflint + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/pkg/generator/pbflint/pbflint.go b/pkg/generator/pbflint/pbflint.go new file mode 100644 index 0000000..dab4bf0 --- /dev/null +++ b/pkg/generator/pbflint/pbflint.go @@ -0,0 +1,100 @@ +package pbflint + +import ( + "bytes" + "strings" + "text/template" + + "github.com/xh3b4sd/tracer" + "github.com/xh3b4sd/workflow/pkg/version" +) + +type Config struct { + Command string + FilePath string + VersionGolang string + VersionProtoc string +} + +type PbfGo struct { + command string + filePath string + versionGolang string + versionProtoc string +} + +func New(config Config) (*PbfGo, error) { + if config.Command == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.Command must not be empty", config) + } + if config.FilePath == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.FilePath must not be empty", config) + } + if config.VersionGolang == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.VersionGolang must not be empty", config) + } + if config.VersionProtoc == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.VersionProtoc must not be empty", config) + } + + p := &PbfGo{ + command: config.Command, + filePath: config.FilePath, + versionGolang: config.VersionGolang, + versionProtoc: config.VersionProtoc, + } + + return p, nil +} + +func (p *PbfGo) Workflow() ([]byte, error) { + b, err := p.render(templateWorkflow) + if err != nil { + return nil, tracer.Mask(err) + } + + return b, nil +} + +func (p *PbfGo) data() interface{} { + type Data struct { + Command string + Version version.Version + } + + return Data{ + Command: p.command, + Version: version.Version{ + Checkout: version.Checkout, + Golang: p.versionGolang, + Protoc: p.versionProtoc, + SetupGo: version.SetupGo, + }, + } +} + +func (p *PbfGo) render(t string) ([]byte, error) { + f := template.FuncMap{ + "ToUpper": func(s string) string { + n := s + + n = strings.ToUpper(n) + n = strings.ReplaceAll(n, "-", "") + + return n + }, + } + + s, err := template.New(p.filePath).Funcs(f).Parse(t) + if err != nil { + return nil, tracer.Mask(err) + } + + var b bytes.Buffer + err = s.ExecuteTemplate(&b, p.filePath, p.data()) + if err != nil { + return nil, tracer.Mask(err) + } + + return b.Bytes(), nil +} diff --git a/pkg/generator/pbflint/pbflint_test.go b/pkg/generator/pbflint/pbflint_test.go new file mode 100644 index 0000000..25b7a6b --- /dev/null +++ b/pkg/generator/pbflint/pbflint_test.go @@ -0,0 +1,93 @@ +package pbflint + +import ( + "bytes" + "flag" + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/xh3b4sd/workflow/pkg/generator" +) + +var update = flag.Bool("update", false, "update .golden files") + +// Test_PbfLint_Workflow tests workflow generation for the protocol buffer +// validation workflow. +// +// go test ./pkg/generator/pbflint -update +func Test_PbfLint_Workflow(t *testing.T) { + testCases := []struct { + command string + golang string + protoc string + }{ + // Case 0 ensures that a workflow file can be generated according to its + // configuration. + { + command: "workflow create pbflint", + golang: "1.15.2", + protoc: "3.13.0", + }, + // Case 1 ensures that a workflow file can be generated according to its + // configuration. + { + command: "workflow create pbflint --some argument", + golang: "1.14.0", + protoc: "3.5.1", + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + var err error + + var g generator.Interface + { + c := Config{ + Command: tc.command, + FilePath: "workflow.yaml", + VersionGolang: tc.golang, + VersionProtoc: tc.protoc, + } + + g, err = New(c) + if err != nil { + t.Fatal(err) + } + } + + var actual []byte + { + actual, err = g.Workflow() + if err != nil { + t.Fatal(err) + } + } + + p := filepath.Join("testdata/workflow", fileName(i)) + if *update { + err := os.WriteFile(p, []byte(actual), 0600) + if err != nil { + t.Fatal(err) + } + } + + expected, err := os.ReadFile(p) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(expected, []byte(actual)) { + t.Fatalf("\n\n%s\n", cmp.Diff(string(actual), string(expected))) + } + }) + } +} + +func fileName(i int) string { + return "case-" + strconv.Itoa(i) + ".golden" +} diff --git a/pkg/generator/pbflint/template.go b/pkg/generator/pbflint/template.go new file mode 100644 index 0000000..71a79b1 --- /dev/null +++ b/pkg/generator/pbflint/template.go @@ -0,0 +1,67 @@ +package pbflint + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "pbf-lint" + +on: + push: + branches: + - "**" + - "!main" + - "!master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-lint: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Go Env" + uses: "actions/setup-go@v{{ .Version.SetupGo }}" + with: + cache: true + go-version: "{{ .Version.Golang }}" + + - name: "Install Protoc Binary" + working-directory: "${{ "{{" }} runner.temp {{ "}}" }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v{{ .Version.Protoc }}/protoc-{{ .Version.Protoc }}-linux-x86_64.zip + unzip protoc-{{ .Version.Protoc }}-linux-x86_64.zip + echo "${{ "{{" }} runner.temp {{ "}}" }}/bin" >> $GITHUB_PATH + + - name: "Install Go Dependencies" + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3 + + - name: "Validate Protocol Buffers" + run: | + inp="./pbf" + out=${{ "{{" }} github.sha {{ "}}" }}/pkg + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + protoc --go_out=$out/$x --twirp_out=$out/$x ${lin[@]} + fi + done +` diff --git a/pkg/generator/pbflint/testdata/workflow/case-0.golden b/pkg/generator/pbflint/testdata/workflow/case-0.golden new file mode 100644 index 0000000..adb146a --- /dev/null +++ b/pkg/generator/pbflint/testdata/workflow/case-0.golden @@ -0,0 +1,64 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create pbflint +# + +name: "pbf-lint" + +on: + push: + branches: + - "**" + - "!main" + - "!master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-lint: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Go Env" + uses: "actions/setup-go@v4" + with: + cache: true + go-version: "1.15.2" + + - name: "Install Protoc Binary" + working-directory: "${{ runner.temp }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip + unzip protoc-3.13.0-linux-x86_64.zip + echo "${{ runner.temp }}/bin" >> $GITHUB_PATH + + - name: "Install Go Dependencies" + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3 + + - name: "Validate Protocol Buffers" + run: | + inp="./pbf" + out=${{ github.sha }}/pkg + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + protoc --go_out=$out/$x --twirp_out=$out/$x ${lin[@]} + fi + done diff --git a/pkg/generator/pbflint/testdata/workflow/case-1.golden b/pkg/generator/pbflint/testdata/workflow/case-1.golden new file mode 100644 index 0000000..52a08a9 --- /dev/null +++ b/pkg/generator/pbflint/testdata/workflow/case-1.golden @@ -0,0 +1,64 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create pbflint --some argument +# + +name: "pbf-lint" + +on: + push: + branches: + - "**" + - "!main" + - "!master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-lint: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Go Env" + uses: "actions/setup-go@v4" + with: + cache: true + go-version: "1.14.0" + + - name: "Install Protoc Binary" + working-directory: "${{ runner.temp }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip + unzip protoc-3.5.1-linux-x86_64.zip + echo "${{ runner.temp }}/bin" >> $GITHUB_PATH + + - name: "Install Go Dependencies" + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3 + + - name: "Validate Protocol Buffers" + run: | + inp="./pbf" + out=${{ github.sha }}/pkg + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + protoc --go_out=$out/$x --twirp_out=$out/$x ${lin[@]} + fi + done diff --git a/pkg/generator/pbfts/error.go b/pkg/generator/pbfts/error.go new file mode 100644 index 0000000..11ca398 --- /dev/null +++ b/pkg/generator/pbfts/error.go @@ -0,0 +1,15 @@ +package pbfts + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/pkg/generator/pbfts/pbfts.go b/pkg/generator/pbfts/pbfts.go new file mode 100644 index 0000000..5ac9e6a --- /dev/null +++ b/pkg/generator/pbfts/pbfts.go @@ -0,0 +1,122 @@ +package pbfts + +import ( + "bytes" + "strings" + "text/template" + + "github.com/xh3b4sd/tracer" + "github.com/xh3b4sd/workflow/pkg/version" +) + +type Config struct { + Command string + FilePath string + GithubOrganization string + GithubRepository string + VersionNode string + VersionProtoc string +} + +type PbfTs struct { + command string + filePath string + githubOrganization string + githubRepository string + versionNode string + versionProtoc string +} + +func New(config Config) (*PbfTs, error) { + if config.Command == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.Command must not be empty", config) + } + if config.FilePath == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.FilePath must not be empty", config) + } + if config.GithubOrganization == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.GithubOrganization must not be empty", config) + } + if config.GithubRepository == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.GithubRepository must not be empty", config) + } + if config.VersionNode == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.VersionNode must not be empty", config) + } + if config.VersionProtoc == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.VersionProtoc must not be empty", config) + } + + p := &PbfTs{ + command: config.Command, + filePath: config.FilePath, + githubOrganization: config.GithubOrganization, + githubRepository: config.GithubRepository, + versionNode: config.VersionNode, + versionProtoc: config.VersionProtoc, + } + + return p, nil +} + +func (p *PbfTs) Workflow() ([]byte, error) { + b, err := p.render(templateWorkflow) + if err != nil { + return nil, tracer.Mask(err) + } + + return b, nil +} + +func (p *PbfTs) data() interface{} { + type Github struct { + Organization string + Repository string + } + + type Data struct { + Command string + Github Github + Version version.Version + } + + return Data{ + Command: p.command, + Github: Github{ + Organization: p.githubOrganization, + Repository: p.githubRepository, + }, + Version: version.Version{ + Checkout: version.Checkout, + Node: p.versionNode, + Protoc: p.versionProtoc, + SetupNode: version.SetupNode, + }, + } +} + +func (p *PbfTs) render(t string) ([]byte, error) { + f := template.FuncMap{ + "ToUpper": func(s string) string { + n := s + + n = strings.ToUpper(n) + n = strings.ReplaceAll(n, "-", "") + + return n + }, + } + + s, err := template.New(p.filePath).Funcs(f).Parse(t) + if err != nil { + return nil, tracer.Mask(err) + } + + var b bytes.Buffer + err = s.ExecuteTemplate(&b, p.filePath, p.data()) + if err != nil { + return nil, tracer.Mask(err) + } + + return b.Bytes(), nil +} diff --git a/pkg/generator/pbfts/pbfts_test.go b/pkg/generator/pbfts/pbfts_test.go new file mode 100644 index 0000000..ab82d55 --- /dev/null +++ b/pkg/generator/pbfts/pbfts_test.go @@ -0,0 +1,104 @@ +package pbfts + +import ( + "bytes" + "flag" + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/xh3b4sd/workflow/pkg/generator" +) + +var update = flag.Bool("update", false, "update .golden files") + +// Test_PbfTs_Workflow tests workflow generation for the protocol buffer +// Typescript code generation workflow. The workflow template is quite complex +// and not easily readable. Considering input parameter like Github organization +// and Node version we need a way to reliable verify the integrity of the YAML +// file rendering. +// +// go test ./pkg/generator/pbfts -update +func Test_GrpcTs_Workflow(t *testing.T) { + testCases := []struct { + command string + organization string + repository string + node string + protoc string + }{ + // Case 0 ensures that a workflow file can be generated according to its + // configuration. + { + command: "workflow create pbfts", + organization: "xh3b4sd", + repository: "tscode", + node: "15.x.x", + protoc: "3.13.0", + }, + // Case 1 ensures that a workflow file can be generated according to its + // configuration. + { + command: "workflow create pbfts --some argument", + organization: "some-org", + repository: "some-repo", + node: "15.x.x", + protoc: "3.5.1", + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + var err error + + var g generator.Interface + { + c := Config{ + Command: tc.command, + FilePath: "workflow.yaml", + GithubOrganization: tc.organization, + GithubRepository: tc.repository, + VersionNode: tc.node, + VersionProtoc: tc.protoc, + } + + g, err = New(c) + if err != nil { + t.Fatal(err) + } + } + + var actual []byte + { + actual, err = g.Workflow() + if err != nil { + t.Fatal(err) + } + } + + p := filepath.Join("testdata/workflow", fileName(i)) + if *update { + err := os.WriteFile(p, []byte(actual), 0600) + if err != nil { + t.Fatal(err) + } + } + + expected, err := os.ReadFile(p) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(expected, []byte(actual)) { + t.Fatalf("\n\n%s\n", cmp.Diff(string(actual), string(expected))) + } + }) + } +} + +func fileName(i int) string { + return "case-" + strconv.Itoa(i) + ".golden" +} diff --git a/pkg/generator/pbfts/template.go b/pkg/generator/pbfts/template.go new file mode 100644 index 0000000..70fc57c --- /dev/null +++ b/pkg/generator/pbfts/template.go @@ -0,0 +1,90 @@ +package pbfts + +const templateWorkflow = `# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# {{ .Command }} +# + +name: "pbf-ts" + +on: + push: + branches: + - "main" + - "master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-ts: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v{{ .Version.Checkout }}" + + - name: "Setup Typescript Env" + uses: "actions/setup-node@v{{ .Version.SetupNode }}" + with: + node-version: "{{ .Version.Node }}" + + - name: "Install Protoc Binary" + working-directory: "${{ "{{" }} runner.temp {{ "}}" }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v{{ .Version.Protoc }}/protoc-{{ .Version.Protoc }}-linux-x86_64.zip + unzip protoc-{{ .Version.Protoc }}-linux-x86_64.zip + echo "${{ "{{" }} runner.temp {{ "}}" }}/bin" >> $GITHUB_PATH + + - name: "Install Typescript Dependencies" + run: | + npm install prettier --global + npm install @protobuf-ts/plugin --global + + - name: "Clone Typescript Code" + run: | + git clone https://github.com/{{ .Github.Organization }}/{{ .Github.Repository }}.git "${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/" + + - name: "Generate Typescript Code" + run: | + inp="./pbf" + out=${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/src + tmp="./tmp" + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + mkdir -p $tmp/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + npx protoc --proto_path=. --ts_out=$tmp/$x --ts_opt=output_javascript ${lin[@]} + mv $tmp/$x/$inp/$x/* $out/$x + rm -rf $tmp/$x + fi + done + + rm -rf $tmp + + - name: "Format Typescript Code" + run: | + prettier -w $(find ${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/src/ -name "*.ts" -o -name "*.tsx") + + - name: "Commit And Push" + uses: "cpina/github-action-push-to-another-repository@v1.7.2" + env: + SSH_DEPLOY_KEY: "${{ "{{" }} secrets.SSH_DEPLOY_KEY_{{ .Github.Repository | ToUpper }} {{ "}}" }}" + with: + source-directory: "${{ "{{" }} github.sha {{ "}}" }}/{{ .Github.Organization }}/{{ .Github.Repository }}/" + destination-github-username: "{{ .Github.Organization }}" + destination-repository-name: "{{ .Github.Repository }}" + commit-message: "update generated code" + target-branch: "main" +` diff --git a/pkg/generator/pbfts/testdata/workflow/case-0.golden b/pkg/generator/pbfts/testdata/workflow/case-0.golden new file mode 100644 index 0000000..5d41736 --- /dev/null +++ b/pkg/generator/pbfts/testdata/workflow/case-0.golden @@ -0,0 +1,87 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create pbfts +# + +name: "pbf-ts" + +on: + push: + branches: + - "main" + - "master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-ts: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Typescript Env" + uses: "actions/setup-node@v3" + with: + node-version: "15.x.x" + + - name: "Install Protoc Binary" + working-directory: "${{ runner.temp }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip + unzip protoc-3.13.0-linux-x86_64.zip + echo "${{ runner.temp }}/bin" >> $GITHUB_PATH + + - name: "Install Typescript Dependencies" + run: | + npm install prettier --global + npm install @protobuf-ts/plugin --global + + - name: "Clone Typescript Code" + run: | + git clone https://github.com/xh3b4sd/tscode.git "${{ github.sha }}/xh3b4sd/tscode/" + + - name: "Generate Typescript Code" + run: | + inp="./pbf" + out=${{ github.sha }}/xh3b4sd/tscode/src + tmp="./tmp" + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + mkdir -p $tmp/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + npx protoc --proto_path=. --ts_out=$tmp/$x --ts_opt=output_javascript ${lin[@]} + mv $tmp/$x/$inp/$x/* $out/$x + rm -rf $tmp/$x + fi + done + + rm -rf $tmp + + - name: "Format Typescript Code" + run: | + prettier -w $(find ${{ github.sha }}/xh3b4sd/tscode/src/ -name "*.ts" -o -name "*.tsx") + + - name: "Commit And Push" + uses: "cpina/github-action-push-to-another-repository@v1.7.2" + env: + SSH_DEPLOY_KEY: "${{ secrets.SSH_DEPLOY_KEY_TSCODE }}" + with: + source-directory: "${{ github.sha }}/xh3b4sd/tscode/" + destination-github-username: "xh3b4sd" + destination-repository-name: "tscode" + commit-message: "update generated code" + target-branch: "main" diff --git a/pkg/generator/pbfts/testdata/workflow/case-1.golden b/pkg/generator/pbfts/testdata/workflow/case-1.golden new file mode 100644 index 0000000..d3c45b9 --- /dev/null +++ b/pkg/generator/pbfts/testdata/workflow/case-1.golden @@ -0,0 +1,87 @@ +# +# Do not edit. This file was generated via the "workflow" command line tool. +# More information about the tool can be found at github.com/xh3b4sd/workflow. +# +# workflow create pbfts --some argument +# + +name: "pbf-ts" + +on: + push: + branches: + - "main" + - "master" + paths: + - "**.proto" + workflow_dispatch: + +jobs: + pbf-ts: + runs-on: "ubuntu-latest" + steps: + + - name: "Setup Git Project" + uses: "actions/checkout@v4" + + - name: "Setup Typescript Env" + uses: "actions/setup-node@v3" + with: + node-version: "15.x.x" + + - name: "Install Protoc Binary" + working-directory: "${{ runner.temp }}" + run: | + curl -LOs https://github.com/protocolbuffers/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip + unzip protoc-3.5.1-linux-x86_64.zip + echo "${{ runner.temp }}/bin" >> $GITHUB_PATH + + - name: "Install Typescript Dependencies" + run: | + npm install prettier --global + npm install @protobuf-ts/plugin --global + + - name: "Clone Typescript Code" + run: | + git clone https://github.com/some-org/some-repo.git "${{ github.sha }}/some-org/some-repo/" + + - name: "Generate Typescript Code" + run: | + inp="./pbf" + out=${{ github.sha }}/some-org/some-repo/src + tmp="./tmp" + + rm -rf $out + + for x in $(ls $inp); do + if [ -n "$(ls $inp/$x)" ]; then + mkdir -p $out/$x + mkdir -p $tmp/$x + + lin=() + for y in $(ls -F $inp/$x); do + lin+=($inp/$x/$y) + done + + npx protoc --proto_path=. --ts_out=$tmp/$x --ts_opt=output_javascript ${lin[@]} + mv $tmp/$x/$inp/$x/* $out/$x + rm -rf $tmp/$x + fi + done + + rm -rf $tmp + + - name: "Format Typescript Code" + run: | + prettier -w $(find ${{ github.sha }}/some-org/some-repo/src/ -name "*.ts" -o -name "*.tsx") + + - name: "Commit And Push" + uses: "cpina/github-action-push-to-another-repository@v1.7.2" + env: + SSH_DEPLOY_KEY: "${{ secrets.SSH_DEPLOY_KEY_SOMEREPO }}" + with: + source-directory: "${{ github.sha }}/some-org/some-repo/" + destination-github-username: "some-org" + destination-repository-name: "some-repo" + commit-message: "update generated code" + target-branch: "main" diff --git a/pkg/parser/command/command.go b/pkg/parser/command/command.go new file mode 100644 index 0000000..7470bfd --- /dev/null +++ b/pkg/parser/command/command.go @@ -0,0 +1,117 @@ +package command + +import ( + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/spf13/afero" + "github.com/xh3b4sd/tracer" +) + +var ( + // header is the regular expression used to parse the common workflow header + // that tracks the command used for the workflow creation. + header = regexp.MustCompile(`(?m)^[\s]*#[\s]+(workflow create .*)$`) +) + +type Config struct { + FileSystem afero.Fs + + WorkflowPath string +} + +type Command struct { + fileSystem afero.Fs + + workflowPath string +} + +func New(config Config) (*Command, error) { + if config.FileSystem == nil { + return nil, tracer.Maskf(invalidConfigError, "%T.FileSystem must not be empty", config) + } + + if config.WorkflowPath == "" { + return nil, tracer.Maskf(invalidConfigError, "%T.WorkflowPath must not be empty", config) + } + + c := &Command{ + fileSystem: config.FileSystem, + + workflowPath: config.WorkflowPath, + } + + return c, nil +} + +func (c *Command) Parse() ([][]string, error) { + files, err := c.files(".yaml") + if err != nil { + return nil, tracer.Mask(err) + } + + commands, err := c.commands(files...) + if err != nil { + return nil, tracer.Mask(err) + } + + var args [][]string + for _, c := range commands { + args = append(args, strings.Split(c, " ")) + } + + return args, nil +} + +func (c *Command) commands(files ...string) ([]string, error) { + var commands []string + + for _, f := range files { + b, err := afero.ReadFile(c.fileSystem, f) + if err != nil { + return nil, tracer.Mask(err) + } + + matches := header.FindSubmatch(b) + + if len(matches) == 0 { + continue + } + + commands = append(commands, string(matches[1])) + } + + return commands, nil +} + +func (c *Command) files(exts ...string) ([]string, error) { + var files []string + { + walkFunc := func(p string, i os.FileInfo, err error) error { + if err != nil { + return tracer.Mask(err) + } + + // We do not want to track files with the wrong extension. We are + // interested in workflow files having the ".yaml" extension. + for _, e := range exts { + if filepath.Ext(i.Name()) != e { + return nil + } + } + + files = append(files, p) + + return nil + } + + err := afero.Walk(c.fileSystem, c.workflowPath, walkFunc) + if err != nil { + return nil, tracer.Mask(err) + } + } + + return files, nil +} diff --git a/pkg/parser/command/command_test.go b/pkg/parser/command/command_test.go new file mode 100644 index 0000000..b8d7b06 --- /dev/null +++ b/pkg/parser/command/command_test.go @@ -0,0 +1,155 @@ +package command + +import ( + "bytes" + "flag" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/spf13/afero" + "github.com/xh3b4sd/workflow/pkg/parser" +) + +var update = flag.Bool("update", false, "update .golden files") + +// Test_Command_Parse tests the parsing of the command instructions used to +// generate workflows. Different workflows and their associated commands are +// quite complex while the parsing code is not easily comprehensible. Therefore +// we need a way to reliably verify the integrity of the command parsing. +// +// go test ./... -run Test_Command_Parse -update +func Test_Command_Parse(t *testing.T) { + testCases := []struct { + fileSystem afero.Fs + }{ + // Case 0 ensures that a command instruction can be parsed according to + // its workflow header. + { + fileSystem: func() afero.Fs { + fs := afero.NewMemMapFs() + + mustCreateDir(fs, ".github/workflows/") + + mustCreateFile(fs, ".github/workflows/some.yaml", ` + # + # workflow create foo bar + # + `) + + return fs + }(), + }, + // Case 1 ensures that a command instruction can be parsed according to + // its workflow header. + { + fileSystem: func() afero.Fs { + fs := afero.NewMemMapFs() + + mustCreateDir(fs, ".github/workflows/") + + mustCreateFile(fs, ".github/workflows/some.yaml", ` + # + # workflow create bar foo + # + `) + + mustCreateFile(fs, ".github/workflows/dependabot.yaml", ` + # + # workflow create dependabot -r xh3b4sd + # + `) + + mustCreateFile(fs, ".github/workflows/grpc-ts.yaml", ` + # + # workflow create grpcts -n 15.x.x + # + `) + + mustCreateFile(fs, ".github/dependabot.yaml", ` + # + # workflow create dependabot should not be taken into account due to path + # + `) + + return fs + }(), + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + var err error + + var p parser.Interface + { + c := Config{ + FileSystem: tc.fileSystem, + + WorkflowPath: ".github/workflows/", + } + + p, err = New(c) + if err != nil { + t.Fatal(err) + } + } + + var actual string + { + l, err := p.Parse() + if err != nil { + t.Fatal(err) + } + + var s []string + for _, a := range l { + s = append(s, strings.Join(a, " ")) + } + + actual = strings.Join(s, "\n") + "\n" + } + + var expected []byte + { + p := filepath.Join("testdata/parse", fileName(i)) + if *update { + err := os.WriteFile(p, []byte(actual), 0600) + if err != nil { + t.Fatal(err) + } + } + + expected, err = os.ReadFile(p) + if err != nil { + t.Fatal(err) + } + } + + if !bytes.Equal(expected, []byte(actual)) { + t.Fatalf("\n\n%s\n", cmp.Diff(string(actual), string(expected))) + } + }) + } +} + +func fileName(i int) string { + return "case-" + strconv.Itoa(i) + ".golden" +} + +func mustCreateDir(fs afero.Fs, p string) { + err := fs.MkdirAll(p, 0755) + if err != nil { + panic(err) + } +} + +func mustCreateFile(fs afero.Fs, p string, b string) { + err := afero.WriteFile(fs, p, []byte(b), 0600) + if err != nil { + panic(err) + } +} diff --git a/pkg/parser/command/error.go b/pkg/parser/command/error.go new file mode 100644 index 0000000..f9f4dd4 --- /dev/null +++ b/pkg/parser/command/error.go @@ -0,0 +1,15 @@ +package command + +import ( + "errors" + + "github.com/xh3b4sd/tracer" +) + +var invalidConfigError = &tracer.Error{ + Kind: "invalidConfigError", +} + +func IsInvalidConfig(err error) bool { + return errors.Is(err, invalidConfigError) +} diff --git a/pkg/parser/command/testdata/parse/case-0.golden b/pkg/parser/command/testdata/parse/case-0.golden new file mode 100644 index 0000000..699985a --- /dev/null +++ b/pkg/parser/command/testdata/parse/case-0.golden @@ -0,0 +1 @@ +workflow create foo bar diff --git a/pkg/parser/command/testdata/parse/case-1.golden b/pkg/parser/command/testdata/parse/case-1.golden new file mode 100644 index 0000000..10eb6be --- /dev/null +++ b/pkg/parser/command/testdata/parse/case-1.golden @@ -0,0 +1,3 @@ +workflow create dependabot -r xh3b4sd +workflow create grpcts -n 15.x.x +workflow create bar foo diff --git a/pkg/parser/interface.go b/pkg/parser/interface.go new file mode 100644 index 0000000..420f582 --- /dev/null +++ b/pkg/parser/interface.go @@ -0,0 +1,10 @@ +package parser + +type Interface interface { + // Parse tries to find all commands that got used to create github + // workflows. The command used during workflow creation is written to the + // header of the workflow file. Parse tries to find all workflow files and + // returns the list of commands it found in the form of os.Args. That way + // updating existing workflow files can be implemented by dynamically. + Parse() ([][]string, error) +} diff --git a/pkg/project/project.go b/pkg/project/project.go new file mode 100644 index 0000000..a5dcfa1 --- /dev/null +++ b/pkg/project/project.go @@ -0,0 +1,29 @@ +package project + +var ( + description = "Command line tool for generating github workflows." + gitSHA = "n/a" + name = "workflow" + source = "https://github.com/xh3b4sd/workflow" + version = "n/a" +) + +func Description() string { + return description +} + +func GitSHA() string { + return gitSHA +} + +func Name() string { + return name +} + +func Source() string { + return source +} + +func Version() string { + return version +} diff --git a/pkg/repo/current.go b/pkg/repo/current.go new file mode 100644 index 0000000..671fc2f --- /dev/null +++ b/pkg/repo/current.go @@ -0,0 +1,61 @@ +package repo + +import ( + "path/filepath" + "strings" +) + +// Current tries to lookup the location of the current Github repository. If +// Current fails to determine the absolute file system path of the program's +// location it panics. Consider the current absolut path. +// +// /Users/xh3b4sd/go/src/github.com/xh3b4sd/workflow/ +// /Users/xh3b4sd/projects/xh3b4sd/apischema +// +// The resulting Github repository returned would be the following respectively. +// +// github.com/xh3b4sd/workflow +// github.com/xh3b4sd/apischema +func Current() string { + a, err := filepath.Abs(".") + if err != nil { + panic(err) + } + + return current(a) +} + +func current(a string) string { + l := strings.Split(a, "/") + + { + var c int + var p []string + var t bool + + for _, s := range l { + if t { + p = append(p, s) + c++ + } + + if s == "github.com" { + p = append(p, s) + t = true + } + + if c >= 2 { + return filepath.Join(p...) + } + } + } + + { + var p []string + + p = append(p, "github.com") + p = append(p, l[len(l)-2:]...) + + return filepath.Join(p...) + } +} diff --git a/pkg/repo/current_test.go b/pkg/repo/current_test.go new file mode 100644 index 0000000..dea1c6e --- /dev/null +++ b/pkg/repo/current_test.go @@ -0,0 +1,38 @@ +package repo + +import ( + "strconv" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_Repo_current(t *testing.T) { + testCases := []struct { + abs string + cur string + }{ + // Case 0 ensures the current repo based on the absolut path structured + // according to legacy golang conventions. + { + abs: "/Users/xh3b4sd/go/src/github.com/xh3b4sd/workflow/", + cur: "github.com/xh3b4sd/workflow", + }, + // Case 1 ensures the current repo based on the absolut path structured + // according to some custom location. + { + abs: "/Users/xh3b4sd/projects/xh3b4sd/apischema", + cur: "github.com/xh3b4sd/apischema", + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + cur := current(tc.abs) + + if tc.cur != cur { + t.Fatalf("\n\n%s\n", cmp.Diff(tc.cur, cur)) + } + }) + } +} diff --git a/pkg/version/checkout.go b/pkg/version/checkout.go new file mode 100644 index 0000000..62e21f5 --- /dev/null +++ b/pkg/version/checkout.go @@ -0,0 +1,5 @@ +package version + +const ( + Checkout = "4" +) diff --git a/pkg/version/golang.go b/pkg/version/golang.go new file mode 100644 index 0000000..2a0fa98 --- /dev/null +++ b/pkg/version/golang.go @@ -0,0 +1,5 @@ +package version + +const ( + Golang = "1.21.1" +) diff --git a/pkg/version/golang_ci_lint.go b/pkg/version/golang_ci_lint.go new file mode 100644 index 0000000..8e67c31 --- /dev/null +++ b/pkg/version/golang_ci_lint.go @@ -0,0 +1,5 @@ +package version + +const ( + GolangCiLint = "1.54.2" +) diff --git a/pkg/version/node.go b/pkg/version/node.go new file mode 100644 index 0000000..248c131 --- /dev/null +++ b/pkg/version/node.go @@ -0,0 +1,5 @@ +package version + +const ( + Node = "18.x.x" +) diff --git a/pkg/version/protoc.go b/pkg/version/protoc.go new file mode 100644 index 0000000..36c6b41 --- /dev/null +++ b/pkg/version/protoc.go @@ -0,0 +1,5 @@ +package version + +const ( + Protoc = "23.4" +) diff --git a/pkg/version/setup_go.go b/pkg/version/setup_go.go new file mode 100644 index 0000000..6d7fab3 --- /dev/null +++ b/pkg/version/setup_go.go @@ -0,0 +1,5 @@ +package version + +const ( + SetupGo = "4" +) diff --git a/pkg/version/setup_node.go b/pkg/version/setup_node.go new file mode 100644 index 0000000..70a5df4 --- /dev/null +++ b/pkg/version/setup_node.go @@ -0,0 +1,5 @@ +package version + +const ( + SetupNode = "3" +) diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..f0aa70e --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,11 @@ +package version + +type Version struct { + Checkout string + Golang string + GolangCiLint string + Node string + Protoc string + SetupGo string + SetupNode string +}