github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/config.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "strings" 13 14 "github.com/kardianos/osext" 15 "github.com/mitchellh/packer/command" 16 "github.com/mitchellh/packer/packer" 17 "github.com/mitchellh/packer/packer/plugin" 18 ) 19 20 // PACKERSPACE is used to represent the spaces that separate args for a command 21 // without being confused with spaces in the path to the command itself. 22 const PACKERSPACE = "-PACKERSPACE-" 23 24 type config struct { 25 DisableCheckpoint bool `json:"disable_checkpoint"` 26 DisableCheckpointSignature bool `json:"disable_checkpoint_signature"` 27 PluginMinPort uint 28 PluginMaxPort uint 29 30 Builders map[string]string 31 PostProcessors map[string]string `json:"post-processors"` 32 Provisioners map[string]string 33 } 34 35 // Decodes configuration in JSON format from the given io.Reader into 36 // the config object pointed to. 37 func decodeConfig(r io.Reader, c *config) error { 38 decoder := json.NewDecoder(r) 39 return decoder.Decode(c) 40 } 41 42 // Discover discovers plugins. 43 // 44 // Search the directory of the executable, then the plugins directory, and 45 // finally the CWD, in that order. Any conflicts will overwrite previously 46 // found plugins, in that order. 47 // Hence, the priority order is the reverse of the search order - i.e., the 48 // CWD has the highest priority. 49 func (c *config) Discover() error { 50 // If we are already inside a plugin process we should not need to 51 // discover anything. 52 if os.Getenv(plugin.MagicCookieKey) == plugin.MagicCookieValue { 53 return nil 54 } 55 56 // First, look in the same directory as the executable. 57 exePath, err := osext.Executable() 58 if err != nil { 59 log.Printf("[ERR] Error loading exe directory: %s", err) 60 } else { 61 if err := c.discover(filepath.Dir(exePath)); err != nil { 62 return err 63 } 64 } 65 66 // Next, look in the plugins directory. 67 dir, err := packer.ConfigDir() 68 if err != nil { 69 log.Printf("[ERR] Error loading config directory: %s", err) 70 } else { 71 if err := c.discover(filepath.Join(dir, "plugins")); err != nil { 72 return err 73 } 74 } 75 76 // Next, look in the CWD. 77 if err := c.discover("."); err != nil { 78 return err 79 } 80 81 // Finally, try to use an internal plugin. Note that this will not override 82 // any previously-loaded plugins. 83 if err := c.discoverInternal(); err != nil { 84 return err 85 } 86 87 return nil 88 } 89 90 // This is a proper packer.BuilderFunc that can be used to load packer.Builder 91 // implementations from the defined plugins. 92 func (c *config) LoadBuilder(name string) (packer.Builder, error) { 93 log.Printf("Loading builder: %s\n", name) 94 bin, ok := c.Builders[name] 95 if !ok { 96 log.Printf("Builder not found: %s\n", name) 97 return nil, nil 98 } 99 100 return c.pluginClient(bin).Builder() 101 } 102 103 // This is a proper implementation of packer.HookFunc that can be used 104 // to load packer.Hook implementations from the defined plugins. 105 func (c *config) LoadHook(name string) (packer.Hook, error) { 106 log.Printf("Loading hook: %s\n", name) 107 return c.pluginClient(name).Hook() 108 } 109 110 // This is a proper packer.PostProcessorFunc that can be used to load 111 // packer.PostProcessor implementations from defined plugins. 112 func (c *config) LoadPostProcessor(name string) (packer.PostProcessor, error) { 113 log.Printf("Loading post-processor: %s", name) 114 bin, ok := c.PostProcessors[name] 115 if !ok { 116 log.Printf("Post-processor not found: %s", name) 117 return nil, nil 118 } 119 120 return c.pluginClient(bin).PostProcessor() 121 } 122 123 // This is a proper packer.ProvisionerFunc that can be used to load 124 // packer.Provisioner implementations from defined plugins. 125 func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) { 126 log.Printf("Loading provisioner: %s\n", name) 127 bin, ok := c.Provisioners[name] 128 if !ok { 129 log.Printf("Provisioner not found: %s\n", name) 130 return nil, nil 131 } 132 133 return c.pluginClient(bin).Provisioner() 134 } 135 136 func (c *config) discover(path string) error { 137 var err error 138 139 if !filepath.IsAbs(path) { 140 path, err = filepath.Abs(path) 141 if err != nil { 142 return err 143 } 144 } 145 146 err = c.discoverSingle( 147 filepath.Join(path, "packer-builder-*"), &c.Builders) 148 if err != nil { 149 return err 150 } 151 152 err = c.discoverSingle( 153 filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors) 154 if err != nil { 155 return err 156 } 157 158 err = c.discoverSingle( 159 filepath.Join(path, "packer-provisioner-*"), &c.Provisioners) 160 if err != nil { 161 return err 162 } 163 164 return nil 165 } 166 167 func (c *config) discoverSingle(glob string, m *map[string]string) error { 168 matches, err := filepath.Glob(glob) 169 if err != nil { 170 return err 171 } 172 173 if *m == nil { 174 *m = make(map[string]string) 175 } 176 177 prefix := filepath.Base(glob) 178 prefix = prefix[:strings.Index(prefix, "*")] 179 for _, match := range matches { 180 file := filepath.Base(match) 181 182 // One Windows, ignore any plugins that don't end in .exe. 183 // We could do a full PATHEXT parse, but this is probably good enough. 184 if runtime.GOOS == "windows" && strings.ToLower(filepath.Ext(file)) != ".exe" { 185 log.Printf( 186 "[DEBUG] Ignoring plugin match %s, no exe extension", 187 match) 188 continue 189 } 190 191 // If the filename has a ".", trim up to there 192 if idx := strings.Index(file, "."); idx >= 0 { 193 file = file[:idx] 194 } 195 196 // Look for foo-bar-baz. The plugin name is "baz" 197 plugin := file[len(prefix):] 198 log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match) 199 (*m)[plugin] = match 200 } 201 202 return nil 203 } 204 205 func (c *config) discoverInternal() error { 206 // Get the packer binary path 207 packerPath, err := osext.Executable() 208 if err != nil { 209 log.Printf("[ERR] Error loading exe directory: %s", err) 210 return err 211 } 212 213 for builder := range command.Builders { 214 _, found := (c.Builders)[builder] 215 if !found { 216 log.Printf("Using internal plugin for %s", builder) 217 (c.Builders)[builder] = fmt.Sprintf("%s%splugin%spacker-builder-%s", 218 packerPath, PACKERSPACE, PACKERSPACE, builder) 219 } 220 } 221 222 for provisioner := range command.Provisioners { 223 _, found := (c.Provisioners)[provisioner] 224 if !found { 225 log.Printf("Using internal plugin for %s", provisioner) 226 (c.Provisioners)[provisioner] = fmt.Sprintf( 227 "%s%splugin%spacker-provisioner-%s", 228 packerPath, PACKERSPACE, PACKERSPACE, provisioner) 229 } 230 } 231 232 for postProcessor := range command.PostProcessors { 233 _, found := (c.PostProcessors)[postProcessor] 234 if !found { 235 log.Printf("Using internal plugin for %s", postProcessor) 236 (c.PostProcessors)[postProcessor] = fmt.Sprintf( 237 "%s%splugin%spacker-post-processor-%s", 238 packerPath, PACKERSPACE, PACKERSPACE, postProcessor) 239 } 240 } 241 242 return nil 243 } 244 245 func (c *config) pluginClient(path string) *plugin.Client { 246 originalPath := path 247 248 // First attempt to find the executable by consulting the PATH. 249 path, err := exec.LookPath(path) 250 if err != nil { 251 // If that doesn't work, look for it in the same directory 252 // as the `packer` executable (us). 253 log.Printf("Plugin could not be found. Checking same directory as executable.") 254 exePath, err := osext.Executable() 255 if err != nil { 256 log.Printf("Couldn't get current exe path: %s", err) 257 } else { 258 log.Printf("Current exe path: %s", exePath) 259 path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath)) 260 } 261 } 262 263 // Check for special case using `packer plugin PLUGIN` 264 args := []string{} 265 if strings.Contains(path, PACKERSPACE) { 266 parts := strings.Split(path, PACKERSPACE) 267 path = parts[0] 268 args = parts[1:] 269 } 270 271 // If everything failed, just use the original path and let the error 272 // bubble through. 273 if path == "" { 274 path = originalPath 275 } 276 277 log.Printf("Creating plugin client for path: %s", path) 278 var config plugin.ClientConfig 279 config.Cmd = exec.Command(path, args...) 280 config.Managed = true 281 config.MinPort = c.PluginMinPort 282 config.MaxPort = c.PluginMaxPort 283 return plugin.NewClient(&config) 284 }