Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SUPFILE_DIR as default environment variable #176

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 20 additions & 48 deletions cmd/sup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func cmdUsage(conf *sup.Supfile) {
func parseArgs(conf *sup.Supfile) (*sup.Network, []*sup.Command, error) {
var commands []*sup.Command

// In case of the conf.Env needs an initialization
if conf.Env == nil {
conf.Env = make(sup.EnvList, 0)
}

args := flag.Args()
if len(args) < 1 {
networkUsage(conf)
Expand All @@ -122,26 +127,14 @@ func parseArgs(conf *sup.Supfile) (*sup.Network, []*sup.Command, error) {
return nil, nil, ErrUnknownNetwork
}

// Parse CLI --env flag env vars, override values defined in Network env.
for _, env := range envVars {
if len(env) == 0 {
continue
}
i := strings.Index(env, "=")
if i < 0 {
if len(env) > 0 {
network.Env.Set(env, "")
}
continue
}
network.Env.Set(env[:i], env[i+1:])
}

hosts, err := network.ParseInventory()
hosts, err := network.ParseInventory(conf.Env)
if err != nil {
return nil, nil, err
}
network.Hosts = append(network.Hosts, hosts...)
if network.Env == nil {
network.Env = make(sup.EnvList, 0)
}

// Does the <network> have at least one host?
if len(network.Hosts) == 0 {
Expand All @@ -155,27 +148,9 @@ func parseArgs(conf *sup.Supfile) (*sup.Network, []*sup.Command, error) {
return nil, nil, ErrUsage
}

// In case of the network.Env needs an initialization
if network.Env == nil {
network.Env = make(sup.EnvList, 0)
}

// Add default env variable with current network
network.Env.Set("SUP_NETWORK", args[0])

// Add default nonce
network.Env.Set("SUP_TIME", time.Now().UTC().Format(time.RFC3339))
if os.Getenv("SUP_TIME") != "" {
network.Env.Set("SUP_TIME", os.Getenv("SUP_TIME"))
}

// Add user
if os.Getenv("SUP_USER") != "" {
network.Env.Set("SUP_USER", os.Getenv("SUP_USER"))
} else {
network.Env.Set("SUP_USER", os.Getenv("USER"))
}

for _, cmd := range args[1:] {
// Target?
target, isTarget := conf.Targets.Get(cmd)
Expand Down Expand Up @@ -248,7 +223,14 @@ func main() {
os.Exit(1)
}
}
conf, err := sup.NewSupfile(data)
conf, err := sup.NewSupfile(data,
// SUPFILE_DIR might change as sup invocations are chained.
sup.WithEnv("SUPFILE_DIR", filepath.Dir(supfile)),
// Add default nonce, but inherit from previous invocation.
sup.WithInheritEnv("SUP_TIME", time.Now().UTC().Format(time.RFC3339)),
// Add user, but inherit from previous invocation.
sup.WithInheritEnv("SUP_USER", os.Getenv("USER")),
)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
Expand Down Expand Up @@ -331,15 +313,6 @@ func main() {
}
}

var vars sup.EnvList
for _, val := range append(conf.Env, network.Env...) {
vars.Set(val.Key, val.Value)
}
if err := vars.ResolveValues(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

// Parse CLI --env flag env vars, define $SUP_ENV and override values defined in Supfile.
var cliVars sup.EnvList
for _, env := range envVars {
Expand All @@ -349,11 +322,10 @@ func main() {
i := strings.Index(env, "=")
if i < 0 {
if len(env) > 0 {
vars.Set(env, "")
cliVars.Set(env, "")
}
continue
}
vars.Set(env[:i], env[i+1:])
cliVars.Set(env[:i], env[i+1:])
}

Expand All @@ -363,7 +335,7 @@ func main() {
for _, v := range cliVars {
supEnv += fmt.Sprintf(" -e %v=%q", v.Key, v.Value)
}
vars.Set("SUP_ENV", strings.TrimSpace(supEnv))
cliVars.Set("SUP_ENV", strings.TrimSpace(supEnv))

// Create new Stackup app.
app, err := sup.New(conf)
Expand All @@ -375,7 +347,7 @@ func main() {
app.Prefix(!disablePrefix)

// Run all the commands in the given network.
err = app.Run(network, vars, commands...)
err = app.Run(network, cliVars, commands...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
Expand Down
52 changes: 52 additions & 0 deletions envlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package sup

import (
"reflect"
"testing"

"gopkg.in/yaml.v2"
)

func TestEnvListUnmarshalYAML(t *testing.T) {
type holder struct {
Env EnvList `yaml:"env"`
}

testCases := []struct {
input string
expect holder
}{
{

input: `
env:
MY_KEY: abc123
`,
expect: holder{
Env: EnvList{
&EnvVar{Key: "MY_KEY", Value: "abc123"},
},
},
},
{

input: `
env:
MY_KEY: $(echo abc123)
`,
expect: holder{
Env: EnvList{
&EnvVar{Key: "MY_KEY", Value: "abc123"},
},
},
},
}

for _, tc := range testCases {
h := holder{}
yaml.Unmarshal([]byte(tc.input), &h)
if !reflect.DeepEqual(h, tc.expect) {
t.Errorf("Unmarshalling yaml did not produce the expected result. Got:\n%#v\nExpected: %#v\n", h, tc.expect)
}
}
}
8 changes: 7 additions & 1 deletion example/Supfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ env:
networks:
# Groups of hosts
local:
env:
SUP_LOCAL: yessir
hosts:
- localhost

Expand Down Expand Up @@ -53,7 +55,7 @@ commands:
upload:
- src: ./
dst: /tmp/$IMAGE
script: ./scripts/docker-build.sh
script: $SUPFILE_DIR/scripts/docker-build.sh
once: true

pull:
Expand Down Expand Up @@ -126,6 +128,10 @@ commands:
curl -X POST --data-urlencode 'payload={"channel": "#_team_", "text": "['$SUP_NETWORK'] '$SUP_USER' deployed '$NAME'"}' \
https://hooks.slack.com/services/X/Y/Z

env:
desc: Print environment
local: env

bash:
desc: Interactive shell on all hosts
stdin: true
Expand Down
16 changes: 1 addition & 15 deletions localhost.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import (
"os"
"os/exec"
"os/user"

"github.com/pkg/errors"
)

// Client is a wrapper over the SSH connection/sessions.
// LocalhostClient is a wrapper over the SSH connection/sessions.
type LocalhostClient struct {
cmd *exec.Cmd
user string
Expand Down Expand Up @@ -105,15 +103,3 @@ func (c *LocalhostClient) WriteClose() error {
func (c *LocalhostClient) Signal(sig os.Signal) error {
return c.cmd.Process.Signal(sig)
}

func ResolveLocalPath(cwd, path, env string) (string, error) {
// Check if file exists first. Use bash to resolve $ENV_VARs.
cmd := exec.Command("bash", "-c", env+"echo -n "+path)
cmd.Dir = cwd
resolvedFilename, err := cmd.Output()
if err != nil {
return "", errors.Wrap(err, "resolving path failed")
}

return string(resolvedFilename), nil
}
10 changes: 5 additions & 5 deletions ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"golang.org/x/crypto/ssh/agent"
)

// Client is a wrapper over the SSH connection/sessions.
// SSHClient is a wrapper over the SSH connection/sessions.
type SSHClient struct {
conn *ssh.Client
sess *ssh.Session
Expand Down Expand Up @@ -219,16 +219,16 @@ func (c *SSHClient) Wait() error {
}

// DialThrough will create a new connection from the ssh server sc is connected to. DialThrough is an SSHDialer.
func (sc *SSHClient) DialThrough(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
conn, err := sc.conn.Dial(net, addr)
func (c *SSHClient) DialThrough(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
conn, err := c.conn.Dial(net, addr)
if err != nil {
return nil, err
}
c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
sc, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
if err != nil {
return nil, err
}
return ssh.NewClient(c, chans, reqs), nil
return ssh.NewClient(sc, chans, reqs), nil

}

Expand Down
16 changes: 11 additions & 5 deletions sup.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ func New(conf *Supfile) (*Stackup, error) {
// Run runs set of commands on multiple hosts defined by network sequentially.
// TODO: This megamoth method needs a big refactor and should be split
// to multiple smaller methods.
func (sup *Stackup) Run(network *Network, envVars EnvList, commands ...*Command) error {
func (sup *Stackup) Run(network *Network, cliVars EnvList, commands ...*Command) error {
if len(commands) == 0 {
return errors.New("no commands to be run")
}

env := envVars.AsExport()
// Order is important here.
// Least specific (most general) env vars first,
// then the network specific ones and finally
// the command line vars.
// Semantics are last-write-wins.
env := append(sup.conf.Env, network.Env...)
env = append(env, cliVars...)

// Create clients for every host (either SSH or Localhost).
var bastion *SSHClient
Expand All @@ -58,7 +64,7 @@ func (sup *Stackup) Run(network *Network, envVars EnvList, commands ...*Command)
// Localhost client.
if host == "localhost" {
local := &LocalhostClient{
env: env + `export SUP_HOST="` + host + `";`,
env: env.AsExport() + `export SUP_HOST="` + host + `";`,
}
if err := local.Connect(host); err != nil {
errCh <- errors.Wrap(err, "connecting to localhost failed")
Expand All @@ -70,7 +76,7 @@ func (sup *Stackup) Run(network *Network, envVars EnvList, commands ...*Command)

// SSH client.
remote := &SSHClient{
env: env + `export SUP_HOST="` + host + `";`,
env: env.AsExport() + `export SUP_HOST="` + host + `";`,
user: network.User,
color: Colors[i%len(Colors)],
}
Expand Down Expand Up @@ -112,7 +118,7 @@ func (sup *Stackup) Run(network *Network, envVars EnvList, commands ...*Command)
// Run command or run multiple commands defined by target sequentially.
for _, cmd := range commands {
// Translate command into task(s).
tasks, err := sup.createTasks(cmd, clients, env)
tasks, err := sup.createTasks(cmd, clients)
if err != nil {
return errors.Wrap(err, "creating task failed")
}
Expand Down
Loading