Skip to content

Commit 3800290

Browse files
armonmitchellh
authored andcommitted
command/push: Adding the push command
1 parent 722a885 commit 3800290

5 files changed

Lines changed: 195 additions & 4 deletions

File tree

‎command/init_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func TestInit_remoteState(t *testing.T) {
150150
defer fixDir(tmp, cwd)
151151

152152
s := terraform.NewState()
153-
conf, srv := testRemoteState(t, s)
153+
conf, srv := testRemoteState(t, s, 200)
154154
defer srv.Close()
155155

156156
ui := new(cli.MockUi)

‎command/pull_test.go‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestPull_cliRemote(t *testing.T) {
3838
defer fixDir(tmp, cwd)
3939

4040
s := terraform.NewState()
41-
conf, srv := testRemoteState(t, s)
41+
conf, srv := testRemoteState(t, s, 200)
4242
defer srv.Close()
4343

4444
ui := new(cli.MockUi)
@@ -67,7 +67,7 @@ func TestPull_local(t *testing.T) {
6767

6868
s := terraform.NewState()
6969
s.Serial = 10
70-
conf, srv := testRemoteState(t, s)
70+
conf, srv := testRemoteState(t, s, 200)
7171

7272
s = terraform.NewState()
7373
s.Serial = 5
@@ -95,7 +95,7 @@ func TestPull_local(t *testing.T) {
9595

9696
// testRemoteState is used to make a test HTTP server to
9797
// return a given state file
98-
func testRemoteState(t *testing.T, s *terraform.State) (*terraform.RemoteState, *httptest.Server) {
98+
func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) {
9999
var b64md5 string
100100
buf := bytes.NewBuffer(nil)
101101

@@ -109,6 +109,10 @@ func testRemoteState(t *testing.T, s *terraform.State) (*terraform.RemoteState,
109109
}
110110

111111
cb := func(resp http.ResponseWriter, req *http.Request) {
112+
if req.Method == "PUT" {
113+
resp.WriteHeader(c)
114+
return
115+
}
112116
if s == nil {
113117
resp.WriteHeader(404)
114118
return

‎command/push.go‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package command
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform/remote"
9+
"github.com/hashicorp/terraform/terraform"
10+
)
11+
12+
type PushCommand struct {
13+
Meta
14+
}
15+
16+
func (c *PushCommand) Run(args []string) int {
17+
var force bool
18+
var remoteConf terraform.RemoteState
19+
args = c.Meta.process(args, false)
20+
cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError)
21+
cmdFlags.StringVar(&remoteConf.Name, "remote", "", "")
22+
cmdFlags.StringVar(&remoteConf.Server, "remote-server", "", "")
23+
cmdFlags.StringVar(&remoteConf.AuthToken, "remote-auth", "", "")
24+
cmdFlags.BoolVar(&force, "force", false, "")
25+
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
26+
if err := cmdFlags.Parse(args); err != nil {
27+
return 1
28+
}
29+
30+
// Validate the remote configuration if given
31+
var conf *terraform.RemoteState
32+
if !remoteConf.Empty() {
33+
if err := remote.ValidateConfig(&remoteConf); err != nil {
34+
c.Ui.Error(fmt.Sprintf("%s", err))
35+
return 1
36+
}
37+
conf = &remoteConf
38+
} else {
39+
// Recover the local state if any
40+
local, _, err := remote.ReadLocalState()
41+
if err != nil {
42+
c.Ui.Error(fmt.Sprintf("%s", err))
43+
return 1
44+
}
45+
if local == nil || local.Remote == nil {
46+
c.Ui.Error("No remote state server configured")
47+
return 1
48+
}
49+
conf = local.Remote
50+
}
51+
52+
// Attempt to push the state
53+
change, err := remote.PushState(conf, force)
54+
if err != nil {
55+
c.Ui.Error(fmt.Sprintf(
56+
"Failed to push state: %v", err))
57+
return 1
58+
}
59+
60+
// Use an error exit code if the update was not a success
61+
if !change.SuccessfulPush() {
62+
c.Ui.Error(fmt.Sprintf("%s", change))
63+
return 1
64+
} else {
65+
c.Ui.Output(fmt.Sprintf("%s", change))
66+
}
67+
return 0
68+
}
69+
70+
func (c *PushCommand) Help() string {
71+
helpText := `
72+
Usage: terraform push [options]
73+
74+
Uploads the local state file to the remote server. This is done automatically
75+
by commands when remote state if configured, but can also be done manually
76+
using this command.
77+
78+
Options:
79+
80+
-force Forces the upload of the local state, ignoring any
81+
conflicts. This should be used carefully, as force pushing
82+
can cause remote state information to be lost.
83+
84+
-remote=name Name of the state file in the state storage server.
85+
Optional, default does not use remote storage.
86+
87+
-remote-auth=token Authentication token for state storage server.
88+
Optional, defaults to blank.
89+
90+
-remote-server=url URL of the remote storage server.
91+
92+
`
93+
return strings.TrimSpace(helpText)
94+
}
95+
96+
func (c *PushCommand) Synopsis() string {
97+
return "Uploads the the local state to the remote server"
98+
}

‎command/push_test.go‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package command
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform/remote"
8+
"github.com/hashicorp/terraform/terraform"
9+
"github.com/mitchellh/cli"
10+
)
11+
12+
func TestPush_noRemote(t *testing.T) {
13+
tmp, cwd := testCwd(t)
14+
defer fixDir(tmp, cwd)
15+
16+
ui := new(cli.MockUi)
17+
c := &PushCommand{
18+
Meta: Meta{
19+
ContextOpts: testCtxConfig(testProvider()),
20+
Ui: ui,
21+
},
22+
}
23+
24+
args := []string{}
25+
if code := c.Run(args); code != 1 {
26+
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
27+
}
28+
}
29+
30+
func TestPush_cliRemote_noState(t *testing.T) {
31+
tmp, cwd := testCwd(t)
32+
defer fixDir(tmp, cwd)
33+
34+
s := terraform.NewState()
35+
conf, srv := testRemoteState(t, s, 200)
36+
defer srv.Close()
37+
38+
ui := new(cli.MockUi)
39+
c := &PushCommand{
40+
Meta: Meta{
41+
ContextOpts: testCtxConfig(testProvider()),
42+
Ui: ui,
43+
},
44+
}
45+
46+
// Remote with no local state!
47+
args := []string{"-remote", conf.Name, "-remote-server", conf.Server}
48+
if code := c.Run(args); code != 1 {
49+
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
50+
}
51+
}
52+
53+
func TestPush_local(t *testing.T) {
54+
tmp, cwd := testCwd(t)
55+
defer fixDir(tmp, cwd)
56+
57+
s := terraform.NewState()
58+
s.Serial = 5
59+
conf, srv := testRemoteState(t, s, 200)
60+
61+
s = terraform.NewState()
62+
s.Serial = 10
63+
s.Remote = conf
64+
defer srv.Close()
65+
66+
// Store the local state
67+
buf := bytes.NewBuffer(nil)
68+
terraform.WriteState(s, buf)
69+
remote.EnsureDirectory()
70+
remote.Persist(buf)
71+
72+
ui := new(cli.MockUi)
73+
c := &PushCommand{
74+
Meta: Meta{
75+
ContextOpts: testCtxConfig(testProvider()),
76+
Ui: ui,
77+
},
78+
}
79+
args := []string{}
80+
if code := c.Run(args); code != 0 {
81+
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
82+
}
83+
}

‎commands.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ func init() {
8484
}, nil
8585
},
8686

87+
"push": func() (cli.Command, error) {
88+
return &command.PushCommand{
89+
Meta: meta,
90+
}, nil
91+
},
92+
8793
"refresh": func() (cli.Command, error) {
8894
return &command.RefreshCommand{
8995
Meta: meta,

0 commit comments

Comments
 (0)