diff --git a/command/push.go b/command/push.go new file mode 100644 index 000000000..dbc498991 --- /dev/null +++ b/command/push.go @@ -0,0 +1,104 @@ +package command + +import ( + "flag" + "fmt" + "os" + "strings" +) + +type PushCommand struct { + Meta +} + +func (c *PushCommand) Run(args []string) int { + var atlasToken string + var moduleLock bool + args = c.Meta.process(args, false) + cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) + cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&atlasToken, "token", "", "") + cmdFlags.BoolVar(&moduleLock, "module-lock", true, "") + cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + if err := cmdFlags.Parse(args); err != nil { + return 1 + } + + // The pwd is used for the configuration path if one is not given + pwd, err := os.Getwd() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) + return 1 + } + + // Get the path to the configuration depending on the args. + var configPath string + args = cmdFlags.Args() + if len(args) > 1 { + c.Ui.Error("The apply command expects at most one argument.") + cmdFlags.Usage() + return 1 + } else if len(args) == 1 { + configPath = args[0] + } else { + configPath = pwd + } + + // Verify the state is remote, we can't push without a remote state + s, err := c.State() + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to read state: %s", err)) + return 1 + } + if !s.State().IsRemote() { + c.Ui.Error( + "Remote state is not enabled. For Atlas to run Terraform\n" + + "for you, remote state must be used and configured. Remote\n" + + "state via any backend is accepted, not just Atlas. To\n" + + "configure remote state, use the `terraform remote config`\n" + + "command.") + return 1 + } + + // Build the context based on the arguments given + _, planned, err := c.Context(contextOpts{ + Path: configPath, + StatePath: c.Meta.statePath, + }) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + if planned { + c.Ui.Error( + "A plan file cannot be given as the path to the configuration.\n" + + "A path to a module (directory with configuration) must be given.") + return 1 + } + + return 0 +} + +func (c *PushCommand) Help() string { + helpText := ` +Usage: terraform push [options] [DIR] + + Upload this Terraform module to an Atlas server for remote + infrastructure management. + +Options: + + -module-lock=true If true (default), then the modules are locked at + their current checkout and uploaded completely. This + prevents Atlas from running "terraform get". + + -token= Access token to use to upload. If blank, the ATLAS_TOKEN + environmental variable will be used. + +` + return strings.TrimSpace(helpText) +} + +func (c *PushCommand) Synopsis() string { + return "Upload this Terraform module to Atlas to run" +} diff --git a/command/push_test.go b/command/push_test.go new file mode 100644 index 000000000..8b52421b8 --- /dev/null +++ b/command/push_test.go @@ -0,0 +1,59 @@ +package command + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/cli" +) + +func TestPush_noState(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 1 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } +} + +func TestPush_noRemoteState(t *testing.T) { + state := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + statePath := testStateFile(t, state) + + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + } + if code := c.Run(args); code != 1 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} diff --git a/commands.go b/commands.go index c585b7827..a4af7f983 100644 --- a/commands.go +++ b/commands.go @@ -80,6 +80,12 @@ func init() { }, nil }, + "push": func() (cli.Command, error) { + return &command.PushCommand{ + Meta: meta, + }, nil + }, + "refresh": func() (cli.Command, error) { return &command.RefreshCommand{ Meta: meta,