Skip to content

Commit

Permalink
feat: add shuttle golang actions as an execution type (#159)
Browse files Browse the repository at this point in the history
This feature adds golang as a native shell scripting alternative. 

This means that you are now able to use golang functions as an alternative to 

```yaml
scripts:
  build: 
    actions:
      - shell: echo "something"
```

now you can create a file

touch actions/build.go
With a function

```go
package main

func Build(ctx context.Context) error {
   println("something")
}
```

Args and shuttle variables aren't currently supported and will follow up in a later PR. This sets the stage for further development, do note that the API isn't set in stone yet.
  • Loading branch information
kjuulh committed Apr 17, 2023
1 parent 4d93f7b commit 1a57a73
Show file tree
Hide file tree
Showing 69 changed files with 1,249 additions and 8 deletions.
24 changes: 23 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package cmd

import (
stdcontext "context"
"fmt"
"io"
"log"
"os"
"path"

"github.com/lunarway/shuttle/pkg/executors/golang/executer"
"github.com/lunarway/shuttle/pkg/ui"

"github.com/lunarway/shuttle/pkg/config"
Expand Down Expand Up @@ -173,9 +175,29 @@ func getProjectContext(rootCmd *cobra.Command, uii *ui.UI, projectPath string, c
}

var c config.ShuttleProjectContext
_, err = c.Setup(fullProjectPath, uii, clean, skipGitPlanPulling, plan, projectFlagSet)
projectContext, err := c.Setup(fullProjectPath, uii, clean, skipGitPlanPulling, plan, projectFlagSet)
if err != nil {
return config.ShuttleProjectContext{}, err
}

ctx := stdcontext.Background()
taskActions, err := executer.List(ctx, uii, fmt.Sprintf("%s/shuttle.yaml", projectContext.ProjectPath), &c)
if err != nil {
return config.ShuttleProjectContext{}, err
}

for name := range taskActions {
c.Scripts[name] = config.ShuttlePlanScript{
Description: name,
Actions: []config.ShuttleAction{
{
Task: name,
},
},

Args: []config.ShuttleScriptArgs{},
}
}

return c, nil
}
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func newRun(uii *ui.UI, contextProvider contextProvider) *cobra.Command {
validateArgs bool
)

executorRegistry := executors.NewRegistry(executors.ShellExecutor)
executorRegistry := executors.NewRegistry(executors.ShellExecutor, executors.TaskExecutor)

runCmd := &cobra.Command{
Use: "run [command]",
Expand Down
2 changes: 1 addition & 1 deletion examples/moon-base/shuttle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ scripts:
serve:
description: Serve starts an nginx image as a long running process.
actions:
- shell: docker run --rm -i --name shuttle-nginx nginx
- shell: docker run --rm -i --name shuttle-nginx nginx
9 changes: 9 additions & 0 deletions examples/moon-base/shuttletask/dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "context"

func Dev(ctx context.Context) error {
println("dev")

return nil
}
5 changes: 5 additions & 0 deletions examples/moon-base/shuttletask/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module actions

go 1.18

replace github.com/lunarway/shuttle => ../../../../../
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ require (
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/otiai10/copy v1.9.0
golang.org/x/mod v0.6.0
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
)

require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
Expand All @@ -29,5 +35,6 @@ require (
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/sys v0.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMF
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4=
github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down Expand Up @@ -61,17 +68,22 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
1 change: 1 addition & 0 deletions pkg/config/shuttleconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func (c *ShuttleProjectContext) Setup(projectPath string, uii *ui.UI, clean bool
if err != nil {
return nil, err
}

c.Scripts = make(map[string]ShuttlePlanScript)
for scriptName, script := range c.Plan.Scripts {
c.Scripts[scriptName] = script
Expand Down
1 change: 1 addition & 0 deletions pkg/config/shuttleplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (a ShuttleScriptArgs) String() string {
type ShuttleAction struct {
Shell string `yaml:"shell"`
Dockerfile string `yaml:"dockerfile"`
Task string `yaml:"task"`
}

// ShuttlePlanConfiguration is a ShuttlePlan sub-element
Expand Down
9 changes: 5 additions & 4 deletions pkg/executors/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (

"github.com/lunarway/shuttle/pkg/config"
"github.com/lunarway/shuttle/pkg/errors"
"github.com/lunarway/shuttle/pkg/ui"
)

type Registry struct {
executors []Matcher
}

type Matcher func(config.ShuttleAction) (Executor, bool)
type Executor func(context.Context, ActionExecutionContext) error
type Executor func(context.Context, *ui.UI, ActionExecutionContext) error

func NewRegistry(executors ...Matcher) *Registry {
return &Registry{
Expand Down Expand Up @@ -63,7 +64,7 @@ func (r *Registry) Execute(ctx context.Context, p config.ShuttleProjectContext,
Action: action,
ActionIndex: actionIndex,
}
err := r.executeAction(ctx, actionContext)
err := r.executeAction(ctx, p.UI, actionContext)
if err != nil {
return err
}
Expand Down Expand Up @@ -176,11 +177,11 @@ func expectedArgumentsHelp(command string, args []config.ShuttleScriptArgs) stri
return s.String()
}

func (r *Registry) executeAction(ctx context.Context, context ActionExecutionContext) error {
func (r *Registry) executeAction(ctx context.Context, ui *ui.UI, context ActionExecutionContext) error {
for _, executor := range r.executors {
handler, ok := executor(context.Action)
if ok {
return handler(ctx, context)
return handler(ctx, ui, context)
}
}

Expand Down
116 changes: 116 additions & 0 deletions pkg/executors/golang/cmder/cmder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package cmder

import (
"context"
"encoding/json"
"fmt"
"log"
"reflect"

"github.com/spf13/cobra"
)

type RootCmd struct {
Cmds []*Cmd
}

func NewRoot() *RootCmd {
cmd := &RootCmd{}

return cmd
}

func (rc *RootCmd) AddCmds(cmd ...*Cmd) *RootCmd {
rc.Cmds = append(rc.Cmds, cmd...)

return rc
}

func (rc *RootCmd) Execute() {
rootcmd := &cobra.Command{Use: "actions"}

rootcmd.AddCommand(
&cobra.Command{Use: "ls", RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Parent().Help()
}},
)

rootcmd.AddCommand(&cobra.Command{Hidden: true, Use: "lsjson", RunE: func(cmd *cobra.Command, args []string) error {
cmdNames := make([]string, len(rc.Cmds))
for i, cmd := range rc.Cmds {
cmd := cmd
cmdNames[i] = cmd.Name
}

rawJson, err := json.Marshal(cmdNames)
if err != nil {
return err
}

// Prints the commands and args as json to stdout
_, err = fmt.Printf("%s", string(rawJson))
if err != nil {
return err
}

return nil
}})

for _, cmd := range rc.Cmds {
cmd := cmd
parameters := make([]string, len(cmd.Args))

cobracmd := &cobra.Command{
Use: cmd.Name,
RunE: func(cobracmd *cobra.Command, args []string) error {
if err := cobracmd.ParseFlags(args); err != nil {
return err
}

inputs := make([]reflect.Value, 0, len(cmd.Args)+1)
inputs = append(inputs, reflect.ValueOf(context.Background()))
for _, arg := range parameters {
inputs = append(inputs, reflect.ValueOf(arg))
}

reflect.
ValueOf(cmd.Func).
Call(inputs)
return nil
},
}
for i, arg := range cmd.Args {
cobracmd.PersistentFlags().StringVar(&parameters[i], arg.Name, "", "")
_ = cobracmd.MarkPersistentFlagRequired(arg.Name)
}

rootcmd.AddCommand(cobracmd)
}

if err := rootcmd.Execute(); err != nil {
log.Fatalf("%v", err)
}
}

type Arg struct {
Name string
}

type Cmd struct {
Name string
Func any
Args []Arg
}

func NewCmd(name string, f any) *Cmd {
return &Cmd{
Name: name,
Func: f,
Args: []Arg{},
}
}

func WithArgs(cmd *Cmd, argName string) *Cmd {
cmd.Args = append(cmd.Args, Arg{Name: argName})
return cmd
}
46 changes: 46 additions & 0 deletions pkg/executors/golang/codegen/codegen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package codegen

import (
"context"
"embed"
"html/template"
"os"
"path"
"strings"

"github.com/lunarway/shuttle/pkg/executors/golang/discover"
"github.com/lunarway/shuttle/pkg/executors/golang/parser"
)

var (
//go:embed templates/mainFile.tmpl
mainFileTmpl embed.FS
)

func GenerateMainFile(
ctx context.Context,
shuttlelocaldir string,
actions *discover.ActionsDiscovered,
functions []*parser.Function,
) error {
tmpmainfile := path.Join(shuttlelocaldir, "tmp/main.go")

file, err := os.Create(tmpmainfile)
if err != nil {
return err
}

tmpl := template.
Must(
template.
New("mainFile.tmpl").
Funcs(map[string]any{
"lower": strings.ToLower,
}).
ParseFS(mainFileTmpl, "templates/mainFile.tmpl"),
)

err = tmpl.Execute(file, functions)

return err
}
22 changes: 22 additions & 0 deletions pkg/executors/golang/codegen/compilation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package codegen

import (
"context"
"os/exec"
"path"

"github.com/lunarway/shuttle/pkg/ui"
)

func CompileBinary(ctx context.Context, ui *ui.UI, shuttlelocaldir string) (string, error) {
cmd := exec.Command("go", "build")
cmd.Dir = path.Join(shuttlelocaldir, "tmp")

output, err := cmd.CombinedOutput()
if err != nil {
ui.Verboseln("compile-binary output: %s", string(output))
return "", err
}

return path.Join(shuttlelocaldir, "tmp", "actions"), nil
}
22 changes: 22 additions & 0 deletions pkg/executors/golang/codegen/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package codegen

import (
"context"
"os/exec"
"path"

"github.com/lunarway/shuttle/pkg/ui"
)

func Format(ctx context.Context, ui *ui.UI, shuttlelocaldir string) error {
cmd := exec.Command("go", "fmt", "./...")
cmd.Dir = path.Join(shuttlelocaldir, "tmp")

output, err := cmd.CombinedOutput()
if err != nil {
ui.Errorln("go fmt: %s, error: %v", string(output), err)
return err
}

return nil
}

0 comments on commit 1a57a73

Please sign in to comment.