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