github.com/d-luu/terraform@v0.11.12-beta1/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  	cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying")
    43  	if c.Destroy {
    44  		cmdFlags.BoolVar(&destroyForce, "force", false, "deprecated: same as auto-approve")
    45  	}
    46  	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
    47  	cmdFlags.IntVar(
    48  		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
    49  	cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
    50  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    51  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    52  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    53  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    54  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    55  	if err := cmdFlags.Parse(args); err != nil {
    56  		return 1
    57  	}
    58  
    59  	// Get the args. The "maybeInit" flag tracks whether we may need to
    60  	// initialize the configuration from a remote path. This is true as long
    61  	// as we have an argument.
    62  	args = cmdFlags.Args()
    63  	maybeInit := len(args) == 1
    64  	configPath, err := ModulePath(args)
    65  	if err != nil {
    66  		c.Ui.Error(err.Error())
    67  		return 1
    68  	}
    69  
    70  	// Check for user-supplied plugin path
    71  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
    72  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
    73  		return 1
    74  	}
    75  
    76  	if !c.Destroy && maybeInit {
    77  		// We need the pwd for the getter operation below
    78  		pwd, err := os.Getwd()
    79  		if err != nil {
    80  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    81  			return 1
    82  		}
    83  
    84  		// Do a detect to determine if we need to do an init + apply.
    85  		if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
    86  			c.Ui.Error(fmt.Sprintf(
    87  				"Invalid path: %s", err))
    88  			return 1
    89  		} else if !strings.HasPrefix(detected, "file") {
    90  			// If this isn't a file URL then we're doing an init +
    91  			// apply.
    92  			var init InitCommand
    93  			init.Meta = c.Meta
    94  			if code := init.Run([]string{detected}); code != 0 {
    95  				return code
    96  			}
    97  
    98  			// Change the config path to be the cwd
    99  			configPath = pwd
   100  		}
   101  	}
   102  
   103  	// Check if the path is a plan
   104  	plan, err := c.Plan(configPath)
   105  	if err != nil {
   106  		c.Ui.Error(err.Error())
   107  		return 1
   108  	}
   109  	if c.Destroy && plan != nil {
   110  		c.Ui.Error(fmt.Sprintf(
   111  			"Destroy can't be called with a plan file."))
   112  		return 1
   113  	}
   114  	if plan != nil {
   115  		// Reset the config path for backend loading
   116  		configPath = ""
   117  	}
   118  
   119  	var diags tfdiags.Diagnostics
   120  
   121  	// Load the module if we don't have one yet (not running from plan)
   122  	var mod *module.Tree
   123  	if plan == nil {
   124  		var modDiags tfdiags.Diagnostics
   125  		mod, modDiags = c.Module(configPath)
   126  		diags = diags.Append(modDiags)
   127  		if modDiags.HasErrors() {
   128  			c.showDiagnostics(diags)
   129  			return 1
   130  		}
   131  	}
   132  
   133  	var conf *config.Config
   134  	if mod != nil {
   135  		conf = mod.Config()
   136  	}
   137  
   138  	// Load the backend
   139  	b, err := c.Backend(&BackendOpts{
   140  		Config: conf,
   141  		Plan:   plan,
   142  	})
   143  	if err != nil {
   144  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
   145  		return 1
   146  	}
   147  
   148  	// Build the operation
   149  	opReq := c.Operation()
   150  	opReq.AutoApprove = autoApprove
   151  	opReq.Destroy = c.Destroy
   152  	opReq.DestroyForce = destroyForce
   153  	opReq.Module = mod
   154  	opReq.Plan = plan
   155  	opReq.PlanRefresh = refresh
   156  	opReq.Type = backend.OperationTypeApply
   157  
   158  	op, err := c.RunOperation(b, opReq)
   159  	if err != nil {
   160  		diags = diags.Append(err)
   161  	}
   162  
   163  	c.showDiagnostics(diags)
   164  	if diags.HasErrors() {
   165  		return 1
   166  	}
   167  
   168  	if !c.Destroy {
   169  		// Get the right module that we used. If we ran a plan, then use
   170  		// that module.
   171  		if plan != nil {
   172  			mod = plan.Module
   173  		}
   174  
   175  		if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" {
   176  			c.Ui.Output(c.Colorize().Color(outputs))
   177  		}
   178  	}
   179  
   180  	return op.ExitCode
   181  }
   182  
   183  func (c *ApplyCommand) Help() string {
   184  	if c.Destroy {
   185  		return c.helpDestroy()
   186  	}
   187  
   188  	return c.helpApply()
   189  }
   190  
   191  func (c *ApplyCommand) Synopsis() string {
   192  	if c.Destroy {
   193  		return "Destroy Terraform-managed infrastructure"
   194  	}
   195  
   196  	return "Builds or changes infrastructure"
   197  }
   198  
   199  func (c *ApplyCommand) helpApply() string {
   200  	helpText := `
   201  Usage: terraform apply [options] [DIR-OR-PLAN]
   202  
   203    Builds or changes infrastructure according to Terraform configuration
   204    files in DIR.
   205  
   206    By default, apply scans the current directory for the configuration
   207    and applies the changes appropriately. However, a path to another
   208    configuration or an execution plan can be provided. Execution plans can be
   209    used to only execute a pre-determined set of actions.
   210  
   211  Options:
   212  
   213    -backup=path           Path to backup the existing state file before
   214                           modifying. Defaults to the "-state-out" path with
   215                           ".backup" extension. Set to "-" to disable backup.
   216  
   217    -auto-approve          Skip interactive approval of plan before applying.
   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    -input=true            Ask for input for variables if not directly set.
   224  
   225    -no-color              If specified, output won't contain any color.
   226  
   227    -parallelism=n         Limit the number of parallel resource operations.
   228                           Defaults to 10.
   229  
   230    -refresh=true          Update state prior to checking for differences. This
   231                           has no effect if a plan file is given to apply.
   232  
   233    -state=path            Path to read and save state (unless state-out
   234                           is specified). Defaults to "terraform.tfstate".
   235  
   236    -state-out=path        Path to write state to that is different than
   237                           "-state". This can be used to preserve the old
   238                           state.
   239  
   240    -target=resource       Resource to target. Operation will be limited to this
   241                           resource and its dependencies. This flag can be used
   242                           multiple times.
   243  
   244    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   245                           flag can be set multiple times.
   246  
   247    -var-file=foo          Set variables in the Terraform configuration from
   248                           a file. If "terraform.tfvars" or any ".auto.tfvars"
   249                           files are present, they will be automatically loaded.
   250  
   251  
   252  `
   253  	return strings.TrimSpace(helpText)
   254  }
   255  
   256  func (c *ApplyCommand) helpDestroy() string {
   257  	helpText := `
   258  Usage: terraform destroy [options] [DIR]
   259  
   260    Destroy Terraform-managed infrastructure.
   261  
   262  Options:
   263  
   264    -backup=path           Path to backup the existing state file before
   265                           modifying. Defaults to the "-state-out" path with
   266                           ".backup" extension. Set to "-" to disable backup.
   267  
   268    -auto-approve          Skip interactive approval before destroying.
   269  
   270    -force                 Deprecated: same as auto-approve.
   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...`