Skip to content

Commit a56d4a1

Browse files
authored
Merge pull request #53 from picostack/staging
Staging
2 parents da64ca9 + 1c4462a commit a56d4a1

File tree

14 files changed

+348
-71
lines changed

14 files changed

+348
-71
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
push:
55
branches: [master]
66
pull_request:
7-
branches: [master]
7+
branches: [master, staging]
88

99
jobs:
1010
test:

config/config.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,17 @@ import (
2121

2222
// State represents a desired system state
2323
type State struct {
24-
Targets task.Targets `json:"targets"`
25-
Env map[string]string `json:"env"`
24+
Targets task.Targets `json:"targets"`
25+
AuthMethods []AuthMethod `json:"auths"`
26+
Env map[string]string `json:"env"`
27+
}
28+
29+
// AuthMethod represents a method of authentication for a target
30+
type AuthMethod struct {
31+
Name string `json:"name"` // name of the auth method
32+
Path string `json:"path"` // path within the secret store
33+
UserKey string `json:"user_key"` // key for username
34+
PassKey string `json:"pass_key"` // key for password
2635
}
2736

2837
// ConfigFromDirectory searches a directory for configuration files and
@@ -72,6 +81,7 @@ func (cb *configBuilder) construct(hostname string) (err error) {
7281
cb.vm.Run(`'use strict';
7382
var STATE = {
7483
targets: [],
84+
auths: [],
7585
env: {}
7686
};
7787
@@ -90,6 +100,15 @@ function T(t) {
90100
function E(k, v) {
91101
STATE.env[k] = v
92102
}
103+
104+
function A(a) {
105+
if(a.name === undefined) { throw "auth name undefined"; }
106+
if(a.path === undefined) { throw "auth path undefined"; }
107+
if(a.user_key === undefined) { throw "auth user_key undefined"; }
108+
if(a.pass_key === undefined) { throw "auth pass_key undefined"; }
109+
110+
STATE.auths.push(a);
111+
}
93112
`)
94113

95114
cb.vm.Set("HOSTNAME", hostname) //nolint:errcheck

executor/cmd.go

+55-12
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,24 @@ var _ Executor = &CommandExecutor{}
1212

1313
// CommandExecutor handles command invocation targets
1414
type CommandExecutor struct {
15-
secrets secret.Store
15+
secrets secret.Store
16+
passEnvironment bool // pass the Pico process environment to children
17+
configSecretPath string // path to global secrets to pass to children
18+
configSecretPrefix string // only pass secrets with this prefix, usually GLOBAL_
1619
}
1720

1821
// NewCommandExecutor creates a new CommandExecutor
19-
func NewCommandExecutor(secrets secret.Store) CommandExecutor {
22+
func NewCommandExecutor(
23+
secrets secret.Store,
24+
passEnvironment bool,
25+
configSecretPath string,
26+
configSecretPrefix string,
27+
) CommandExecutor {
2028
return CommandExecutor{
21-
secrets: secrets,
29+
secrets: secrets,
30+
passEnvironment: passEnvironment,
31+
configSecretPath: configSecretPath,
32+
configSecretPrefix: configSecretPrefix,
2233
}
2334
}
2435

@@ -34,34 +45,66 @@ func (e *CommandExecutor) Subscribe(bus chan task.ExecutionTask) {
3445
}
3546
}
3647

37-
func (e *CommandExecutor) execute(
38-
target task.Target,
48+
type exec struct {
49+
path string
50+
env map[string]string
51+
shutdown bool
52+
passEnvironment bool
53+
}
54+
55+
func (e *CommandExecutor) prepare(
56+
name string,
3957
path string,
4058
shutdown bool,
4159
execEnv map[string]string,
42-
) (err error) {
43-
secrets, err := e.secrets.GetSecretsForTarget(target.Name)
60+
) (exec, error) {
61+
// get global secrets from the Pico config path in the secret store.
62+
// only secrets with the prefix are retrieved.
63+
global, err := secret.GetPrefixedSecrets(e.secrets, e.configSecretPath, e.configSecretPrefix)
4464
if err != nil {
45-
return errors.Wrap(err, "failed to get secrets for target")
65+
return exec{}, errors.Wrap(err, "failed to get global secrets for target")
66+
}
67+
68+
secrets, err := e.secrets.GetSecretsForTarget(name)
69+
if err != nil {
70+
return exec{}, errors.Wrap(err, "failed to get secrets for target")
4671
}
4772

4873
env := make(map[string]string)
4974

50-
// merge execution environment with secrets
75+
// merge execution environment with secrets in the following order:
76+
// globals first, then execution environment, then per-target secrets
77+
for k, v := range global {
78+
env[k] = v
79+
}
5180
for k, v := range execEnv {
5281
env[k] = v
5382
}
5483
for k, v := range secrets {
5584
env[k] = v
5685
}
5786

87+
return exec{path, env, shutdown, e.passEnvironment}, nil
88+
}
89+
90+
func (e *CommandExecutor) execute(
91+
target task.Target,
92+
path string,
93+
shutdown bool,
94+
execEnv map[string]string,
95+
) (err error) {
96+
ex, err := e.prepare(target.Name, path, shutdown, execEnv)
97+
if err != nil {
98+
return err
99+
}
100+
58101
zap.L().Debug("executing with secrets",
59102
zap.String("target", target.Name),
60103
zap.Strings("cmd", target.Up),
61104
zap.String("url", target.RepoURL),
62105
zap.String("dir", path),
63-
zap.Int("env", len(env)),
64-
zap.Int("secrets", len(secrets)))
106+
zap.Any("env", ex.env),
107+
zap.Bool("passthrough", e.passEnvironment))
65108

66-
return target.Execute(path, env, shutdown)
109+
return target.Execute(ex.path, ex.env, ex.shutdown, ex.passEnvironment)
67110
}

executor/cmd_test.go

+61-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package executor_test
1+
package executor
22

33
import (
44
"os"
@@ -7,9 +7,9 @@ import (
77

88
"golang.org/x/sync/errgroup"
99

10-
"github.com/picostack/pico/executor"
1110
"github.com/picostack/pico/secret/memory"
1211
"github.com/picostack/pico/task"
12+
"github.com/stretchr/testify/assert"
1313

1414
_ "github.com/picostack/pico/logger"
1515
)
@@ -20,11 +20,13 @@ func TestMain(m *testing.M) {
2020
}
2121

2222
func TestCommandExecutor(t *testing.T) {
23-
ce := executor.NewCommandExecutor(&memory.MemorySecrets{
24-
Secrets: map[string]string{
25-
"SOME_SECRET": "123",
23+
ce := NewCommandExecutor(&memory.MemorySecrets{
24+
Secrets: map[string]map[string]string{
25+
"test": map[string]string{
26+
"SOME_SECRET": "123",
27+
},
2628
},
27-
})
29+
}, false, "pico", "GLOBAL_")
2830
bus := make(chan task.ExecutionTask)
2931

3032
g := errgroup.Group{}
@@ -55,3 +57,56 @@ func TestCommandExecutor(t *testing.T) {
5557

5658
os.RemoveAll(".test/.git")
5759
}
60+
61+
func TestCommandPrepareWithoutPassthrough(t *testing.T) {
62+
ce := NewCommandExecutor(&memory.MemorySecrets{
63+
Secrets: map[string]map[string]string{
64+
"test": map[string]string{
65+
"SOME_SECRET": "123",
66+
},
67+
},
68+
}, false, "pico", "GLOBAL_")
69+
70+
ex, err := ce.prepare("test", "./", false, map[string]string{
71+
"DATA_DIR": "/data/shared",
72+
})
73+
assert.NoError(t, err)
74+
assert.Equal(t, exec{
75+
path: "./",
76+
env: map[string]string{
77+
"SOME_SECRET": "123",
78+
"DATA_DIR": "/data/shared",
79+
},
80+
shutdown: false,
81+
passEnvironment: false,
82+
}, ex)
83+
}
84+
85+
func TestCommandPrepareWithGlobal(t *testing.T) {
86+
ce := NewCommandExecutor(&memory.MemorySecrets{
87+
Secrets: map[string]map[string]string{
88+
"test": map[string]string{
89+
"SOME_SECRET": "123",
90+
},
91+
"pico": map[string]string{
92+
"GLOBAL_SECRET": "456",
93+
"IGNORE": "this",
94+
},
95+
},
96+
}, false, "pico", "GLOBAL_")
97+
98+
ex, err := ce.prepare("test", "./", false, map[string]string{
99+
"DATA_DIR": "/data/shared",
100+
})
101+
assert.NoError(t, err)
102+
assert.Equal(t, exec{
103+
path: "./",
104+
env: map[string]string{
105+
"SOME_SECRET": "123",
106+
"SECRET": "456",
107+
"DATA_DIR": "/data/shared",
108+
},
109+
shutdown: false,
110+
passEnvironment: false,
111+
}, ex)
112+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/picostack/pico
33
go 1.13
44

55
require (
6-
github.com/Southclaws/gitwatch v1.3.2
6+
github.com/Southclaws/gitwatch v1.3.3
77
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
88
github.com/eapache/go-resiliency v1.2.0
99
github.com/frankban/quicktest v1.4.1 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ github.com/Southclaws/gitwatch v1.3.1 h1:4XtiujsnxHKSKze3Tb5sWwTdBxSVW/JLbK54ruJ
99
github.com/Southclaws/gitwatch v1.3.1/go.mod h1:xCudUiwWxkDYZ69cEhlTwAKIzbG1OpnA/s/pjPIW6gU=
1010
github.com/Southclaws/gitwatch v1.3.2 h1:zmt571n8ItXgkRJPyCFtFjcymvsFOGcm7JnHNpFDP+8=
1111
github.com/Southclaws/gitwatch v1.3.2/go.mod h1:xCudUiwWxkDYZ69cEhlTwAKIzbG1OpnA/s/pjPIW6gU=
12+
github.com/Southclaws/gitwatch v1.3.3 h1:w5AI9IcMEVqb6cPyDjM9tvOI4r26m4UHAl5BVEvgKac=
13+
github.com/Southclaws/gitwatch v1.3.3/go.mod h1:xCudUiwWxkDYZ69cEhlTwAKIzbG1OpnA/s/pjPIW6gU=
1214
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
1315
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
1416
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=

main.go

+21-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
_ "github.com/picostack/pico/logger"
1515
"github.com/picostack/pico/service"
16+
"github.com/picostack/pico/task"
1617
)
1718

1819
var version = "master"
@@ -38,14 +39,18 @@ this repository has new commits, Pico will automatically reconfigure.`,
3839
Usage: "argument `target` specifies Git repository for configuration.",
3940
ArgsUsage: "target",
4041
Flags: []cli.Flag{
42+
cli.StringFlag{Name: "git-username", EnvVar: "GIT_USERNAME"},
43+
cli.StringFlag{Name: "git-password", EnvVar: "GIT_PASSWORD"},
4144
cli.StringFlag{Name: "hostname", EnvVar: "HOSTNAME"},
4245
cli.StringFlag{Name: "directory", EnvVar: "DIRECTORY", Value: "./cache/"},
43-
cli.BoolFlag{Name: "no-ssh", EnvVar: "NO_SSH"},
46+
cli.DurationFlag{Name: "pass-env", EnvVar: "PASS_ENV"},
47+
cli.BoolFlag{Name: "ssh", EnvVar: "SSH"},
4448
cli.DurationFlag{Name: "check-interval", EnvVar: "CHECK_INTERVAL", Value: time.Second * 10},
4549
cli.StringFlag{Name: "vault-addr", EnvVar: "VAULT_ADDR"},
4650
cli.StringFlag{Name: "vault-token", EnvVar: "VAULT_TOKEN"},
4751
cli.StringFlag{Name: "vault-path", EnvVar: "VAULT_PATH", Value: "/secret"},
4852
cli.DurationFlag{Name: "vault-renew-interval", EnvVar: "VAULT_RENEW_INTERVAL", Value: time.Hour * 24},
53+
cli.StringFlag{Name: "vault-config-path", EnvVar: "VAULT_CONFIG_PATH", Value: "pico"},
4954
},
5055
Action: func(c *cli.Context) (err error) {
5156
if !c.Args().Present() {
@@ -68,15 +73,21 @@ this repository has new commits, Pico will automatically reconfigure.`,
6873
zap.L().Debug("initialising service")
6974

7075
svc, err := service.Initialise(service.Config{
71-
Target: c.Args().First(),
72-
Hostname: hostname,
73-
Directory: c.String("directory"),
74-
NoSSH: c.Bool("no-ssh"),
75-
CheckInterval: c.Duration("check-interval"),
76-
VaultAddress: c.String("vault-addr"),
77-
VaultToken: c.String("vault-token"),
78-
VaultPath: c.String("vault-path"),
79-
VaultRenewal: c.Duration("vault-renew-interval"),
76+
Target: task.Repo{
77+
URL: c.Args().First(),
78+
User: c.String("git-username"),
79+
Pass: c.String("git-password"),
80+
},
81+
Hostname: hostname,
82+
Directory: c.String("directory"),
83+
PassEnvironment: c.Bool("pass-env"),
84+
SSH: c.Bool("ssh"),
85+
CheckInterval: c.Duration("check-interval"),
86+
VaultAddress: c.String("vault-addr"),
87+
VaultToken: c.String("vault-token"),
88+
VaultPath: c.String("vault-path"),
89+
VaultRenewal: c.Duration("vault-renew-interval"),
90+
VaultConfig: c.String("vault-config-path"),
8091
})
8192
if err != nil {
8293
return errors.Wrap(err, "failed to initialise")

reconfigurer/git.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type GitProvider struct {
2323
hostname string
2424
configRepo string
2525
checkInterval time.Duration
26-
ssh transport.AuthMethod
26+
authMethod transport.AuthMethod
2727

2828
configWatcher *gitwatch.Session
2929
}
@@ -34,14 +34,14 @@ func New(
3434
hostname string,
3535
configRepo string,
3636
checkInterval time.Duration,
37-
ssh transport.AuthMethod,
37+
authMethod transport.AuthMethod,
3838
) *GitProvider {
3939
return &GitProvider{
4040
directory: directory,
4141
hostname: hostname,
4242
configRepo: configRepo,
4343
checkInterval: checkInterval,
44-
ssh: ssh,
44+
authMethod: authMethod,
4545
}
4646
}
4747

@@ -104,7 +104,7 @@ func (p *GitProvider) watchConfig() (err error) {
104104
[]gitwatch.Repository{{URL: p.configRepo}},
105105
p.checkInterval,
106106
p.directory,
107-
p.ssh,
107+
p.authMethod,
108108
false)
109109
if err != nil {
110110
return errors.Wrap(err, "failed to watch config target")

secret/memory/memory.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import (
66

77
// MemorySecrets implements a simple in-memory secret.Store for testing
88
type MemorySecrets struct {
9-
Secrets map[string]string
9+
Secrets map[string]map[string]string
1010
}
1111

1212
var _ secret.Store = &MemorySecrets{}
1313

1414
// GetSecretsForTarget implements secret.Store
1515
func (v *MemorySecrets) GetSecretsForTarget(name string) (map[string]string, error) {
16-
return v.Secrets, nil
16+
table, ok := v.Secrets[name]
17+
if !ok {
18+
return nil, nil
19+
}
20+
return table, nil
1721
}

0 commit comments

Comments
 (0)