github.com/robertreppel/terraform@v0.11.8-0.20180412164320-05291ab9822d/commands.go (about)

     1  package main
     2  
     3  import (
     4  	"log"
     5  	"os"
     6  	"os/signal"
     7  
     8  	"github.com/hashicorp/terraform/command"
     9  	pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery"
    10  	"github.com/hashicorp/terraform/svchost"
    11  	"github.com/hashicorp/terraform/svchost/auth"
    12  	"github.com/hashicorp/terraform/svchost/disco"
    13  	"github.com/mitchellh/cli"
    14  )
    15  
    16  // runningInAutomationEnvName gives the name of an environment variable that
    17  // can be set to any non-empty value in order to suppress certain messages
    18  // that assume that Terraform is being run from a command prompt.
    19  const runningInAutomationEnvName = "TF_IN_AUTOMATION"
    20  
    21  // Commands is the mapping of all the available Terraform commands.
    22  var Commands map[string]cli.CommandFactory
    23  var PlumbingCommands map[string]struct{}
    24  
    25  // Ui is the cli.Ui used for communicating to the outside world.
    26  var Ui cli.Ui
    27  
    28  const (
    29  	ErrorPrefix  = "e:"
    30  	OutputPrefix = "o:"
    31  )
    32  
    33  func initCommands(config *Config) {
    34  	var inAutomation bool
    35  	if v := os.Getenv(runningInAutomationEnvName); v != "" {
    36  		inAutomation = true
    37  	}
    38  
    39  	credsSrc := credentialsSource(config)
    40  	services := disco.NewDisco()
    41  	services.SetCredentialsSource(credsSrc)
    42  	for userHost, hostConfig := range config.Hosts {
    43  		host, err := svchost.ForComparison(userHost)
    44  		if err != nil {
    45  			// We expect the config was already validated by the time we get
    46  			// here, so we'll just ignore invalid hostnames.
    47  			continue
    48  		}
    49  		services.ForceHostServices(host, hostConfig.Services)
    50  	}
    51  
    52  	dataDir := os.Getenv("TF_DATA_DIR")
    53  
    54  	meta := command.Meta{
    55  		Color:            true,
    56  		GlobalPluginDirs: globalPluginDirs(),
    57  		PluginOverrides:  &PluginOverrides,
    58  		Ui:               Ui,
    59  
    60  		Services:    services,
    61  		Credentials: credsSrc,
    62  
    63  		RunningInAutomation: inAutomation,
    64  		PluginCacheDir:      config.PluginCacheDir,
    65  		OverrideDataDir:     dataDir,
    66  
    67  		ShutdownCh: makeShutdownCh(),
    68  	}
    69  
    70  	// The command list is included in the terraform -help
    71  	// output, which is in turn included in the docs at
    72  	// website/source/docs/commands/index.html.markdown; if you
    73  	// add, remove or reclassify commands then consider updating
    74  	// that to match.
    75  
    76  	PlumbingCommands = map[string]struct{}{
    77  		"state":        struct{}{}, // includes all subcommands
    78  		"debug":        struct{}{}, // includes all subcommands
    79  		"force-unlock": struct{}{},
    80  	}
    81  
    82  	Commands = map[string]cli.CommandFactory{
    83  		"apply": func() (cli.Command, error) {
    84  			return &command.ApplyCommand{
    85  				Meta: meta,
    86  			}, nil
    87  		},
    88  
    89  		"console": func() (cli.Command, error) {
    90  			return &command.ConsoleCommand{
    91  				Meta: meta,
    92  			}, nil
    93  		},
    94  
    95  		"destroy": func() (cli.Command, error) {
    96  			return &command.ApplyCommand{
    97  				Meta:    meta,
    98  				Destroy: true,
    99  			}, nil
   100  		},
   101  
   102  		"env": func() (cli.Command, error) {
   103  			return &command.WorkspaceCommand{
   104  				Meta:       meta,
   105  				LegacyName: true,
   106  			}, nil
   107  		},
   108  
   109  		"env list": func() (cli.Command, error) {
   110  			return &command.WorkspaceListCommand{
   111  				Meta:       meta,
   112  				LegacyName: true,
   113  			}, nil
   114  		},
   115  
   116  		"env select": func() (cli.Command, error) {
   117  			return &command.WorkspaceSelectCommand{
   118  				Meta:       meta,
   119  				LegacyName: true,
   120  			}, nil
   121  		},
   122  
   123  		"env new": func() (cli.Command, error) {
   124  			return &command.WorkspaceNewCommand{
   125  				Meta:       meta,
   126  				LegacyName: true,
   127  			}, nil
   128  		},
   129  
   130  		"env delete": func() (cli.Command, error) {
   131  			return &command.WorkspaceDeleteCommand{
   132  				Meta:       meta,
   133  				LegacyName: true,
   134  			}, nil
   135  		},
   136  
   137  		"fmt": func() (cli.Command, error) {
   138  			return &command.FmtCommand{
   139  				Meta: meta,
   140  			}, nil
   141  		},
   142  
   143  		"get": func() (cli.Command, error) {
   144  			return &command.GetCommand{
   145  				Meta: meta,
   146  			}, nil
   147  		},
   148  
   149  		"graph": func() (cli.Command, error) {
   150  			return &command.GraphCommand{
   151  				Meta: meta,
   152  			}, nil
   153  		},
   154  
   155  		"import": func() (cli.Command, error) {
   156  			return &command.ImportCommand{
   157  				Meta: meta,
   158  			}, nil
   159  		},
   160  
   161  		"init": func() (cli.Command, error) {
   162  			return &command.InitCommand{
   163  				Meta: meta,
   164  			}, nil
   165  		},
   166  
   167  		"internal-plugin": func() (cli.Command, error) {
   168  			return &command.InternalPluginCommand{
   169  				Meta: meta,
   170  			}, nil
   171  		},
   172  
   173  		"output": func() (cli.Command, error) {
   174  			return &command.OutputCommand{
   175  				Meta: meta,
   176  			}, nil
   177  		},
   178  
   179  		"plan": func() (cli.Command, error) {
   180  			return &command.PlanCommand{
   181  				Meta: meta,
   182  			}, nil
   183  		},
   184  
   185  		"providers": func() (cli.Command, error) {
   186  			return &command.ProvidersCommand{
   187  				Meta: meta,
   188  			}, nil
   189  		},
   190  
   191  		"push": func() (cli.Command, error) {
   192  			return &command.PushCommand{
   193  				Meta: meta,
   194  			}, nil
   195  		},
   196  
   197  		"refresh": func() (cli.Command, error) {
   198  			return &command.RefreshCommand{
   199  				Meta: meta,
   200  			}, nil
   201  		},
   202  
   203  		"show": func() (cli.Command, error) {
   204  			return &command.ShowCommand{
   205  				Meta: meta,
   206  			}, nil
   207  		},
   208  
   209  		"taint": func() (cli.Command, error) {
   210  			return &command.TaintCommand{
   211  				Meta: meta,
   212  			}, nil
   213  		},
   214  
   215  		"validate": func() (cli.Command, error) {
   216  			return &command.ValidateCommand{
   217  				Meta: meta,
   218  			}, nil
   219  		},
   220  
   221  		"version": func() (cli.Command, error) {
   222  			return &command.VersionCommand{
   223  				Meta:              meta,
   224  				Revision:          GitCommit,
   225  				Version:           Version,
   226  				VersionPrerelease: VersionPrerelease,
   227  				CheckFunc:         commandVersionCheck,
   228  			}, nil
   229  		},
   230  
   231  		"untaint": func() (cli.Command, error) {
   232  			return &command.UntaintCommand{
   233  				Meta: meta,
   234  			}, nil
   235  		},
   236  
   237  		"workspace": func() (cli.Command, error) {
   238  			return &command.WorkspaceCommand{
   239  				Meta: meta,
   240  			}, nil
   241  		},
   242  
   243  		"workspace list": func() (cli.Command, error) {
   244  			return &command.WorkspaceListCommand{
   245  				Meta: meta,
   246  			}, nil
   247  		},
   248  
   249  		"workspace select": func() (cli.Command, error) {
   250  			return &command.WorkspaceSelectCommand{
   251  				Meta: meta,
   252  			}, nil
   253  		},
   254  
   255  		"workspace show": func() (cli.Command, error) {
   256  			return &command.WorkspaceShowCommand{
   257  				Meta: meta,
   258  			}, nil
   259  		},
   260  
   261  		"workspace new": func() (cli.Command, error) {
   262  			return &command.WorkspaceNewCommand{
   263  				Meta: meta,
   264  			}, nil
   265  		},
   266  
   267  		"workspace delete": func() (cli.Command, error) {
   268  			return &command.WorkspaceDeleteCommand{
   269  				Meta: meta,
   270  			}, nil
   271  		},
   272  
   273  		//-----------------------------------------------------------
   274  		// Plumbing
   275  		//-----------------------------------------------------------
   276  
   277  		"debug": func() (cli.Command, error) {
   278  			return &command.DebugCommand{
   279  				Meta: meta,
   280  			}, nil
   281  		},
   282  
   283  		"debug json2dot": func() (cli.Command, error) {
   284  			return &command.DebugJSON2DotCommand{
   285  				Meta: meta,
   286  			}, nil
   287  		},
   288  
   289  		"force-unlock": func() (cli.Command, error) {
   290  			return &command.UnlockCommand{
   291  				Meta: meta,
   292  			}, nil
   293  		},
   294  
   295  		"state": func() (cli.Command, error) {
   296  			return &command.StateCommand{}, nil
   297  		},
   298  
   299  		"state list": func() (cli.Command, error) {
   300  			return &command.StateListCommand{
   301  				Meta: meta,
   302  			}, nil
   303  		},
   304  
   305  		"state rm": func() (cli.Command, error) {
   306  			return &command.StateRmCommand{
   307  				StateMeta: command.StateMeta{
   308  					Meta: meta,
   309  				},
   310  			}, nil
   311  		},
   312  
   313  		"state mv": func() (cli.Command, error) {
   314  			return &command.StateMvCommand{
   315  				StateMeta: command.StateMeta{
   316  					Meta: meta,
   317  				},
   318  			}, nil
   319  		},
   320  
   321  		"state pull": func() (cli.Command, error) {
   322  			return &command.StatePullCommand{
   323  				Meta: meta,
   324  			}, nil
   325  		},
   326  
   327  		"state push": func() (cli.Command, error) {
   328  			return &command.StatePushCommand{
   329  				Meta: meta,
   330  			}, nil
   331  		},
   332  
   333  		"state show": func() (cli.Command, error) {
   334  			return &command.StateShowCommand{
   335  				Meta: meta,
   336  			}, nil
   337  		},
   338  	}
   339  }
   340  
   341  // makeShutdownCh creates an interrupt listener and returns a channel.
   342  // A message will be sent on the channel for every interrupt received.
   343  func makeShutdownCh() <-chan struct{} {
   344  	resultCh := make(chan struct{})
   345  
   346  	signalCh := make(chan os.Signal, 4)
   347  	signal.Notify(signalCh, ignoreSignals...)
   348  	signal.Notify(signalCh, forwardSignals...)
   349  	go func() {
   350  		for {
   351  			<-signalCh
   352  			resultCh <- struct{}{}
   353  		}
   354  	}()
   355  
   356  	return resultCh
   357  }
   358  
   359  func credentialsSource(config *Config) auth.CredentialsSource {
   360  	creds := auth.NoCredentials
   361  	if len(config.Credentials) > 0 {
   362  		staticTable := map[svchost.Hostname]map[string]interface{}{}
   363  		for userHost, creds := range config.Credentials {
   364  			host, err := svchost.ForComparison(userHost)
   365  			if err != nil {
   366  				// We expect the config was already validated by the time we get
   367  				// here, so we'll just ignore invalid hostnames.
   368  				continue
   369  			}
   370  			staticTable[host] = creds
   371  		}
   372  		creds = auth.StaticCredentialsSource(staticTable)
   373  	}
   374  
   375  	for helperType, helperConfig := range config.CredentialsHelpers {
   376  		log.Printf("[DEBUG] Searching for credentials helper named %q", helperType)
   377  		available := pluginDiscovery.FindPlugins("credentials", globalPluginDirs())
   378  		available = available.WithName(helperType)
   379  		if available.Count() == 0 {
   380  			log.Printf("[ERROR] Unable to find credentials helper %q; ignoring", helperType)
   381  			break
   382  		}
   383  
   384  		selected := available.Newest()
   385  
   386  		helperSource := auth.HelperProgramCredentialsSource(selected.Path, helperConfig.Args...)
   387  		creds = auth.Credentials{
   388  			creds,
   389  			auth.CachingCredentialsSource(helperSource), // cached because external operation may be slow/expensive
   390  		}
   391  
   392  		// There should only be zero or one "credentials_helper" blocks. We
   393  		// assume that the config was validated earlier and so we don't check
   394  		// for extras here.
   395  		break
   396  	}
   397  
   398  	return creds
   399  }