Merge pull request #337 from hashicorp/f-apply-init

`terraform apply MODULE` shorthand
This commit is contained in:
Mitchell Hashimoto 2014-09-29 16:01:28 -07:00
commit d996959691
3 changed files with 134 additions and 7 deletions

View File

@ -8,6 +8,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -35,6 +36,12 @@ func (c *ApplyCommand) Run(args []string) int {
return 1 return 1
} }
pwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
return 1
}
var configPath string var configPath string
args = cmdFlags.Args() args = cmdFlags.Args()
if len(args) > 1 { if len(args) > 1 {
@ -44,11 +51,7 @@ func (c *ApplyCommand) Run(args []string) int {
} else if len(args) == 1 { } else if len(args) == 1 {
configPath = args[0] configPath = args[0]
} else { } else {
var err error configPath = pwd
configPath, err = os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
}
} }
// Prepare the extra hooks to count resources // Prepare the extra hooks to count resources
@ -67,6 +70,24 @@ func (c *ApplyCommand) Run(args []string) int {
backupPath = stateOutPath + DefaultBackupExtention backupPath = stateOutPath + DefaultBackupExtention
} }
// Do a detect to determine if we need to do an init + apply.
if detected, err := module.Detect(configPath, pwd); err != nil {
c.Ui.Error(fmt.Sprintf(
"Invalid path: %s", err))
return 1
} else if !strings.HasPrefix(detected, "file") {
// If this isn't a file URL then we're doing an init +
// apply.
var init InitCommand
init.Meta = c.Meta
if code := init.Run([]string{detected}); code != 0 {
return code
}
// Change the config path to be the cwd
configPath = pwd
}
// Build the context based on the arguments given // Build the context based on the arguments given
ctx, planned, err := c.Context(contextOpts{ ctx, planned, err := c.Context(contextOpts{
Path: configPath, Path: configPath,
@ -229,10 +250,16 @@ func (c *ApplyCommand) Run(args []string) int {
func (c *ApplyCommand) Help() string { func (c *ApplyCommand) Help() string {
helpText := ` helpText := `
Usage: terraform apply [options] [dir] Usage: terraform apply [options] [DIR]
Builds or changes infrastructure according to Terraform configuration Builds or changes infrastructure according to Terraform configuration
files . files in DIR.
DIR can also be a SOURCE as given to the "init" command. In this case,
apply behaves as though "init" was called followed by "apply". This only
works for sources that aren't files, and only if the current working
directory is empty of Terraform files. This is a shortcut for getting
started.
Options: Options:

View File

@ -3,6 +3,9 @@ package command
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -196,6 +199,73 @@ func TestApply_error(t *testing.T) {
} }
} }
func TestApply_init(t *testing.T) {
// Change to the temporary directory
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
dir := tempDir(t)
if err := os.MkdirAll(dir, 0755); err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Chdir(dir); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)
// Create the test fixtures
statePath := testTempFile(t)
ln := testHttpServer(t)
defer ln.Close()
// Initialize the command
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
// Build the URL to the init
var u url.URL
u.Scheme = "http"
u.Host = ln.Addr().String()
u.Path = "/header"
args := []string{
"-state", statePath,
u.String(),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
if _, err := os.Stat("hello.tf"); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := os.Stat(statePath); err != nil {
t.Fatalf("err: %s", err)
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
defer f.Close()
state, err := terraform.ReadState(f)
if err != nil {
t.Fatalf("err: %s", err)
}
if state == nil {
t.Fatal("state should not be nil")
}
}
func TestApply_noArgs(t *testing.T) { func TestApply_noArgs(t *testing.T) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@ -942,6 +1012,31 @@ func TestApply_disableBackup(t *testing.T) {
} }
} }
func testHttpServer(t *testing.T) net.Listener {
ln, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("err: %s", err)
}
mux := http.NewServeMux()
mux.HandleFunc("/header", testHttpHandlerHeader)
var server http.Server
server.Handler = mux
go server.Serve(ln)
return ln
}
func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) {
var url url.URL
url.Scheme = "file"
url.Path = testFixturePath("init")
w.Header().Add("X-Terraform-Get", url.String())
w.WriteHeader(200)
}
const applyVarFile = ` const applyVarFile = `
foo = "bar" foo = "bar"
` `

View File

@ -19,6 +19,11 @@ and applies the changes appropriately. However, a path to another configuration
or an execution plan can be provided. Execution plans can be used to only or an execution plan can be provided. Execution plans can be used to only
execute a pre-determined set of actions. execute a pre-determined set of actions.
The `dir` argument can also be a [module source](/docs/modules/index.html).
In this case, `apply` behaves as though `init` were called with that
argument followed by an `apply` in the current directory. This is meant
as a shortcut for getting started.
The command-line flags are all optional. The list of available flags are: The command-line flags are all optional. The list of available flags are:
* `-backup=path` - Path to the backup file. Defaults to `-state-out` with * `-backup=path` - Path to the backup file. Defaults to `-state-out` with