goyek is used to create build pipelines in Go

goyek

Create build pipelines in Go

Go Reference Keep a Changelog GitHub Release go.mod LICENSE

Build Status Go Report Card codecov Mentioned in Awesome Go

Table of Contents:

Description

goyek is used to create build pipelines in Go. As opposed to many other tools, it is just a Go library.

Here are some good parts:

  • No binary installation is needed. Simply add it to go.mod like any other Go module.
    • You can be sure that everyone uses the same version of goyek.
  • It has low learning curve, thanks to the minimal API surface, documentation, and examples.
  • One can reuse code like in any Go application. It may be helpful to use packages like:
  • It is easy to debug, like a regular Go application.
  • The API is based on testing. The task's command look like a unit test. It is even possible to use testify for asserting.
  • Tasks and helpers can be easily tested. See exec_test.go.

goyek API is mainly inspired by the http, testing, and flag packages.

Please Star this repository if you find it valuable and worth maintaining.

Quick start

Copy and paste the following code into build/build.go:

package main

import "github.com/goyek/goyek"

func main() {
	flow := &goyek.Taskflow{}

	hello := flow.Register(taskHello())
	fmt := flow.Register(taskFmt())

	flow.Register(goyek.Task{
		Name:  "all",
		Usage: "build pipeline",
		Deps: goyek.Deps{
			hello,
			fmt,
		},
	})

	flow.Main()
}

func taskHello() goyek.Task {
	return goyek.Task{
		Name:  "hello",
		Usage: "demonstration",
		Command: func(tf *goyek.TF) {
			tf.Log("Hello world!")
		},
	}
}

func taskFmt() goyek.Task {
	return goyek.Task{
		Name:    "fmt",
		Usage:   "go fmt",
		Command: goyek.Exec("go", "fmt", "./..."),
	}
}

Run:

go mod tidy

Sample usage:

$ go run ./build -h
Usage: [flag(s) | task(s)]...
Flags:
  -v    Default: false    Verbose output: log all tasks as they are run. Also print all text from Log and Logf calls even if the task succeeds.
Tasks:
  all      build pipeline
  fmt      go fmt
  hello    demonstration
$ go run ./build all
ok     0.167s
$ go run ./build all -v
===== TASK  hello
Hello world!
----- PASS: hello (0.00s)
===== TASK  fmt
Cmd: go fmt ./...
----- PASS: fmt (0.18s)
ok      0.183s

Tired of writing go run ./build each time? Just add an alias to your shell. For example, add the line below to ~/.bash_aliases:

alias goyek='go run ./build'

Examples

Features

Task registration

The registered tasks are required to have a non-empty name, matching the regular expression ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$, available as TaskNamePattern. This means the following are acceptable:

  • letters (a-z and A-Z)
  • digits (0-9)
  • underscore (_)
  • hyphens (-) - except at the beginning

A task with a given name can be only registered once.

A task without description is not listed in CLI usage.

Task command

Task command is a function which is executed when a task is executed. It is not required to to set a command. Not having a command is very handy when registering "pipelines".

Task dependencies

During task registration it is possible to add a dependency to an already registered task. When taskflow is processed, it makes sure that the dependency is executed before the current task is run. Take note that each task will be executed at most once.

Helpers for running programs

Use func Exec(name string, args ...string) func(*TF) to create a task's command which only runs a single program.

Use func (tf *TF) Cmd(name string, args ...string) *exec.Cmd if within a task's command function when you want to execute more programs or you need more granular control.

Verbose mode

Enable verbose output using the -v CLI flag. It works similar to go test -v. Verbose mode streams all logs to the output. If it is disabled, only logs from failed task are send to the output.

Use func (f *Taskflow) VerboseParam() BoolParam if you need to check if verbose mode was set within a task's command.

Default task

Default task can be assigned via the Taskflow.DefaultTask field.

When the default task is set, then it is run if no task is provided via CLI.

Parameters

The parameters can be set via CLI using the flag syntax.

On the CLI, flags can be set in the following ways:

  • -param simple - for simple single-word values
  • -param "value with blanks"
  • -param="value with blanks"
  • -param - setting boolean parameters implicitly to true

For example, go run ./build test -v -pkg ./... would run the test task with v bool parameter (verbose mode) set to true, and pkg string parameter set to "./...".

Parameters must first be registered via func (f *Taskflow) RegisterValueParam(newValue func() ParamValue, info ParamInfo) ValueParam, or one of the provided methods like RegisterStringParam.

The registered parameters are required to have a non-empty name, matching the regular expression ^[a-zA-Z0-9][a-zA-Z0-9_-]*$, available as ParamNamePattern. This means the following are acceptable:

  • letters (a-z and A-Z)
  • digits (0-9)
  • underscore (_) - except at the beginning
  • hyphens (-) - except at the beginning

After registration, tasks need to specify which parameters they will read. Do this by assigning the RegisteredParam instance from the registration result to the Task.Params field. If a task tries to retrieve the value from an unregistered parameter, the task will fail.

When registration is done, the task's command can retrieve the parameter value using the Get(*TF) method from the registration result instance during the task's Command execution.

See examples/parameters/main.go for a detailed example.

Taskflow will fail execution if there are unused parameters.

Supported Go versions

Minimal supported Go version is 1.11.

Alternatives

Make

While Make is currently the de facto standard, it has some pitfalls:

  • Requires to learn Make, which is not so easy.
  • It is hard to develop a Makefile which is truly cross-platform.
  • Debugging and testing Make targets is not fun.

However, if you know Make and are happy with it, do not change it. Make is very powerful and a lot of stuff can be made faster, if you know how to use it.

goyek is intended to be simpler and easier to learn, while still being able to handle most use cases.

Mage

Mage is a framework/tool which magically discovers the targets from magefiles, which results in some drawbacks:

  • Requires using build tags.
  • Reusing tasks is hacky.
  • Requires installation or using zero install option which is slow.
  • Debugging would be extermly complex.
  • Magical by design (of course one may like it).

goyek is intended to be a non-magical alternative for Mage. Write regular Go code. No build tags, special names for functions, tricky imports.

Task

While Task is simpler and easier to use than Make it still has similar problems:

  • Requires to learn Task's YAML structure and the minimalistic, cross-platform interpreter which it uses.
  • Debugging and testing tasks is not fun.
  • Hard to make reusable tasks.
  • Requires to "install" the tool.

Bazel

Bazel is a very sophisticated tool which is created to efficiently handle complex and long-running build pipelines. It requires the build target inputs and outputs to be fully specified.

goyek is just a simple library that is mainly supposed to create a build pipeline consisting of commands like go vet, go test, go build. However, take notice that goyek is a library. Nothing prevents you from, for example, using Mage's target package to make your build pipeline more efficient.

Presentations

Date Presentation Description
2021-05-05 goyek - Create build pipelines in Go goyek v0.3.0 demo
2021-03-10 taskflow - Create build pipelines in Go taskflow v0.1.1 demo
2020-12-14 Build pipeline for a Go project build pipeline using Make, Mage, and taskflow v0.1.0

Note: goyek was named taskflow before v0.3.0.

Contributing

We are open to any feedback and contribution.

You can find us on Gophers Slack in #goyek channel.

Feel free to create an issue, or a pull request.

You may use Gitpod to experiment with the codebase.

Comments
  • Rework output handling

    Rework output handling

    Why

    This PR is a proposal to better separate stdout/stderr usage. Primary motivation for this was the recommendation from clig.dev to send messages to stderr and have stdout only for net information from the tasks/tools. Since the change is an incompatible one, further, related incompatibilities are included, see below.

    What

    1. Expand Taskflow.Output as a separate type having two writer, one for Primary output, one for Message output. (incompatible API change)
    2. Goyek code was modified to only use Message output. (incompatible user-interface change) As a side-effect, any fmt.Fprintf error "handling" has been centralized and explicitly skipped.
    3. Removed the DefaultOutput global variable. It seems as if this was only used for the tests. Tests should override this explicitly anyway, to avoid side-effects. Furthermore, such global defaults are tricky; I refer to the potential problems as seen with http.Client and the transport defaults. (incompatible API change)

    Documentation/Changelog has not yet been updated, would do so if proposed changes are discussed and settled.

    Checklist

    • [ ] CHANGELOG.md is updated.
    • [ ] README.md is updated.
    • [x] The code changes follow Effective Go.
  • Feature: registered parameters

    Feature: registered parameters

    This PR is the result of #37 .

    Thinking further, I figured that the registered parameters could be tracked by Go's built-in flags system. This handles usage output, as well as invalid parameter handling out of the box; As well as having less code to handle within this library.

    Example output of go run build\build.go -h (output shortened to highlight diff):

    Usage: [flag(s)] task(s)
    Flags:
      -ci string
            Whether CI is calling the build script (default "false")
      -v    Verbose output: log all tasks as they are run. Also print all text from Log and Logf calls even if the task succeeds.
    Tasks:
      ...
      diff        git diff; -ci
      ...
    

    This is a breaking change as Taskflow.Params is dropped and tasks need to specify which parameters they read (a change as intended).

    More thoughts on these parameters:

    • I wish the type information could be saved. As it is now, implementation of TFParams mirrors all the parsing what flag package has internally for the typed parameters. As it stands, the current setup does not support keeping the type information. Unless the type information of the parameter (string, int, ...) is carried over to a RegisteredParamString, RegisteredParamInt, and so forth - though this generates even more code.
    • Is there big of a need of a built-in support for text/json parsing of parameters? This sounds a bit overengineered to me.

    Most of the tests could carry over or needed only slight modification. Configure and MustConfigure aren't covered yet.

    How does this look?

  • Add possibility to change the OOTB parameters

    Add possibility to change the OOTB parameters

    Why

    I wanted a method to set the default values for OOTB params which wasn't possible, because if you tried to register a verbose parameter again, goyek would panic because it was already registered.

    What

    I added a new method to create a flow (NewFlow) which accepts an Options-struct in which the defaults can be set.

    Checklist

    • [x] CHANGELOG.md is updated.
    • [x] README.md is updated.
    • [x] The code changes follow Effective Go.
  • Use HTML comment syntax in the PR template

    Use HTML comment syntax in the PR template

    Why

    Why this pull request was created? Explain the problem that this pull request is trying to solve.

    The current pull request template:

    • requires to add new lines manually (if not the explanation is be added to the section comments):
      • write: write
      • preview: preview
    • doesn't inform what to do with the checklist items when the mentioned points are not applicable to the introduced changes

    What

    What does this pull request contain? Explain what is this pull request changing.

    Add comments to the pull request template so filling it is easier. The comment in the checklist informs that when no changes are required, the items should be checked (better visibility). The comments are not visible in the preview mode, so all new PRs will look the same as the older one.

    Checklist

    • [x] CHANGELOG.md is updated.
    • [x] README.md is updated.
    • [x] The code changes follow Effective Go.
  • Feature: Registered parameters

    Feature: Registered parameters

    Continuation of #39 .

    Main points:

    • Parameters need to be registered in order to be used
    • Registered parameters are described in usage output
    • Parameters are provided uniformly via flags (either as --longform or, optionally, -l) -- see https://clig.dev for inspiration

    Note: This is an incompatible change, for both the CLI and the Go interface, in line with current v0 of Taskflow.

  • Draft: Option to mark params as required

    Draft: Option to mark params as required

    Why

    Params could not be marked as required, so for every parameter you had to check whether it had a value or not inside the task handler. It seems like basic functionality for a task runner to be able to mark params as required.

    What

    It is a draft pull request to get feedback on the way I've set the validation up. I've made it so only the params which are used in the task which is being run, are checked whether they are required. All params which are required, but not set, are printed and the task fails.

    If a maintainer or other contributor thinks this is the right direction for such a feature, I will write tests, update the CHANGELOG etc, but I wanted to have feedback first.

    Checklist

    • [ ] CHANGELOG.md is updated.
    • [ ] README.md is updated.
    • [x] The code changes follow Effective Go.
    • [ ] The code changes are covered by tests.
  • Alternative method signature for Exec / Simplified Error handling

    Alternative method signature for Exec / Simplified Error handling

    Hi,

    I became aware of this by the Golang Weekly newsletter and I like the idea of goyek. I started to play around with it and I would like to start a discussion about two potential changes:

    Signature change for goyek.Exec

    Change the signature for goyek.Exec from

    func Exec(name string, args ...string) func(*TF)
    

    to

    func Exec(cmdLine string) func(*TF)
    

    This is inspired by github.com/bitfield/script (see https://github.com/bitfield/script/blob/9847d19e69d72d73b191e1f3c65d5dba94605a1c/filters.go#L123).

    The main advantage of this change to me is, that it increases readability and it simplifies the process of copying back and forth of the shell commands between goyek and an actual shell (or a Makefile/Shell script one is migrating). By using the backticks for the string in Go, in most cases there would be no need to modify the command while copying back and forth.

    Error handling

    I could imagine, that a task often has a pattern like this:

    res, err := someFunc()
    if err != nil {
    	tf.Error(err)
    }
    ...
    

    Since goyek is somewhat inspired by testing.T (and its relatives), I though about how I nowadays handle errors in tests. I highly prefere the very simple https://github.com/matryer/is package, which provides me a method for error handling like this (https://pkg.go.dev/github.com/matryer/is#I.NoErr):

    is.NoErr(err)
    

    If the error is nil, the normal flow continues. If the error is non nil, the tests are ended with t.Fail().

    For goyek I could imagine two methods with the following signature:

    func (*goyek.TF).IfErr(err error, args ...interface{})
    func (*goyek.TF).IfErrf(err error, format string, args ...interface{})
    

    For the exact naming there are several options:

    • tf.IfErr
    • tf.IfError
    • tf.IsErr
    • tf.IsNoErr
    • ...

    Thanks again for your initiative, keep up the good work.

  • Add TF.Run for running sub-tasks

    Add TF.Run for running sub-tasks

    Why

    Like testing package it would be very helpful to create something like sub-tasks.

    Example use case: run go test for each Go Module within a repository.

    How

    Implement TF.Run() method which works similarly to the one in the testing package.

    We can also dogfood it e.g. to run golangci-lint for all Go modules within this repository.

    We could also support running a concrete subtask e.g ./goyek Task/Sub1 (separate PR).

    We could also try to support running tasks and subtasks using regex/wildcards (separate PR). go test -run regex takes regex . If we decide to take the same approach we wrap the regex with ^ $.

  • TF Log(f), Error(f), Fatal(f), Skip(f), methods add file and line information

    TF Log(f), Error(f), Fatal(f), Skip(f), methods add file and line information

    Why

    Like testing package it would be very helpful if the "printing" methods add file and line information.

    For example, it will help to find where exactly a task is failing.

    How

    1. The TF's Log(f), Error(f), Fatal(f), Skip(f) add file and line information
    2. Add TF.Helper() method which works like the one for testing package.

      Helper marks the calling function as a test helper function. When printing file and line information, that function will be skipped. Helper may be called simultaneously from multiple goroutines.

    Remarks:

    1. Printing via tf.Output() MUST NOT print file and line information
    2. Reference implementation: https://github.com/golang/go/blob/master/src/testing/testing.go

    We can consider also adding a field like Taskflow.DisableCodeline to provide a possibility to disable such functionality. However, I would rather not add it until someone really needs it. One can always use tf.Output() to print without file and line information.

  • Change API

    Change API

    Why

    Partially addresses https://github.com/goyek/goyek/issues/62

    What

    • Rename Task.Command field to Action to avoid confusion with exec.Command and TF.Cmd.
    • Rename Taskflow type to Flow to make the name simpler.
    • Rename TF type to Progress to name the type more meaningful.
    • Remove DefaultOutput global variable to reduce the API surface.
    • Remove TF.Exec method to reduce the API surface.

    Checklist

    • [x] CHANGELOG.md is updated.
    • [x] README.md is updated.
    • [x] The code changes follow Effective Go.
  • Update wrapper scripts in README.md

    Update wrapper scripts in README.md

    Why

    Why this pull request was created? Explain the problem that this pull request is trying to solve.

    README.md has not up-to-date wrapper scripts.

    What

    What does this pull request contain? Explain what is this pull request changing.

    Update wrapper scripts in README.md.

    Checklist

    • [x] CHANGELOG.md is updated.
    • [x] README.md is updated.
    • [x] The code changes follow Effective Go.
  • Allow running tasks concurrently

    Allow running tasks concurrently

    Why

    I'm looking at goyek as an alternative to Mage - thanks for open sourcing this, so far I am loving the API as it feels very similar to the built in testing library and to libraries like urfav/cli.

    Something that I liked in Mage is that it was very easy to run tasks concurrently. For example, if task all depends on tasks a and b, tasks a and b would be executed concurrently in separate Goroutines. I tried modelling this in goyek as follows:

    package main
    
    import (
    	"time"
    
    	"github.com/goyek/goyek"
    )
    
    func main() {
    	flow := &goyek.Flow{}
    
    	a := flow.Register(goyek.Task{
    		Name: "a",
    		Action: func(tf *goyek.TF) {
    			time.Sleep(time.Second * 1)
    			tf.Log("a")
    			time.Sleep(time.Second * 1)
    			tf.Log("a")
    			time.Sleep(time.Second * 1)
    			tf.Log("a")
    		},
    	})
    
    	b := flow.Register(goyek.Task{
    		Name: "b",
    		Action: func(tf *goyek.TF) {
    			time.Sleep(time.Second * 1)
    			tf.Log("b")
    			time.Sleep(time.Second * 1)
    			tf.Log("b")
    			time.Sleep(time.Second * 1)
    			tf.Log("b")
    		},
    	})
    
    	flow.Register(goyek.Task{
    		Name: "all",
    		Deps: goyek.Deps{a, b},
    	})
    
    	flow.Main()
    }
    

    It appears that the tasks run sequentially, even though a and b don't depend on one another.

    go run ./build all -v
    ===== TASK  a
    a
    a
    a
    ----- PASS: a (3.00s)
    ===== TASK  b
    b
    b
    b
    ----- PASS: b (3.00s)
    ok      6.006s
    

    What

    Would you be open to a contribution to support running tasks concurrently?

    Some approaches to the API could be to add a flag at the Flow level:

    flow := &goyek.Flow{
    	Concurrent: true,
    }
    
    or to allow concurrency to be specified at a task level:
    
    ```go
    flow.Register(goyek.Task{
    	Name: "all",
    	Deps: goyek.ConcurrentDeps{a, b},
    })
    
A library to help you create pipelines in Golang

pipeline Pipeline is a go library that helps you build pipelines without worrying about channel management and concurrency. It contains common fan-in

Dec 13, 2022
Build powerful pipelines in any programming language.
Build powerful pipelines in any programming language.

Gaia is an open source automation platform which makes it easy and fun to build powerful pipelines in any programming language. Based on HashiCorp's g

Jan 3, 2023
Least-recently-used-LRU- - Design CacheEvictionPolicy with 2 strategy LRU(Least recently used)

Least-recently-used-LRU- Design CacheEvictionPolicy with 2 strategy LRU(Least re

Jan 4, 2022
Pipelines using goroutines

pipeline This package provides a simplistic implementation of Go pipelines as outlined in Go Concurrency Patterns: Pipelines and cancellation. Docs Go

Oct 5, 2022
elPrep: a high-performance tool for analyzing sequence alignment/map files in sequencing pipelines.
elPrep: a high-performance tool for analyzing sequence alignment/map files in sequencing pipelines.

Overview elPrep is a high-performance tool for analyzing .sam/.bam files (up to and including variant calling) in sequencing pipelines. The key advant

Nov 2, 2022
Robust, flexible and resource-efficient pipelines using Go and the commandline
Robust, flexible and resource-efficient pipelines using Go and the commandline

Robust, flexible and resource-efficient pipelines using Go and the commandline Project links: Documentation & Main Website | Issue Tracker | Chat Why

Dec 25, 2022
🐺 Deploy Databases and Services Easily for Development and Testing Pipelines.
🐺 Deploy Databases and Services Easily for Development and Testing Pipelines.

Peanut provides an API and a command line tool to deploy and configure the commonly used services like databases, message brokers, graphing tools ... etc. It perfectly suited for development, manual testing, automated testing pipelines where mocking is not possible and test drives.

Jan 3, 2023
🏯 Monitor your (gitlab/github) CI/CD pipelines via command line interface with fortress
🏯 Monitor your (gitlab/github) CI/CD pipelines via command line interface with fortress

__ _ / _| | | | |_ ___ _ __| |_ _ __ ___ ___ ___ | _/ _ \| '__| __| '__/ _ \/ __/ _

Mar 31, 2022
Optimus is an easy-to-use, reliable, and performant workflow orchestrator for data transformation, data modeling, pipelines, and data quality management.

Optimus Optimus is an easy-to-use, reliable, and performant workflow orchestrator for data transformation, data modeling, pipelines, and data quality

Jan 6, 2023
Dud is a lightweight tool for versioning data alongside source code and building data pipelines.

Dud Website | Install | Getting Started | Source Code Dud is a lightweight tool for versioning data alongside source code and building data pipelines.

Jan 1, 2023
GitOops is a tool to help attackers and defenders identify lateral movement and privilege escalation paths in GitHub organizations by abusing CI/CD pipelines and GitHub access controls.
GitOops is a tool to help attackers and defenders identify lateral movement and privilege escalation paths in GitHub organizations by abusing CI/CD pipelines and GitHub access controls.

GitOops is a tool to help attackers and defenders identify lateral movement and privilege escalation paths in GitHub organizations by abusing CI/CD pipelines and GitHub access controls.

Jan 2, 2023
A simple CLI tool to use the _simulate API of elasticsearch to quickly test pipelines

elasticsearch-pipeline-tester A simple CLI tool to use the _simulate API of elasticsearch to quickly test pipelines usage: pipelinetester [<flags>] <p

Oct 19, 2021
Drone plugin to skip pipelines based on changed files

drone-skip-pipeline Drone plugin to skip pipelines based on changed files. Build Build the binary with the following command: export GOOS=linux export

Aug 7, 2022
tfa is a 2fa cli tool that aims to help you to generate 2fa code on CI/CD pipelines.

tfa tfa is 2fa cli tool that aim to help you to generate 2fa code on CI/CD pipelines. You can provide secret with stdin or flag. Install brew install

Nov 27, 2022
Application to learn and demo Tekton pipelines

Tekton sample Application to learn and demo Tekton pipelines Building $ go test ./pkg/api && go build Running it locally $ podman-compose up --force-r

Oct 28, 2021
A small web dashboard with stats for all pipelines of Buildkite organization.
A small web dashboard with stats for all pipelines of Buildkite organization.

Buildkite Stats A small Buildkite dashboard useful to prioritize which pipelines a Buildkite organization is waiting the most on. Noteworthy details:

Apr 25, 2022
Dataflow is a Kubernetes-native platform for executing large parallel data-processing pipelines.
Dataflow is a Kubernetes-native platform for executing large parallel data-processing pipelines.

Dataflow Summary Dataflow is a Kubernetes-native platform for executing large parallel data-processing pipelines. Each pipeline is specified as a Kube

Jan 4, 2023
Run Buildkite Pipelines locally

bkl - Run Buildkite Pipelines locally Run buildkite pipelines locally (no buildkite.com required). Installing Either download a release binary or if y

Aug 23, 2022
K3ai Executor is the runner pod to execute the "one-click" pipelines
K3ai Executor is the runner pod to execute the

Welcome to K3ai Project K3ai is a lightweight tool to get an AI Infrastructure Stack up in minutes not days. NOTE on the K3ai origins Original K3ai Pr

Nov 11, 2021
🤖 DroneCI plugin to skip pipelines based on files changes

DroneCI Skip Pipeline ?? DroneCI plugin to skip pipelines based on files changes Motivations This DroneCI plugin enables you skip (or short-circuit) a

Dec 14, 2022