github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/packer/environment.go (about)

     1  // The packer package contains the core components of Packer.
     2  package packer
     3  
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"sort"
    10  	"strings"
    11  	"sync"
    12  )
    13  
    14  // The function type used to lookup Builder implementations.
    15  type BuilderFunc func(name string) (Builder, error)
    16  
    17  // The function type used to lookup Command implementations.
    18  type CommandFunc func(name string) (Command, error)
    19  
    20  // The function type used to lookup Hook implementations.
    21  type HookFunc func(name string) (Hook, error)
    22  
    23  // The function type used to lookup PostProcessor implementations.
    24  type PostProcessorFunc func(name string) (PostProcessor, error)
    25  
    26  // The function type used to lookup Provisioner implementations.
    27  type ProvisionerFunc func(name string) (Provisioner, error)
    28  
    29  // ComponentFinder is a struct that contains the various function
    30  // pointers necessary to look up components of Packer such as builders,
    31  // commands, etc.
    32  type ComponentFinder struct {
    33  	Builder       BuilderFunc
    34  	Command       CommandFunc
    35  	Hook          HookFunc
    36  	PostProcessor PostProcessorFunc
    37  	Provisioner   ProvisionerFunc
    38  }
    39  
    40  // The environment interface provides access to the configuration and
    41  // state of a single Packer run.
    42  //
    43  // It allows for things such as executing CLI commands, getting the
    44  // list of available builders, and more.
    45  type Environment interface {
    46  	Builder(string) (Builder, error)
    47  	Cache() Cache
    48  	Cli([]string) (int, error)
    49  	Hook(string) (Hook, error)
    50  	PostProcessor(string) (PostProcessor, error)
    51  	Provisioner(string) (Provisioner, error)
    52  	Ui() Ui
    53  }
    54  
    55  // An implementation of an Environment that represents the Packer core
    56  // environment.
    57  type coreEnvironment struct {
    58  	cache      Cache
    59  	commands   []string
    60  	components ComponentFinder
    61  	ui         Ui
    62  }
    63  
    64  // This struct configures new environments.
    65  type EnvironmentConfig struct {
    66  	Cache      Cache
    67  	Commands   []string
    68  	Components ComponentFinder
    69  	Ui         Ui
    70  }
    71  
    72  type helpCommandEntry struct {
    73  	i        int
    74  	key      string
    75  	synopsis string
    76  }
    77  
    78  // DefaultEnvironmentConfig returns a default EnvironmentConfig that can
    79  // be used to create a new enviroment with NewEnvironment with sane defaults.
    80  func DefaultEnvironmentConfig() *EnvironmentConfig {
    81  	config := &EnvironmentConfig{}
    82  	config.Commands = make([]string, 0)
    83  	config.Ui = &BasicUi{
    84  		Reader:      os.Stdin,
    85  		Writer:      os.Stdout,
    86  		ErrorWriter: os.Stdout,
    87  	}
    88  
    89  	return config
    90  }
    91  
    92  // This creates a new environment
    93  func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error) {
    94  	if config == nil {
    95  		err = errors.New("config must be given to initialize environment")
    96  		return
    97  	}
    98  
    99  	env := &coreEnvironment{}
   100  	env.cache = config.Cache
   101  	env.commands = config.Commands
   102  	env.components = config.Components
   103  	env.ui = config.Ui
   104  
   105  	// We want to make sure the components have valid function pointers.
   106  	// If a function pointer was not given, we assume that the function
   107  	// will just return a nil component.
   108  	if env.components.Builder == nil {
   109  		env.components.Builder = func(string) (Builder, error) { return nil, nil }
   110  	}
   111  
   112  	if env.components.Command == nil {
   113  		env.components.Command = func(string) (Command, error) { return nil, nil }
   114  	}
   115  
   116  	if env.components.Hook == nil {
   117  		env.components.Hook = func(string) (Hook, error) { return nil, nil }
   118  	}
   119  
   120  	if env.components.PostProcessor == nil {
   121  		env.components.PostProcessor = func(string) (PostProcessor, error) { return nil, nil }
   122  	}
   123  
   124  	if env.components.Provisioner == nil {
   125  		env.components.Provisioner = func(string) (Provisioner, error) { return nil, nil }
   126  	}
   127  
   128  	// The default cache is just the system temporary directory
   129  	if env.cache == nil {
   130  		env.cache = &FileCache{CacheDir: os.TempDir()}
   131  	}
   132  
   133  	resultEnv = env
   134  	return
   135  }
   136  
   137  // Returns a builder of the given name that is registered with this
   138  // environment.
   139  func (e *coreEnvironment) Builder(name string) (b Builder, err error) {
   140  	b, err = e.components.Builder(name)
   141  	if err != nil {
   142  		return
   143  	}
   144  
   145  	if b == nil {
   146  		err = fmt.Errorf("No builder returned for name: %s", name)
   147  	}
   148  
   149  	return
   150  }
   151  
   152  // Returns the cache for this environment
   153  func (e *coreEnvironment) Cache() Cache {
   154  	return e.cache
   155  }
   156  
   157  // Returns a hook of the given name that is registered with this
   158  // environment.
   159  func (e *coreEnvironment) Hook(name string) (h Hook, err error) {
   160  	h, err = e.components.Hook(name)
   161  	if err != nil {
   162  		return
   163  	}
   164  
   165  	if h == nil {
   166  		err = fmt.Errorf("No hook returned for name: %s", name)
   167  	}
   168  
   169  	return
   170  }
   171  
   172  // Returns a PostProcessor for the given name that is registered with this
   173  // environment.
   174  func (e *coreEnvironment) PostProcessor(name string) (p PostProcessor, err error) {
   175  	p, err = e.components.PostProcessor(name)
   176  	if err != nil {
   177  		return
   178  	}
   179  
   180  	if p == nil {
   181  		err = fmt.Errorf("No post processor found for name: %s", name)
   182  	}
   183  
   184  	return
   185  }
   186  
   187  // Returns a provisioner for the given name that is registered with this
   188  // environment.
   189  func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) {
   190  	p, err = e.components.Provisioner(name)
   191  	if err != nil {
   192  		return
   193  	}
   194  
   195  	if p == nil {
   196  		err = fmt.Errorf("No provisioner returned for name: %s", name)
   197  	}
   198  
   199  	return
   200  }
   201  
   202  // Executes a command as if it was typed on the command-line interface.
   203  // The return value is the exit code of the command.
   204  func (e *coreEnvironment) Cli(args []string) (result int, err error) {
   205  	log.Printf("Environment.Cli: %#v\n", args)
   206  
   207  	// If we have no arguments, just short-circuit here and print the help
   208  	if len(args) == 0 {
   209  		e.printHelp()
   210  		return 1, nil
   211  	}
   212  
   213  	// This variable will track whether or not we're supposed to print
   214  	// the help or not.
   215  	isHelp := false
   216  	for _, arg := range args {
   217  		if arg == "-h" || arg == "--help" {
   218  			isHelp = true
   219  			break
   220  		}
   221  	}
   222  
   223  	// Trim up to the command name
   224  	for i, v := range args {
   225  		if len(v) > 0 && v[0] != '-' {
   226  			args = args[i:]
   227  			break
   228  		}
   229  	}
   230  
   231  	log.Printf("command + args: %#v", args)
   232  
   233  	version := args[0] == "version"
   234  	if !version {
   235  		for _, arg := range args {
   236  			if arg == "--version" || arg == "-v" {
   237  				version = true
   238  				break
   239  			}
   240  		}
   241  	}
   242  
   243  	var command Command
   244  	if version {
   245  		command = new(versionCommand)
   246  	}
   247  
   248  	if command == nil {
   249  		command, err = e.components.Command(args[0])
   250  		if err != nil {
   251  			return
   252  		}
   253  
   254  		// If we still don't have a command, show the help.
   255  		if command == nil {
   256  			e.ui.Error(fmt.Sprintf("Unknown command: %s\n", args[0]))
   257  			e.printHelp()
   258  			return 1, nil
   259  		}
   260  	}
   261  
   262  	// If we're supposed to print help, then print the help of the
   263  	// command rather than running it.
   264  	if isHelp {
   265  		e.ui.Say(command.Help())
   266  		return 0, nil
   267  	}
   268  
   269  	log.Printf("Executing command: %s\n", args[0])
   270  	return command.Run(e, args[1:]), nil
   271  }
   272  
   273  // Prints the CLI help to the UI.
   274  func (e *coreEnvironment) printHelp() {
   275  	// Created a sorted slice of the map keys and record the longest
   276  	// command name so we can better format the output later.
   277  	maxKeyLen := 0
   278  	for _, command := range e.commands {
   279  		if len(command) > maxKeyLen {
   280  			maxKeyLen = len(command)
   281  		}
   282  	}
   283  
   284  	// Sort the keys
   285  	sort.Strings(e.commands)
   286  
   287  	// Create the communication/sync mechanisms to get the synopsis' of
   288  	// the various commands. We do this in parallel since the overhead
   289  	// of the subprocess underneath is very expensive and this speeds things
   290  	// up an incredible amount.
   291  	var wg sync.WaitGroup
   292  	ch := make(chan *helpCommandEntry)
   293  
   294  	for i, key := range e.commands {
   295  		wg.Add(1)
   296  
   297  		// Get the synopsis in a goroutine since it may take awhile
   298  		// to subprocess out.
   299  		go func(i int, key string) {
   300  			defer wg.Done()
   301  			var synopsis string
   302  			command, err := e.components.Command(key)
   303  			if err != nil {
   304  				synopsis = fmt.Sprintf("Error loading command: %s", err.Error())
   305  			} else if command == nil {
   306  				return
   307  			} else {
   308  				synopsis = command.Synopsis()
   309  			}
   310  
   311  			// Pad the key with spaces so that they're all the same width
   312  			key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key)))
   313  
   314  			// Output the command and the synopsis
   315  			ch <- &helpCommandEntry{
   316  				i:        i,
   317  				key:      key,
   318  				synopsis: synopsis,
   319  			}
   320  		}(i, key)
   321  	}
   322  
   323  	e.ui.Say("usage: packer [--version] [--help] <command> [<args>]\n")
   324  	e.ui.Say("Available commands are:")
   325  
   326  	// Make a goroutine that just waits for all the synopsis gathering
   327  	// to complete, and then output it.
   328  	synopsisDone := make(chan struct{})
   329  	go func() {
   330  		defer close(synopsisDone)
   331  		entries := make([]string, len(e.commands))
   332  
   333  		for entry := range ch {
   334  			e.ui.Machine("command", entry.key, entry.synopsis)
   335  			message := fmt.Sprintf("    %s    %s", entry.key, entry.synopsis)
   336  			entries[entry.i] = message
   337  		}
   338  
   339  		for _, message := range entries {
   340  			if message != "" {
   341  				e.ui.Say(message)
   342  			}
   343  		}
   344  	}()
   345  
   346  	// Wait to complete getting the synopsis' then close the channel
   347  	wg.Wait()
   348  	close(ch)
   349  	<-synopsisDone
   350  
   351  	e.ui.Say("\nGlobally recognized options:")
   352  	e.ui.Say("    -machine-readable    Machine-readable output format.")
   353  }
   354  
   355  // Returns the UI for the environment. The UI is the interface that should
   356  // be used for all communication with the outside world.
   357  func (e *coreEnvironment) Ui() Ui {
   358  	return e.ui
   359  }