Skip to content

Commit

Permalink
Merge pull request #153 from phase2/develop
Browse files Browse the repository at this point in the history
Release 2.1.3
  • Loading branch information
febbraro authored Mar 3, 2018
2 parents 09ff3c7 + 9bca3aa commit 9cd6f8f
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 61 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ os: linux

language: go
go:
- 1.9
- "1.10"
- tip

matrix:
Expand All @@ -13,14 +13,14 @@ matrix:
- go: tip

install:
- "go get -u github.com/golang/dep/..."
- "curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh"
- "go get -u github.com/alecthomas/gometalinter"
- "gometalinter --install --update"
- "dep ensure"

script:
- "scripts/test-go-fmt.sh"
- "gometalinter --vendor --config=gometalinter.json ./..."
- "gometalinter --vendor --deadline=60s --config=gometalinter.json ./..."
- "go run cmd/main.go"

notifications:
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ Here are a few conventions:
return cmd.Failure(message)
```

## Developer Testing Commands

You can use `rig dev:win` or `rig dev:fail` as no-op commands to observe the
effects of a success or failure without external dependencies on the local
environment or side effects from "real" commands doing their job.

## Development Environment Setup

### Developing with Docker
Expand Down
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
FROM golang:1.9-alpine
FROM golang:1.10-alpine

RUN apk add --no-cache \
ca-certificates \
curl \
git \
gcc \
libffi-dev \
Expand All @@ -12,8 +13,8 @@ RUN apk add --no-cache \
ruby-dev \
tar

RUN go get -u github.com/golang/dep/... \
&& go get -u github.com/alecthomas/gometalinter \
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
RUN go get -u github.com/alecthomas/gometalinter \
&& go get -u github.com/goreleaser/goreleaser

RUN gometalinter --install --update
Expand Down
16 changes: 8 additions & 8 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ func main() {
Usage: "Disable all desktop notifications",
EnvVar: "RIG_NOTIFY_QUIET",
},
cli.BoolFlag{
Name: "power-user",
Usage: "Switch power-user mode on for quieter help output.",
EnvVar: "RIG_POWER_USER_MODE",
},
}

app.Before = func(c *cli.Context) error {
Expand All @@ -60,6 +65,7 @@ func main() {
app.Commands = append(app.Commands, (&commands.Remove{}).Commands()...)
app.Commands = append(app.Commands, (&commands.Project{}).Commands()...)
app.Commands = append(app.Commands, (&commands.Doctor{}).Commands()...)
app.Commands = append(app.Commands, (&commands.Dev{}).Commands()...)

app.Run(os.Args)
}
24 changes: 16 additions & 8 deletions commands/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,34 @@ func (cmd *BaseCommand) Before(c *cli.Context) error {

// Success encapsulates the functionality for reporting command success
func (cmd *BaseCommand) Success(message string) error {
// Handle success messaging.
// Handle success messaging. If the spinner is running or not, this will
// output accordingly and issue a notification.
if message != "" {
cmd.out.Info(message)
util.NotifySuccess(cmd.context, message)
} else {
// If there is an active spinner wrap it up. This is not placed before the
// logging above so commands can rely on cmd.Success to set the last spinner
// status in lieu of an extraneous log entry.
cmd.out.NoSpin()
}

// If there is an active spinner wrap it up. This is not placed before the logging above so commands can rely on
// cmd.Success to set the last spinner status in lieu of an extraneous log entry.
cmd.out.NoSpin()

return nil
}

// Failure encapsulates the functionality for reporting command failure
func (cmd *BaseCommand) Failure(message string, errorName string, exitCode int) error {
// Make sure any running spinner halts.
cmd.out.NoSpin()
// If the spinner is running, output something to get closure and shut it down.
if cmd.out.Spinning {
cmd.out.Error(message)
}

// Handle error messaging.
util.NotifyError(cmd.context, message)

// Print expanded troubleshooting guidance.
if !cmd.context.GlobalBool("power-user") {
util.PrintDebugHelp(message, errorName, exitCode)
}
return cli.NewExitError(fmt.Sprintf("ERROR: %s [%s] (%d)", message, errorName, exitCode), exitCode)
}

Expand Down
50 changes: 50 additions & 0 deletions commands/dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package commands

import (
"time"

"github.com/urfave/cli"
)

// Dev is the command for setting docker config to talk to a Docker Machine
type Dev struct {
BaseCommand
}

// Commands returns the operations supported by this command
func (cmd *Dev) Commands() []cli.Command {
return []cli.Command{
{
Name: "dev:win",
Usage: "A no-op command that will always succeed.",
Before: cmd.Before,
Action: cmd.RunSucceed,
Hidden: true,
},
{
Name: "dev:fail",
Usage: "A no-op command that will always fail.",
Before: cmd.Before,
Action: cmd.RunFail,
Hidden: true,
},
}
}

// RunSucceed executes the `rig dev:succeed` command
func (cmd *Dev) RunSucceed(c *cli.Context) error {
cmd.out.Spin("Think positive...")
time.Sleep(3 * time.Second)
cmd.out.Info("We've got it.")
return cmd.Success("Positively successful!")
}

// RunFail executes the `rig dev:fail` command
func (cmd *Dev) RunFail(c *cli.Context) error {
cmd.out.Spin("Abandon all hope...")
time.Sleep(3 * time.Second)
cmd.out.Warning("Hope slipping...")
cmd.out.Spin("Is the sky painted black?")
time.Sleep(3 * time.Second)
return cmd.Failure("Hope abandoned :(", "ABANDON-HOPE", 418)
}
51 changes: 27 additions & 24 deletions commands/project_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ func (cmd *ProjectSync) Commands() []cli.Command {
Flags: []cli.Flag{
cli.IntFlag{
Name: "initial-sync-timeout",
Value: 60,
Usage: "Maximum amount of time in seconds to allow for detecting each of start of the Unison container and start of initial sync. (not needed on linux)",
Value: 120,
Usage: "Maximum amount of time in seconds to allow for detecting each of start of the Unison container and start of initial sync. If you encounter failures detecting initial sync increasing this value may help. Search for sync on http://docs.outrigger.sh/faq/troubleshooting/ (not needed on linux)",
EnvVar: "RIG_PROJECT_SYNC_TIMEOUT",
},
// Arbitrary sleep length but anything less than 3 wasn't catching
Expand Down Expand Up @@ -119,20 +119,22 @@ func (cmd *ProjectSync) RunStart(ctx *cli.Context) error {

// StartUnisonSync will create and launch the volumes and containers on systems that need/support Unison
func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, config *ProjectConfig, workingDir string) error {
cmd.out.Spin("Starting Outrigger Filesync (unison)...")

// Ensure the processes can handle a large number of watches
if err := cmd.machine.SetSysctl("fs.inotify.max_user_watches", maxWatches); err != nil {
cmd.Failure(fmt.Sprintf("Failure configuring file watches on Docker Machine: %v", err), "INOTIFY-WATCH-FAILURE", 12)
}

cmd.out.Channel.Info.Printf("Starting sync volume: %s", volumeName)
cmd.out.SpinWithVerbose("Starting sync volume: %s", volumeName)
if err := util.Command("docker", "volume", "create", volumeName).Run(); err != nil {
return cmd.Failure(fmt.Sprintf("Failed to create sync volume: %s", volumeName), "VOLUME-CREATE-FAILED", 13)
}

cmd.out.Info("Starting Unison container")
cmd.out.Info("Sync volume '%s' created", volumeName)
cmd.out.SpinWithVerbose(fmt.Sprintf("Starting sync container: %s (same name)", volumeName))
unisonMinorVersion := cmd.GetUnisonMinorVersion()

cmd.out.Channel.Verbose.Printf("Local Unison version for compatibilty: %s", unisonMinorVersion)
cmd.out.Verbose("Local Unison version for compatibilty: %s", unisonMinorVersion)
util.Command("docker", "container", "stop", volumeName).Run()
containerArgs := []string{
"container", "run", "--detach", "--rm",
Expand All @@ -151,8 +153,8 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con
if err != nil {
return cmd.Failure(err.Error(), "SYNC-INIT-FAILED", 13)
}

cmd.out.Info("Initializing sync")
cmd.out.Info("Sync container '%s' started", volumeName)
cmd.out.SpinWithVerbose("Initializing file sync...")

// Determine the location of the local Unison log file.
var logFile = fmt.Sprintf("%s.log", volumeName)
Expand All @@ -179,7 +181,7 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con
unisonArgs = append(unisonArgs, "-ignore", ignore)
}
}
cmd.out.Verbose("Unison Args: %s", strings.Join(unisonArgs[:], " "))

/* #nosec */
command := exec.Command("unison", unisonArgs...)
command.Dir = workingDir
Expand All @@ -192,12 +194,14 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con
return cmd.Failure(err.Error(), "UNISON-SYNC-FAILED", 13)
}

cmd.out.Info("Watch unison process activities in the sync log: %s", logFile)

return cmd.Success("Unison sync started successfully")
}

// SetupBindVolume will create minimal Docker Volumes for systems that have native container/volume support
func (cmd *ProjectSync) SetupBindVolume(volumeName string, workingDir string) error {
cmd.out.Info("Starting local bind volume: %s", volumeName)
cmd.out.SpinWithVerbose("Starting local bind volume: %s", volumeName)
util.Command("docker", "volume", "rm", volumeName).Run()

volumeArgs := []string{
Expand All @@ -220,6 +224,7 @@ func (cmd *ProjectSync) RunStop(ctx *cli.Context) error {
if runtime.GOOS == "linux" {
return cmd.Success("No Unison container to stop, using local bind volume")
}
cmd.out.Spin(fmt.Sprintf("Stopping Unison container"))

cmd.Config = NewProjectConfig()
if cmd.Config.NotEmpty() {
Expand All @@ -238,7 +243,7 @@ func (cmd *ProjectSync) RunStop(ctx *cli.Context) error {
return cmd.Failure(err.Error(), "SYNC-CONTAINER-FAILURE", 13)
}

return cmd.Success(fmt.Sprintf("Unison container %s stopped", volumeName))
return cmd.Success(fmt.Sprintf("Unison container '%s' stopped", volumeName))
}

// GetVolumeName will find the volume name through a variety of fall backs
Expand Down Expand Up @@ -283,7 +288,7 @@ func (cmd *ProjectSync) LoadComposeFile() (*ComposeFile, error) {
// when compiled without -cgo this executable will not use the native mac dns resolution
// which is how we have configured dnsdock to provide names for containers.
func (cmd *ProjectSync) WaitForUnisonContainer(containerName string, timeoutSeconds int) (string, error) {
cmd.out.Info("Waiting for container to start")
cmd.out.SpinWithVerbose("Sync container '%s' started , waiting for unison server process...", containerName)

var timeoutLoopSleep = time.Duration(100) * time.Millisecond
// * 10 here because we loop once every 100 ms and we want to get to seconds
Expand All @@ -303,7 +308,7 @@ func (cmd *ProjectSync) WaitForUnisonContainer(containerName string, timeoutSeco
return ip, nil
}

cmd.out.Info("Failure: %v", err)
cmd.out.SpinWithVerbose("Failure: %v", err)
time.Sleep(timeoutLoopSleep)
}

Expand All @@ -313,12 +318,12 @@ func (cmd *ProjectSync) WaitForUnisonContainer(containerName string, timeoutSeco
// WaitForSyncInit will wait for the local unison process to finish initializing
// when the log file exists and has stopped growing in size
func (cmd *ProjectSync) WaitForSyncInit(logFile string, workingDir string, timeoutSeconds int, syncWaitSeconds int) error {
cmd.out.Info("Waiting for initial sync detection")
cmd.out.SpinWithVerbose("Waiting for initial sync detection...")

// The use of os.Stat below is not subject to our working directory configuration,
// so to ensure we can stat the log file we convert it to an absolute path.
if logFilePath, err := util.AbsJoin(workingDir, logFile); err != nil {
cmd.out.Info(err.Error())
cmd.out.Error(err.Error())
} else {
// Create a temp file to cause a sync action
var tempFile = ".rig-check-sync-start"
Expand All @@ -333,32 +338,29 @@ func (cmd *ProjectSync) WaitForSyncInit(logFile string, workingDir string, timeo
var timeoutLoops = timeoutSeconds * 10
var statSleep = time.Duration(syncWaitSeconds) * time.Second
for i := 1; i <= timeoutLoops; i++ {
if i%10 == 0 {
os.Stdout.WriteString(".")
}

statInfo, err := os.Stat(logFilePath)
if err == nil {
os.Stdout.WriteString(" initial sync detected\n")
cmd.out.Info("Initial sync detected")

cmd.out.Info("Waiting for initial sync to finish")
cmd.out.SpinWithVerbose("Waiting for initial sync to finish")
// Initialize at -2 to force at least one loop
var lastSize = int64(-2)
for lastSize != statInfo.Size() {
os.Stdout.WriteString(".")
time.Sleep(statSleep)
lastSize = statInfo.Size()
if statInfo, err = os.Stat(logFilePath); err != nil {
cmd.out.Info(err.Error())
cmd.out.Error(err.Error())
lastSize = -1
}
}
os.Stdout.WriteString(" done\n")

// Remove the temp file, waiting until after sync so spurious
// failure message doesn't show in log
if err := util.RemoveFile(tempFile, workingDir); err != nil {
cmd.out.Warning("Could not remove the temporary file: %s: %s", tempFile, err.Error())
}

cmd.out.Info("File sync completed")
return nil
}

Expand All @@ -373,6 +375,7 @@ func (cmd *ProjectSync) WaitForSyncInit(logFile string, workingDir string, timeo
}
}

cmd.out.Error("Initial sync detection failed, this could indicate a need to increase the initial-sync-timeout. See rig project sync --help")
return fmt.Errorf("Failed to detect start of initial sync")
}

Expand Down
Loading

0 comments on commit 9cd6f8f

Please sign in to comment.