Skip to content

Commit

Permalink
Add verbose & clean args + refactor git to go-cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
emilingerslev committed Oct 1, 2018
1 parent 90da7bc commit e6cc159
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 69 deletions.
19 changes: 16 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import (
"os"
"path"

"github.com/lunarway/shuttle/pkg/output"

"github.com/lunarway/shuttle/pkg/config"
"github.com/spf13/cobra"
)

var (
projectPath string
verbose bool
clean bool
version = "<dev-version>"
commit = "<unspecified-commit>"
)
Expand All @@ -26,17 +30,26 @@ A CLI for handling shared build and deploy tools between many
projects no matter what technologies the project is using.
Read more about shuttle at https://github.com/lunarway/shuttle`, version),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if verbose {
fmt.Println("Running shuttle")
fmt.Println(fmt.Sprintf("- version: %s", version))
fmt.Println(fmt.Sprintf("- commit: %s", commit))
fmt.Println(fmt.Sprintf("- project-path: %s", projectPath))
}
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
output.ExitWithErrorCode(1, fmt.Sprintf("%s", err))
}
}

func init() {
rootCmd.PersistentFlags().StringVarP(&projectPath, "project", "p", ".", "Project path")
rootCmd.PersistentFlags().BoolVarP(&clean, "clean", "c", false, "Start from clean setup")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print verbose output")
}

func getProjectContext() config.ShuttleProjectContext {
Expand All @@ -48,6 +61,6 @@ func getProjectContext() config.ShuttleProjectContext {

var fullProjectPath = path.Join(dir, projectPath)
var c config.ShuttleProjectContext
c.Setup(fullProjectPath)
c.Setup(fullProjectPath, verbose, clean)
return c
}
8 changes: 4 additions & 4 deletions cmd/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type context struct {
Args map[string]string
}

var output string
var templateOutput string
var templateCmd = &cobra.Command{
Use: "template [template]",
Short: "Execute a template",
Expand Down Expand Up @@ -52,7 +52,7 @@ var templateCmd = &cobra.Command{
Vars: projectContext.Config.Variables,
}

if output == "" {
if templateOutput == "" {
err = tmpl.Execute(os.Stdout, context)
if err != nil {
panic(err)
Expand All @@ -61,7 +61,7 @@ var templateCmd = &cobra.Command{
// TODO: This is probably not the right place to initialize the temp dir?
os.MkdirAll(projectContext.TempDirectoryPath, os.ModePerm)

file, err := os.Create(path.Join(projectContext.TempDirectoryPath, output))
file, err := os.Create(path.Join(projectContext.TempDirectoryPath, templateOutput))
if err != nil {
panic(err)
}
Expand All @@ -75,7 +75,7 @@ var templateCmd = &cobra.Command{
}

func init() {
templateCmd.Flags().StringVarP(&output, "output", "o", "", "Select filename to output file to in temporary directory")
templateCmd.Flags().StringVarP(&templateOutput, "output", "o", "", "Select filename to output file to in temporary directory")
rootCmd.AddCommand(templateCmd)
}

Expand Down
3 changes: 2 additions & 1 deletion examples/moon-base/shuttle.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
plan: '../station-plan'
vars:
docker:
image: earth-united/moon-base
image: earth-united/moon-base
run-as-root: false
7 changes: 6 additions & 1 deletion examples/station-plan/plan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ scripts:
test:
description: Run test for the project
actions:
- shell: go test
- shell: go test
say-hi:
description: just say hi
args: []
actions:
- shell: echo "test"
14 changes: 12 additions & 2 deletions pkg/config/shuttleconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"fmt"
"io/ioutil"
"os"
"path"

yaml "gopkg.in/yaml.v2"
Expand Down Expand Up @@ -30,12 +31,21 @@ type ShuttleProjectContext struct {
}

// Setup the ShuttleProjectContext for a specific path
func (c *ShuttleProjectContext) Setup(projectPath string) *ShuttleProjectContext {
func (c *ShuttleProjectContext) Setup(projectPath string, verbose bool, clean bool) *ShuttleProjectContext {
c.Config.getConf(projectPath)
c.ProjectPath = projectPath
c.LocalShuttleDirectoryPath = path.Join(c.ProjectPath, ".shuttle")

if clean {
os.RemoveAll(c.LocalShuttleDirectoryPath)
if verbose {
fmt.Println(fmt.Sprintf("Cleaning %s", c.LocalShuttleDirectoryPath))
}
}
os.MkdirAll(c.LocalShuttleDirectoryPath, os.ModePerm)

c.TempDirectoryPath = path.Join(c.LocalShuttleDirectoryPath, "temp")
c.LocalPlanPath = FetchPlan(c.Config.Plan, projectPath, c.LocalShuttleDirectoryPath)
c.LocalPlanPath = FetchPlan(c.Config.Plan, projectPath, c.LocalShuttleDirectoryPath, verbose)
c.Plan.Load(c.LocalPlanPath)
c.Scripts = make(map[string]ShuttlePlanScript)
for scriptName, script := range c.Plan.Scripts {
Expand Down
10 changes: 8 additions & 2 deletions pkg/config/shuttleplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"

"github.com/lunarway/shuttle/pkg/git"
"github.com/lunarway/shuttle/pkg/output"
"gopkg.in/yaml.v2"
)

Expand All @@ -21,11 +22,13 @@ type ShuttlePlanScript struct {
Args []ShuttleScriptArgs `yaml:"args"`
}

// ShuttleScriptArgs describes an arguments that a script accepts
type ShuttleScriptArgs struct {
Name string `yaml:"name"`
Required bool `yaml:"required"`
}

// ShuttleAction describes an action done by a shuttle script
type ShuttleAction struct {
Shell string `yaml:"shell"`
Dockerfile string `yaml:"dockerfile"`
Expand Down Expand Up @@ -60,15 +63,18 @@ func (p *ShuttlePlanConfiguration) Load(planPath string) *ShuttlePlanConfigurati
}

// FetchPlan so it exists locally and return path to that plan
func FetchPlan(plan string, projectPath string, localShuttleDirectoryPath string) string {
func FetchPlan(plan string, projectPath string, localShuttleDirectoryPath string, verbose bool) string {
switch {
case git.IsGitPlan(plan):
return git.GetGitPlan(plan, localShuttleDirectoryPath)
output.Verbose(verbose, "Using git plan at '%s'", plan)
return git.GetGitPlan(plan, localShuttleDirectoryPath, verbose)
case isMatching("^http://|^https://", plan):
panic("plan not valid: http is not supported yet")
case isFilePath(plan, true):
output.Verbose(verbose, "Using local plan at '%s'", plan)
return plan
case isFilePath(plan, false):
output.Verbose(verbose, "Using local plan at '%s'", plan)
return path.Join(projectPath, plan)

}
Expand Down
91 changes: 38 additions & 53 deletions pkg/git/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package git
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"os/user"
"path"
"regexp"
"strings"
"time"

"github.com/lunarway/shuttle/pkg/output"

go_cmd "github.com/go-cmd/cmd"
)

type gitPlan struct {
Expand Down Expand Up @@ -64,36 +65,13 @@ func IsGitPlan(plan string) bool {
}

// GetGitPlan will pull git repository and return its path
func GetGitPlan(plan string, localShuttleDirectoryPath string) string {
// We need the user to find the homedir.

func GetGitPlan(plan string, localShuttleDirectoryPath string, verbose bool) string {
parsedGitPlan := parseGitPlan(plan)
planPath := path.Join(localShuttleDirectoryPath, "plan")

if fileAvailable(planPath) {

execCmd := exec.Command("git", "pull", "origin")
execCmd.Env = append(os.Environ())
execCmd.Dir = planPath

var stdout, stderr []byte
var errStdout, errStderr error
stdoutIn, _ := execCmd.StdoutPipe()
stderrIn, _ := execCmd.StderrPipe()
startErr := execCmd.Start()
checkIfError(startErr)

go func() {
stdout, errStdout = copyAndCapture(ioutil.Discard, stdoutIn)
}()

go func() {
stderr, errStderr = copyAndCapture(ioutil.Discard, stderrIn)
}()

err := execCmd.Wait()
checkIfError(err)

output.Verbose(verbose, "Pulling latest git changes")
gitCmd("pull origin", planPath, verbose)
} else {
os.MkdirAll(localShuttleDirectoryPath, os.ModePerm)

Expand All @@ -106,31 +84,8 @@ func GetGitPlan(plan string, localShuttleDirectoryPath string) string {
panic(fmt.Sprintf("Unknown protocol '%s'", parsedGitPlan.protocol))
}

execCmd := exec.Command("git", "clone", cloneArg, "plan")
execCmd.Env = append(os.Environ())
execCmd.Dir = localShuttleDirectoryPath

var stdout, stderr []byte
var errStdout, errStderr error
stdoutIn, _ := execCmd.StdoutPipe()
stderrIn, _ := execCmd.StderrPipe()
startErr := execCmd.Start()
checkIfError(startErr)

go func() {
stdout, errStdout = copyAndCapture(ioutil.Discard, stdoutIn)
}()

go func() {
stderr, errStderr = copyAndCapture(ioutil.Discard, stderrIn)
}()

err := execCmd.Wait()

if err != nil {
output.ExitWithErrorCode(3, fmt.Sprintf("Could not clone %s\ngit output:%v\n%v", plan, string(stdout), string(stderr)))
}

output.Verbose(verbose, "Cloning repository %s", cloneArg)
gitCmd("clone "+cloneArg+" plan", localShuttleDirectoryPath, verbose)
}

return planPath
Expand Down Expand Up @@ -193,3 +148,33 @@ func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
panic(true)
return nil, nil
}

func gitCmd(command string, dir string, printOutput bool) {
cmdOptions := go_cmd.Options{
Buffered: false,
Streaming: true,
}
execCmd := go_cmd.NewCmdOptions(cmdOptions, "sh", "-c", "cd '"+dir+"'; git "+command)
execCmd.Env = os.Environ()
go func() {
for {
select {
case line := <-execCmd.Stdout:
if printOutput {
fmt.Println("git> " + line)
}
case line := <-execCmd.Stderr:
if printOutput {
fmt.Fprintln(os.Stderr, "git> "+line)
}
}
}
}()
status := <-execCmd.Start()
for len(execCmd.Stdout) > 0 || len(execCmd.Stderr) > 0 {
time.Sleep(10 * time.Millisecond)
}
if status.Exit > 0 {
output.ExitWithErrorCode(4, fmt.Sprintf("Failed executing git command `%s` in `%s`\nExit code: %v", command, dir, status.Exit))
}
}
12 changes: 12 additions & 0 deletions pkg/output/verbose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package output

import (
"fmt"
)

// Verbose prints verbose output
func Verbose(verbose bool, msg string, args ...interface{}) {
if verbose {
fmt.Println(fmt.Sprintf(msg, args...))
}
}
13 changes: 10 additions & 3 deletions tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@ test_can_get_variable_from_repo_plan() {
}

test_fails_getting_no_repo_plan() {
assertErrorCode 3 -p examples/bad/no-repo-project ls
if [[ ! "$result" =~ "Could not clone " ]]; then
fail "Expected output to contain 'Could not clone ', but it was:\n$result"
assertErrorCode 4 -p examples/bad/no-repo-project ls
if [[ ! "$result" =~ "Failed executing git command \`clone" ]]; then
fail "Expected output to contain 'Failed executing git command \`clone', but it was:\n$result"
fi
}

test_get_a_boolean() {
result=$(./shuttle -p examples/moon-base get run-as-root 2>&1)
if [[ "$result" != "false" ]]; then
fail "Expected output to be 'false', but it was:\n$result"
fi
}

Expand Down

0 comments on commit e6cc159

Please sign in to comment.