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