github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/apply.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/tfdiags"
    11  
    12  	"github.com/hashicorp/go-getter"
    13  	"github.com/hashicorp/terraform/backend"
    14  	"github.com/hashicorp/terraform/config"
    15  	"github.com/hashicorp/terraform/config/module"
    16  	"github.com/hashicorp/terraform/terraform"
    17  )
    18  
    19  // ApplyCommand is a Command implementation that applies a Terraform
    20  // configuration and actually builds or changes infrastructure.
    21  type ApplyCommand struct {
    22  	Meta
    23  
    24  	// If true, then this apply command will become the "destroy"
    25  	// command. It is just like apply but only processes a destroy.
    26  	Destroy bool
    27  }
    28  
    29  func (c *ApplyCommand) Run(args []string) int {
    30  	var destroyForce, refresh, autoApprove bool
    31  	args, err := c.Meta.process(args, true)
    32  	if err != nil {
    33  		return 1
    34  	}
    35  
    36  	cmdName := "apply"
    37  	if c.Destroy {
    38  		cmdName = "destroy"
    39  	}
    40  
    41  	cmdFlags := c.Meta.flagSet(cmdName)
    42  	if c.Destroy {
    43  		cmdFlags.BoolVar(&destroyForce, "force", false, "force")
    44  	}
    45  	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
    46  	if !c.Destroy {
    47  		cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying")
    48  	}
    49  	cmdFlags.IntVar(
    50  		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
    51  	cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
    52  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    53  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    54  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    55  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    56  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    57  	if err := cmdFlags.Parse(args); err != nil {
    58  		return 1
    59  	}
    60  
    61  	// Get the args. The "maybeInit" flag tracks whether we may need to
    62  	// initialize the configuration from a remote path. This is true as long
    63  	// as we have an argument.
    64  	args = cmdFlags.Args()
    65  	maybeInit := len(args) == 1
    66  	configPath, err := ModulePath(args)
    67  	if err != nil {
    68  		c.Ui.Error(err.Error())
    69  		return 1
    70  	}
    71  
    72  	// Check for user-supplied plugin path
    73  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
    74  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
    75  		return 1
    76  	}
    77  
    78  	if !c.Destroy && maybeInit {
    79  		// We need the pwd for the getter operation below
    80  		pwd, err := os.Getwd()
    81  		if err != nil {
    82  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    83  			return 1
    84  		}
    85  
    86  		// Do a detect to determine if we need to do an init + apply.
    87  		if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
    88  			c.Ui.Error(fmt.Sprintf(
    89  				"Invalid path: %s", err))
    90  			return 1
    91  		} else if !strings.HasPrefix(detected, "file") {
    92  			// If this isn't a file URL then we're doing an init +
    93  			// apply.
    94  			var init InitCommand
    95  			init.Meta = c.Meta
    96  			if code := init.Run([]string{detected}); code != 0 {
    97  				return code
    98  			}
    99  
   100  			// Change the config path to be the cwd
   101  			configPath = pwd
   102  		}
   103  	}
   104  
   105  	// Check if the path is a plan
   106  	plan, err := c.Plan(configPath)
   107  	if err != nil {
   108  		c.Ui.Error(err.Error())
   109  		return 1
   110  	}
   111  	if c.Destroy && plan != nil {
   112  		c.Ui.Error(fmt.Sprintf(
   113  			"Destroy can't be called with a plan file."))
   114  		return 1
   115  	}
   116  	if plan != nil {
   117  		// Reset the config path for backend loading
   118  		configPath = ""
   119  	}
   120  
   121  	var diags tfdiags.Diagnostics
   122  
   123  	// Load the module if we don't have one yet (not running from plan)
   124  	var mod *module.Tree
   125  	if plan == nil {
   126  		var modDiags tfdiags.Diagnostics
   127  		mod, modDiags = c.Module(configPath)
   128  		diags = diags.Append(modDiags)
   129  		if modDiags.HasErrors() {
   130  			c.showDiagnostics(diags)
   131  			return 1
   132  		}
   133  	}
   134  
   135  	var conf *config.Config
   136  	if mod != nil {
   137  		conf = mod.Config()
   138  	}
   139  
   140  	// Load the backend
   141  	b, err := c.Backend(&BackendOpts{
   142  		Config: conf,
   143  		Plan:   plan,
   144  	})
   145  	if err != nil {
   146  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
   147  		return 1
   148  	}
   149  
   150  	// Build the operation
   151  	opReq := c.Operation()
   152  	opReq.Destroy = c.Destroy
   153  	opReq.Module = mod
   154  	opReq.Plan = plan
   155  	opReq.PlanRefresh = refresh
   156  	opReq.Type = backend.OperationTypeApply
   157  	opReq.AutoApprove = autoApprove
   158  	opReq.DestroyForce = destroyForce
   159  
   160  	op, err := c.RunOperation(b, opReq)
   161  	if err != nil {
   162  		diags = diags.Append(err)
   163  	}
   164  
   165  	c.showDiagnostics(diags)
   166  	if diags.HasErrors() {
   167  		return 1
   168  	}
   169  
   170  	if !c.Destroy {
   171  		// Get the right module that we used. If we ran a plan, then use
   172  		// that module.
   173  		if plan != nil {
   174  			mod = plan.Module
   175  		}
   176  
   177  		if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" {
   178  			c.Ui.Output(c.Colorize().Color(outputs))
   179  		}
   180  	}
   181  
   182  	return 0
   183  }
   184  
   185  func (c *ApplyCommand) Help() string {
   186  	if c.Destroy {
   187  		return c.helpDestroy()
   188  	}
   189  
   190  	return c.helpApply()
   191  }
   192  
   193  func (c *ApplyCommand) Synopsis() string {
   194  	if c.Destroy {
   195  		return "Destroy Terraform-managed infrastructure"
   196  	}
   197  
   198  	return "Builds or changes infrastructure"
   199  }
   200  
   201  func (c *ApplyCommand) helpApply() string {
   202  	helpText := `
   203  Usage: terraform apply [options] [DIR-OR-PLAN]
   204  
   205    Builds or changes infrastructure according to Terraform configuration
   206    files in DIR.
   207  
   208    By default, apply scans the current directory for the configuration
   209    and applies the changes appropriately. However, a path to another
   210    configuration or an execution plan can be provided. Execution plans can be
   211    used to only execute a pre-determined set of actions.
   212  
   213  Options:
   214  
   215    -backup=path           Path to backup the existing state file before
   216                           modifying. Defaults to the "-state-out" path with
   217                           ".backup" extension. Set to "-" to disable backup.
   218  
   219    -lock=true             Lock the state file when locking is supported.
   220  
   221    -lock-timeout=0s       Duration to retry a state lock.
   222  
   223    -auto-approve          Skip interactive approval of plan before applying.
   224  
   225    -input=true            Ask for input for variables if not directly set.
   226  
   227    -no-color              If specified, output won't contain any color.
   228  
   229    -parallelism=n         Limit the number of parallel resource operations.
   230                           Defaults to 10.
   231  
   232    -refresh=true          Update state prior to checking for differences. This
   233                           has no effect if a plan file is given to apply.
   234  
   235    -state=path            Path to read and save state (unless state-out
   236                           is specified). Defaults to "terraform.tfstate".
   237  
   238    -state-out=path        Path to write state to that is different than
   239                           "-state". This can be used to preserve the old
   240                           state.
   241  
   242    -target=resource       Resource to target. Operation will be limited to this
   243                           resource and its dependencies. This flag can be used
   244                           multiple times.
   245  
   246    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   247                           flag can be set multiple times.
   248  
   249    -var-file=foo          Set variables in the Terraform configuration from
   250                           a file. If "terraform.tfvars" or any ".auto.tfvars"
   251                           files are present, they will be automatically loaded.
   252  
   253  
   254  `
   255  	return strings.TrimSpace(helpText)
   256  }
   257  
   258  func (c *ApplyCommand) helpDestroy() string {
   259  	helpText := `
   260  Usage: terraform destroy [options] [DIR]
   261  
   262    Destroy Terraform-managed infrastructure.
   263  
   264  Options:
   265  
   266    -backup=path           Path to backup the existing state file before
   267                           modifying. Defaults to the "-state-out" path with
   268                           ".backup" extension. Set to "-" to disable backup.
   269  
   270    -force                 Don't ask for input for destroy confirmation.
   271  
   272    -lock=true             Lock the state file when locking is supported.
   273  
   274    -lock-timeout=0s       Duration to retry a state lock.
   275  
   276    -no-color              If specified, output won't contain any color.
   277  
   278    -parallelism=n         Limit the number of concurrent operations.
   279                           Defaults to 10.
   280  
   281    -refresh=true          Update state prior to checking for differences. This
   282                           has no effect if a plan file is given to apply.
   283  
   284    -state=path            Path to read and save state (unless state-out
   285                           is specified). Defaults to "terraform.tfstate".
   286  
   287    -state-out=path        Path to write state to that is different than
   288                           "-state". This can be used to preserve the old
   289                           state.
   290  
   291    -target=resource       Resource to target. Operation will be limited to this
   292                           resource and its dependencies. This flag can be used
   293                           multiple times.
   294  
   295    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   296                           flag can be set multiple times.
   297  
   298    -var-file=foo          Set variables in the Terraform configuration from
   299                           a file. If "terraform.tfvars" or any ".auto.tfvars"
   300                           files are present, they will be automatically loaded.
   301  
   302  
   303  `
   304  	return strings.TrimSpace(helpText)
   305  }
   306  
   307  func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string {
   308  	if state == nil {
   309  		return ""
   310  	}
   311  
   312  	ms := state.ModuleByPath(modPath)
   313  	if ms == nil {
   314  		return ""
   315  	}
   316  
   317  	outputs := ms.Outputs
   318  	outputBuf := new(bytes.Buffer)
   319  	if len(outputs) > 0 {
   320  		schemaMap := make(map[string]*config.Output)
   321  		if schema != nil {
   322  			for _, s := range schema {
   323  				schemaMap[s.Name] = s
   324  			}
   325  		}
   326  
   327  		if includeHeader {
   328  			outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
   329  		}
   330  
   331  		// Output the outputs in alphabetical order
   332  		keyLen := 0
   333  		ks := make([]string, 0, len(outputs))
   334  		for key, _ := range outputs {
   335  			ks = append(ks, key)
   336  			if len(key) > keyLen {
   337  				keyLen = len(key)
   338  			}
   339  		}
   340  		sort.Strings(ks)
   341  
   342  		for _, k := range ks {
   343  			schema, ok := schemaMap[k]
   344  			if ok && schema.Sensitive {
   345  				outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
   346  				continue
   347  			}
   348  
   349  			v := outputs[k]
   350  			switch typedV := v.Value.(type) {
   351  			case string:
   352  				outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
   353  			case []interface{}:
   354  				outputBuf.WriteString(formatListOutput("", k, typedV))
   355  				outputBuf.WriteString("\n")
   356  			case map[string]interface{}:
   357  				outputBuf.WriteString(formatMapOutput("", k, typedV))
   358  				outputBuf.WriteString("\n")
   359  			}
   360  		}
   361  	}
   362  
   363  	return strings.TrimSpace(outputBuf.String())
   364  }
   365  
   366  const outputInterrupt = `Interrupt received.
   367  Please wait for Terraform to exit or data loss may occur.
   368  Gracefully shutting down...`