github.com/vic3lord/terraform@v0.8.0-rc1.0.20170626102919-16c6dd2cb372/command/apply.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/go-getter"
    12  	"github.com/hashicorp/terraform/backend"
    13  	"github.com/hashicorp/terraform/config"
    14  	"github.com/hashicorp/terraform/config/module"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  // ApplyCommand is a Command implementation that applies a Terraform
    19  // configuration and actually builds or changes infrastructure.
    20  type ApplyCommand struct {
    21  	Meta
    22  
    23  	// If true, then this apply command will become the "destroy"
    24  	// command. It is just like apply but only processes a destroy.
    25  	Destroy bool
    26  
    27  	// When this channel is closed, the apply will be cancelled.
    28  	ShutdownCh <-chan struct{}
    29  }
    30  
    31  func (c *ApplyCommand) Run(args []string) int {
    32  	var destroyForce, refresh bool
    33  	args = c.Meta.process(args, true)
    34  
    35  	cmdName := "apply"
    36  	if c.Destroy {
    37  		cmdName = "destroy"
    38  	}
    39  
    40  	cmdFlags := c.Meta.flagSet(cmdName)
    41  	if c.Destroy {
    42  		cmdFlags.BoolVar(&destroyForce, "force", false, "force")
    43  	}
    44  	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
    45  	cmdFlags.IntVar(
    46  		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
    47  	cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
    48  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    49  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    50  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    51  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    52  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    53  	if err := cmdFlags.Parse(args); err != nil {
    54  		return 1
    55  	}
    56  
    57  	// Get the args. The "maybeInit" flag tracks whether we may need to
    58  	// initialize the configuration from a remote path. This is true as long
    59  	// as we have an argument.
    60  	args = cmdFlags.Args()
    61  	maybeInit := len(args) == 1
    62  	configPath, err := ModulePath(args)
    63  	if err != nil {
    64  		c.Ui.Error(err.Error())
    65  		return 1
    66  	}
    67  
    68  	// Check for user-supplied plugin path
    69  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
    70  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
    71  		return 1
    72  	}
    73  
    74  	if !c.Destroy && maybeInit {
    75  		// We need the pwd for the getter operation below
    76  		pwd, err := os.Getwd()
    77  		if err != nil {
    78  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    79  			return 1
    80  		}
    81  
    82  		// Do a detect to determine if we need to do an init + apply.
    83  		if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
    84  			c.Ui.Error(fmt.Sprintf(
    85  				"Invalid path: %s", err))
    86  			return 1
    87  		} else if !strings.HasPrefix(detected, "file") {
    88  			// If this isn't a file URL then we're doing an init +
    89  			// apply.
    90  			var init InitCommand
    91  			init.Meta = c.Meta
    92  			if code := init.Run([]string{detected}); code != 0 {
    93  				return code
    94  			}
    95  
    96  			// Change the config path to be the cwd
    97  			configPath = pwd
    98  		}
    99  	}
   100  
   101  	// Check if the path is a plan
   102  	plan, err := c.Plan(configPath)
   103  	if err != nil {
   104  		c.Ui.Error(err.Error())
   105  		return 1
   106  	}
   107  	if c.Destroy && plan != nil {
   108  		c.Ui.Error(fmt.Sprintf(
   109  			"Destroy can't be called with a plan file."))
   110  		return 1
   111  	}
   112  	if plan != nil {
   113  		// Reset the config path for backend loading
   114  		configPath = ""
   115  	}
   116  
   117  	// Load the module if we don't have one yet (not running from plan)
   118  	var mod *module.Tree
   119  	if plan == nil {
   120  		mod, err = c.Module(configPath)
   121  		if err != nil {
   122  			c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
   123  			return 1
   124  		}
   125  	}
   126  
   127  	/*
   128  		terraform.SetDebugInfo(DefaultDataDir)
   129  
   130  		// Check for the legacy graph
   131  		if experiment.Enabled(experiment.X_legacyGraph) {
   132  			c.Ui.Output(c.Colorize().Color(
   133  				"[reset][bold][yellow]" +
   134  					"Legacy graph enabled! This will use the graph from Terraform 0.7.x\n" +
   135  					"to execute this operation. This will be removed in the future so\n" +
   136  					"please report any issues causing you to use this to the Terraform\n" +
   137  					"project.\n\n"))
   138  		}
   139  	*/
   140  
   141  	var conf *config.Config
   142  	if mod != nil {
   143  		conf = mod.Config()
   144  	}
   145  
   146  	// Load the backend
   147  	b, err := c.Backend(&BackendOpts{
   148  		Config: conf,
   149  		Plan:   plan,
   150  	})
   151  	if err != nil {
   152  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
   153  		return 1
   154  	}
   155  
   156  	// If we're not forcing and we're destroying, verify with the
   157  	// user at this point.
   158  	if !destroyForce && c.Destroy {
   159  		// Default destroy message
   160  		desc := "Terraform will delete all your managed infrastructure.\n" +
   161  			"There is no undo. Only 'yes' will be accepted to confirm."
   162  
   163  		// If targets are specified, list those to user
   164  		if c.Meta.targets != nil {
   165  			var descBuffer bytes.Buffer
   166  			descBuffer.WriteString("Terraform will delete the following infrastructure:\n")
   167  			for _, target := range c.Meta.targets {
   168  				descBuffer.WriteString("\t")
   169  				descBuffer.WriteString(target)
   170  				descBuffer.WriteString("\n")
   171  			}
   172  			descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm")
   173  			desc = descBuffer.String()
   174  		}
   175  
   176  		v, err := c.UIInput().Input(&terraform.InputOpts{
   177  			Id:          "destroy",
   178  			Query:       "Do you really want to destroy?",
   179  			Description: desc,
   180  		})
   181  		if err != nil {
   182  			c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
   183  			return 1
   184  		}
   185  		if v != "yes" {
   186  			c.Ui.Output("Destroy cancelled.")
   187  			return 1
   188  		}
   189  	}
   190  
   191  	// Build the operation
   192  	opReq := c.Operation()
   193  	opReq.Destroy = c.Destroy
   194  	opReq.Module = mod
   195  	opReq.Plan = plan
   196  	opReq.PlanRefresh = refresh
   197  	opReq.Type = backend.OperationTypeApply
   198  
   199  	// Perform the operation
   200  	ctx, ctxCancel := context.WithCancel(context.Background())
   201  	defer ctxCancel()
   202  	op, err := b.Operation(ctx, opReq)
   203  	if err != nil {
   204  		c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err))
   205  		return 1
   206  	}
   207  
   208  	// Wait for the operation to complete or an interrupt to occur
   209  	select {
   210  	case <-c.ShutdownCh:
   211  		// Cancel our context so we can start gracefully exiting
   212  		ctxCancel()
   213  
   214  		// Notify the user
   215  		c.Ui.Output(outputInterrupt)
   216  
   217  		// Still get the result, since there is still one
   218  		select {
   219  		case <-c.ShutdownCh:
   220  			c.Ui.Error(
   221  				"Two interrupts received. Exiting immediately. Note that data\n" +
   222  					"loss may have occurred.")
   223  			return 1
   224  		case <-op.Done():
   225  		}
   226  	case <-op.Done():
   227  		if err := op.Err; err != nil {
   228  			c.Ui.Error(err.Error())
   229  			return 1
   230  		}
   231  	}
   232  
   233  	if !c.Destroy {
   234  		// Get the right module that we used. If we ran a plan, then use
   235  		// that module.
   236  		if plan != nil {
   237  			mod = plan.Module
   238  		}
   239  
   240  		if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" {
   241  			c.Ui.Output(c.Colorize().Color(outputs))
   242  		}
   243  	}
   244  
   245  	return 0
   246  }
   247  
   248  func (c *ApplyCommand) Help() string {
   249  	if c.Destroy {
   250  		return c.helpDestroy()
   251  	}
   252  
   253  	return c.helpApply()
   254  }
   255  
   256  func (c *ApplyCommand) Synopsis() string {
   257  	if c.Destroy {
   258  		return "Destroy Terraform-managed infrastructure"
   259  	}
   260  
   261  	return "Builds or changes infrastructure"
   262  }
   263  
   264  func (c *ApplyCommand) helpApply() string {
   265  	helpText := `
   266  Usage: terraform apply [options] [DIR-OR-PLAN]
   267  
   268    Builds or changes infrastructure according to Terraform configuration
   269    files in DIR.
   270  
   271    By default, apply scans the current directory for the configuration
   272    and applies the changes appropriately. However, a path to another
   273    configuration or an execution plan can be provided. Execution plans can be
   274    used to only execute a pre-determined set of actions.
   275  
   276    DIR can also be a SOURCE as given to the "init" command. In this case,
   277    apply behaves as though "init" was called followed by "apply". This only
   278    works for sources that aren't files, and only if the current working
   279    directory is empty of Terraform files. This is a shortcut for getting
   280    started.
   281  
   282  Options:
   283  
   284    -backup=path           Path to backup the existing state file before
   285                           modifying. Defaults to the "-state-out" path with
   286                           ".backup" extension. Set to "-" to disable backup.
   287  
   288    -lock=true             Lock the state file when locking is supported.
   289  
   290    -lock-timeout=0s       Duration to retry a state lock.
   291  
   292    -input=true            Ask for input for variables if not directly set.
   293  
   294    -no-color              If specified, output won't contain any color.
   295  
   296    -parallelism=n         Limit the number of parallel resource operations.
   297                           Defaults to 10.
   298  
   299    -refresh=true          Update state prior to checking for differences. This
   300                           has no effect if a plan file is given to apply.
   301  
   302    -state=path            Path to read and save state (unless state-out
   303                           is specified). Defaults to "terraform.tfstate".
   304  
   305    -state-out=path        Path to write state to that is different than
   306                           "-state". This can be used to preserve the old
   307                           state.
   308  
   309    -target=resource       Resource to target. Operation will be limited to this
   310                           resource and its dependencies. This flag can be used
   311                           multiple times.
   312  
   313    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   314                           flag can be set multiple times.
   315  
   316    -var-file=foo          Set variables in the Terraform configuration from
   317                           a file. If "terraform.tfvars" is present, it will be
   318                           automatically loaded if this flag is not specified.
   319  
   320  
   321  `
   322  	return strings.TrimSpace(helpText)
   323  }
   324  
   325  func (c *ApplyCommand) helpDestroy() string {
   326  	helpText := `
   327  Usage: terraform destroy [options] [DIR]
   328  
   329    Destroy Terraform-managed infrastructure.
   330  
   331  Options:
   332  
   333    -backup=path           Path to backup the existing state file before
   334                           modifying. Defaults to the "-state-out" path with
   335                           ".backup" extension. Set to "-" to disable backup.
   336  
   337    -force                 Don't ask for input for destroy confirmation.
   338  
   339    -lock=true             Lock the state file when locking is supported.
   340  
   341    -lock-timeout=0s       Duration to retry a state lock.
   342  
   343    -no-color              If specified, output won't contain any color.
   344  
   345    -parallelism=n         Limit the number of concurrent operations.
   346                           Defaults to 10.
   347  
   348    -refresh=true          Update state prior to checking for differences. This
   349                           has no effect if a plan file is given to apply.
   350  
   351    -state=path            Path to read and save state (unless state-out
   352                           is specified). Defaults to "terraform.tfstate".
   353  
   354    -state-out=path        Path to write state to that is different than
   355                           "-state". This can be used to preserve the old
   356                           state.
   357  
   358    -target=resource       Resource to target. Operation will be limited to this
   359                           resource and its dependencies. This flag can be used
   360                           multiple times.
   361  
   362    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   363                           flag can be set multiple times.
   364  
   365    -var-file=foo          Set variables in the Terraform configuration from
   366                           a file. If "terraform.tfvars" is present, it will be
   367                           automatically loaded if this flag is not specified.
   368  
   369  
   370  `
   371  	return strings.TrimSpace(helpText)
   372  }
   373  
   374  func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string {
   375  	if state == nil {
   376  		return ""
   377  	}
   378  
   379  	ms := state.ModuleByPath(modPath)
   380  	if ms == nil {
   381  		return ""
   382  	}
   383  
   384  	outputs := ms.Outputs
   385  	outputBuf := new(bytes.Buffer)
   386  	if len(outputs) > 0 {
   387  		schemaMap := make(map[string]*config.Output)
   388  		if schema != nil {
   389  			for _, s := range schema {
   390  				schemaMap[s.Name] = s
   391  			}
   392  		}
   393  
   394  		if includeHeader {
   395  			outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
   396  		}
   397  
   398  		// Output the outputs in alphabetical order
   399  		keyLen := 0
   400  		ks := make([]string, 0, len(outputs))
   401  		for key, _ := range outputs {
   402  			ks = append(ks, key)
   403  			if len(key) > keyLen {
   404  				keyLen = len(key)
   405  			}
   406  		}
   407  		sort.Strings(ks)
   408  
   409  		for _, k := range ks {
   410  			schema, ok := schemaMap[k]
   411  			if ok && schema.Sensitive {
   412  				outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
   413  				continue
   414  			}
   415  
   416  			v := outputs[k]
   417  			switch typedV := v.Value.(type) {
   418  			case string:
   419  				outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
   420  			case []interface{}:
   421  				outputBuf.WriteString(formatListOutput("", k, typedV))
   422  				outputBuf.WriteString("\n")
   423  			case map[string]interface{}:
   424  				outputBuf.WriteString(formatMapOutput("", k, typedV))
   425  				outputBuf.WriteString("\n")
   426  			}
   427  		}
   428  	}
   429  
   430  	return strings.TrimSpace(outputBuf.String())
   431  }
   432  
   433  const outputInterrupt = `Interrupt received.
   434  Please wait for Terraform to exit or data loss may occur.
   435  Gracefully shutting down...`