cli: allow terraform options on rack install/update (#85)

This commit is contained in:
David Dollar 2020-01-28 13:43:12 -05:00 committed by David Dollar
parent 9dbc425d88
commit 5192c09625
No known key found for this signature in database
GPG Key ID: AFAF263FB45B2124
7 changed files with 246 additions and 27 deletions

View File

@ -404,7 +404,6 @@ func switchRack(c *stdcli.Context, name string) error {
func tag(name, value string) string {
return fmt.Sprintf("<%s>%s</%s>", name, value, name)
}
func terraform(c *stdcli.Context, dir string, env map[string]string, args ...string) error {
wd, err := os.Getwd()
if err != nil {
@ -439,7 +438,45 @@ func terraformEnv(provider string) (map[string]string, error) {
}
}
func terraformVars(provider string) (map[string]string, error) {
func terraformOptionVars(dir string, args []string) (map[string]string, error) {
vars := map[string]string{}
vf := filepath.Join(dir, "vars.json")
if _, err := os.Stat(vf); !os.IsNotExist(err) {
data, err := ioutil.ReadFile(vf)
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &vars); err != nil {
return nil, err
}
}
for _, arg := range args {
parts := strings.Split(arg, "=")
k := strings.TrimSpace(parts[0])
if v := strings.TrimSpace(parts[1]); v != "" {
vars[k] = v
} else {
delete(vars, k)
}
}
data, err := json.MarshalIndent(vars, "", " ")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(vf, data, 0600); err != nil {
return nil, err
}
return vars, nil
}
func terraformProviderVars(provider string) (map[string]string, error) {
switch provider {
case "do":
env, err := requireEnv("DIGITALOCEAN_ACCESS_ID", "DIGITALOCEAN_SECRET_KEY", "DIGITALOCEAN_TOKEN")
@ -449,11 +486,28 @@ func terraformVars(provider string) (map[string]string, error) {
vars := map[string]string{
"access_id": env["DIGITALOCEAN_ACCESS_ID"],
"secret_key": env["DIGITALOCEAN_SECRET_KEY"],
"release": "",
"token": env["DIGITALOCEAN_TOKEN"],
}
return vars, nil
default:
return map[string]string{}, nil
vars := map[string]string{
"release": "",
}
return vars, nil
}
}
func terraformTemplateHelpers() template.FuncMap {
return template.FuncMap{
"keys": func(h map[string]string) []string {
ks := []string{}
for k := range h {
ks = append(ks, k)
}
sort.Strings(ks)
return ks
},
}
}
@ -464,14 +518,14 @@ func terraformWriteTemplate(filename string, params map[string]interface{}) erro
params["Source"] = fmt.Sprintf("github.com/convox/convox//terraform/system/%s", params["Provider"])
}
t, err := template.New("main").Parse(`
t, err := template.New("main").Funcs(terraformTemplateHelpers()).Parse(`
module "system" {
source = "{{.Source}}"
name = "{{.Name}}"
release = "{{.Release}}"
{{- range $k, $v := .Vars }}
{{$k}} = "{{$v}}"
name = "{{.Name}}"
{{- range (keys .Vars) }}
{{.}} = "{{index $.Vars .}}"
{{- end }}
}

View File

@ -22,8 +22,8 @@ func init() {
})
registerWithoutProvider("rack install", "install a new rack", RackInstall, stdcli.CommandOptions{
Usage: "<provider> <name>",
Validate: stdcli.Args(2),
Usage: "<provider> <name> [option=value]...",
Validate: stdcli.ArgsMin(2),
})
register("rack logs", "get logs for the rack", RackLogs, stdcli.CommandOptions{
@ -67,8 +67,8 @@ func init() {
})
registerWithoutProvider("rack update", "update a rack", RackUpdate, stdcli.CommandOptions{
Usage: "<name>",
Validate: stdcli.Args(1),
Usage: "<name> [option=value]...",
Validate: stdcli.ArgsMin(1),
})
}
@ -110,11 +110,6 @@ func RackInstall(rack sdk.Interface, c *stdcli.Context) error {
return err
}
vars, err := terraformVars(provider)
if err != nil {
return err
}
dir, err := c.SettingDirectory(fmt.Sprintf("racks/%s", name))
if err != nil {
return err
@ -124,6 +119,20 @@ func RackInstall(rack sdk.Interface, c *stdcli.Context) error {
return err
}
vars, err := terraformProviderVars(provider)
if err != nil {
return err
}
ov, err := terraformOptionVars(dir, c.Args[2:])
if err != nil {
return err
}
for k, v := range ov {
vars[k] = v
}
tf := filepath.Join(dir, "main.tf")
if _, err := os.Stat(tf); !os.IsNotExist(err) {
@ -133,7 +142,6 @@ func RackInstall(rack sdk.Interface, c *stdcli.Context) error {
params := map[string]interface{}{
"Name": name,
"Provider": provider,
"Release": "",
"Vars": vars,
}
@ -367,27 +375,35 @@ func RackUpdate(rack sdk.Interface, c *stdcli.Context) error {
return rackUpdateRemote(c, name)
}
dir, err := c.SettingDirectory(fmt.Sprintf("racks/%s", name))
if err != nil {
return err
}
env, err := terraformEnv(r.Provider)
if err != nil {
return err
}
vars, err := terraformVars(r.Provider)
vars, err := terraformProviderVars(r.Provider)
if err != nil {
return err
}
dir, err := c.SettingDirectory(fmt.Sprintf("racks/%s", name))
ov, err := terraformOptionVars(dir, c.Args[1:])
if err != nil {
return err
}
for k, v := range ov {
vars[k] = v
}
tf := filepath.Join(dir, "main.tf")
params := map[string]interface{}{
"Name": name,
"Provider": r.Provider,
"Release": "",
"Vars": vars,
}
@ -403,7 +419,7 @@ func RackUpdate(rack sdk.Interface, c *stdcli.Context) error {
return err
}
return c.OK()
return nil
}
func rackUninstallRemote(c *stdcli.Context, name string) error {

View File

@ -73,7 +73,46 @@ func TestRackInstall(t *testing.T) {
tfdata, err := ioutil.ReadFile(tf)
require.NoError(t, err)
testdata, err := ioutil.ReadFile("testdata/terraform.local.tf")
testdata, err := ioutil.ReadFile("testdata/terraform/dev1.tf")
require.NoError(t, err)
require.Equal(t, strings.Trim(string(tfdata), "\n"), strings.Trim(string(testdata), "\n"))
res, err = testExecute(e, "switch", nil)
require.NoError(t, err)
require.Equal(t, 0, res.Code)
res.RequireStderr(t, []string{""})
res.RequireStdout(t, []string{"dev1"})
me.AssertExpectations(t)
})
}
func TestRackInstallArgs(t *testing.T) {
testClientWait(t, 50*time.Millisecond, func(e *cli.Engine, i *mocksdk.Interface) {
me := &mockstdcli.Executor{}
me.On("Terminal", "terraform", "init").Return(nil)
me.On("Terminal", "terraform", "apply", "-auto-approve").Return(nil)
e.Executor = me
res, err := testExecute(e, "rack install local dev1 foo=bar baz=qux", nil)
require.NoError(t, err)
require.Equal(t, 0, res.Code)
res.RequireStderr(t, []string{""})
res.RequireStdout(t, []string{""})
dir := filepath.Join(e.Settings, "racks", "dev1")
tf := filepath.Join(dir, "main.tf")
_, err = os.Stat(dir)
require.NoError(t, err)
_, err = os.Stat(tf)
require.NoError(t, err)
tfdata, err := ioutil.ReadFile(tf)
require.NoError(t, err)
testdata, err := ioutil.ReadFile("testdata/terraform/dev1.args.tf")
require.NoError(t, err)
require.Equal(t, strings.Trim(string(tfdata), "\n"), strings.Trim(string(testdata), "\n"))
@ -417,9 +456,71 @@ func TestRackUpdate(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 0, res.Code)
res.RequireStderr(t, []string{""})
res.RequireStdout(t, []string{
"OK",
})
res.RequireStdout(t, []string{""})
dir := filepath.Join(e.Settings, "racks", "dev1")
tf := filepath.Join(dir, "main.tf")
_, err = os.Stat(dir)
require.NoError(t, err)
_, err = os.Stat(tf)
require.NoError(t, err)
tfdata, err := ioutil.ReadFile(tf)
require.NoError(t, err)
testdata, err := ioutil.ReadFile("testdata/terraform/dev1.tf")
require.NoError(t, err)
require.Equal(t, strings.Trim(string(tfdata), "\n"), strings.Trim(string(testdata), "\n"))
me.AssertExpectations(t)
})
}
func TestRackUpdateArgs(t *testing.T) {
testClientWait(t, 50*time.Millisecond, func(e *cli.Engine, i *mocksdk.Interface) {
require.NoError(t, testLocalRack(e, "dev1", "local", "https://host1"))
me := e.Executor.(*mockstdcli.Executor)
me.On("Terminal", "terraform", "init").Return(nil).Once()
me.On("Terminal", "terraform", "apply", "-auto-approve").Return(nil).Once()
res, err := testExecute(e, "rack install local dev2 foo=bar baz=qux", nil)
require.NoError(t, err)
require.Equal(t, 0, res.Code)
res.RequireStderr(t, []string{""})
res.RequireStdout(t, []string{""})
tf := filepath.Join(e.Settings, "racks", "dev2", "main.tf")
fmt.Printf("tf: %+v\n", tf)
tfdata, err := ioutil.ReadFile(tf)
require.NoError(t, err)
testdata, err := ioutil.ReadFile("testdata/terraform/dev2.args.tf")
require.NoError(t, err)
require.Equal(t, strings.Trim(string(testdata), "\n"), strings.Trim(string(tfdata), "\n"))
me.On("Terminal", "terraform", "init", "-upgrade").Return(nil).Once()
me.On("Terminal", "terraform", "apply", "-auto-approve").Return(nil).Once()
res, err = testExecute(e, "rack update dev2 foo= other=side", nil)
require.NoError(t, err)
require.Equal(t, 0, res.Code)
res.RequireStderr(t, []string{""})
res.RequireStdout(t, []string{""})
tfdata2, err := ioutil.ReadFile(tf)
require.NoError(t, err)
testdata2, err := ioutil.ReadFile("testdata/terraform/dev2.update.tf")
require.NoError(t, err)
require.Equal(t, strings.Trim(string(tfdata2), "\n"), strings.Trim(string(testdata2), "\n"))
me.AssertExpectations(t)
})

16
pkg/cli/testdata/terraform/dev1.args.tf vendored Normal file
View File

@ -0,0 +1,16 @@
module "system" {
source = "github.com/convox/convox//terraform/system/local"
name = "dev1"
baz = "qux"
foo = "bar"
release = ""
}
output "api" {
value = module.system.api
}
output "provider" {
value = "local"
}

View File

@ -1,7 +1,7 @@
module "system" {
source = "github.com/convox/convox//terraform/system/local"
name = "dev1"
name = "dev1"
release = ""
}

16
pkg/cli/testdata/terraform/dev2.args.tf vendored Normal file
View File

@ -0,0 +1,16 @@
module "system" {
source = "github.com/convox/convox//terraform/system/local"
name = "dev2"
baz = "qux"
foo = "bar"
release = ""
}
output "api" {
value = module.system.api
}
output "provider" {
value = "local"
}

View File

@ -0,0 +1,16 @@
module "system" {
source = "github.com/convox/convox//terraform/system/local"
name = "dev2"
baz = "qux"
other = "side"
release = ""
}
output "api" {
value = module.system.api
}
output "provider" {
value = "local"
}