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 }