From f623d90f3ffffbd371225cccba06ed5b5f7f8810 Mon Sep 17 00:00:00 2001 From: emicklei_kramphub Date: Thu, 23 Aug 2018 16:12:41 +0200 Subject: [PATCH] run all commands at once using temporary shell script --- README.md | 5 +++-- main.go | 2 +- migration.go | 32 +++++++++++++++++++++++--------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b69bb30..81a4fc9 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ Each change is a single YAML file with one or more shell commands that change in - gcloud iam service-accounts delete loadrunner A change must have at least a `do` section and optionally an `undo` section. -The `do` section typically has a list of gcloud commands that create resources. Each line will be executed as a shell command so any available tool can be used. +The `do` section typically has a list of gcloud commands that create resources but any available tool can be used. +All lines will be executed at once using a single temporary shell script so you can use shell variables to simplify each section. The `undo` section typically has an ordered list of gcloud commands that deletes the same resources (in reverse order if relevant). -Each command can use the following environment variables: `$PROJECT`,`$REGION`,`$ZONE` and any additional environment variables populated from the target configuration (see `env` section in the configuration below). +Each command in each section can use the following environment variables: `$PROJECT`,`$REGION`,`$ZONE` and any additional environment variables populated from the target configuration (see `env` section in the configuration below). Information about the last applied change to a project is stored as a Google Storage Bucket object. diff --git a/main.go b/main.go index 3b352e3..0d8f709 100644 --- a/main.go +++ b/main.go @@ -62,7 +62,7 @@ func newApp() *cli.App { }, { Name: "up", - Usage: "Runs the do section of all pending migrations in order, one after the other.", + Usage: "Runs the do section of all pending migrations in order, one after the other. If a migration file is specified then stop after applying that one.", Action: func(c *cli.Context) error { defer started(c, "up = apply pending migrations")() return cmdMigrationsUp(c) diff --git a/migration.go b/migration.go index 4c38fca..4302ad3 100644 --- a/migration.go +++ b/migration.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/exec" + "path" "path/filepath" "sort" "strings" @@ -60,20 +61,33 @@ func (m Migration) ToYAML() ([]byte, error) { } // ExecuteAll the commands for this migration. +// We create a temporary executable file with all commands. +// This allows for using shell variables in multiple commands. func ExecuteAll(commands []string, envs []string) error { if len(commands) == 0 { return nil } - for i, each := range commands { - log.Println(each) - cmd := exec.Command("sh", "-c", each) - cmd.Env = append(os.Environ(), envs...) // extend, not replace - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - return fmt.Errorf("%d: failed to run :%v", i, err) + tempScript := path.Join(os.TempDir(), "gmig.sh") + content := new(bytes.Buffer) + fmt.Fprintln(content, `#!/bin/bash +set -e -v`) + for _, each := range commands { + fmt.Fprintln(content, each) + } + if err := ioutil.WriteFile(tempScript, content.Bytes(), os.ModePerm); err != nil { + return fmt.Errorf("failed to write temporary migration section: %v", err) + } + defer func() { + if err := os.Remove(tempScript); err != nil { + log.Printf("warning: failed to remove temporary migration execution script:%s\n", tempScript) } + }() + cmd := exec.Command("sh", "-c", tempScript) + cmd.Env = append(os.Environ(), envs...) // extend, not replace + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run migration section: %v", err) } return nil }