github.com/aclaygray/packer@v1.3.2/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/hashicorp/packer/command" 15 "github.com/hashicorp/packer/packer" 16 "github.com/hashicorp/packer/packer/plugin" 17 "github.com/kardianos/osext" 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 return c.discoverSingle( 159 filepath.Join(path, "packer-provisioner-*"), &c.Provisioners) 160 } 161 162 func (c *config) discoverSingle(glob string, m *map[string]string) error { 163 matches, err := filepath.Glob(glob) 164 if err != nil { 165 return err 166 } 167 168 if *m == nil { 169 *m = make(map[string]string) 170 } 171 172 prefix := filepath.Base(glob) 173 prefix = prefix[:strings.Index(prefix, "*")] 174 for _, match := range matches { 175 file := filepath.Base(match) 176 177 // One Windows, ignore any plugins that don't end in .exe. 178 // We could do a full PATHEXT parse, but this is probably good enough. 179 if runtime.GOOS == "windows" && strings.ToLower(filepath.Ext(file)) != ".exe" { 180 log.Printf( 181 "[DEBUG] Ignoring plugin match %s, no exe extension", 182 match) 183 continue 184 } 185 186 // If the filename has a ".", trim up to there 187 if idx := strings.Index(file, "."); idx >= 0 { 188 file = file[:idx] 189 } 190 191 // Look for foo-bar-baz. The plugin name is "baz" 192 plugin := file[len(prefix):] 193 log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match) 194 (*m)[plugin] = match 195 } 196 197 return nil 198 } 199 200 func (c *config) discoverInternal() error { 201 // Get the packer binary path 202 packerPath, err := osext.Executable() 203 if err != nil { 204 log.Printf("[ERR] Error loading exe directory: %s", err) 205 return err 206 } 207 208 for builder := range command.Builders { 209 _, found := (c.Builders)[builder] 210 if !found { 211 log.Printf("Using internal plugin for %s", builder) 212 (c.Builders)[builder] = fmt.Sprintf("%s%splugin%spacker-builder-%s", 213 packerPath, PACKERSPACE, PACKERSPACE, builder) 214 } 215 } 216 217 for provisioner := range command.Provisioners { 218 _, found := (c.Provisioners)[provisioner] 219 if !found { 220 log.Printf("Using internal plugin for %s", provisioner) 221 (c.Provisioners)[provisioner] = fmt.Sprintf( 222 "%s%splugin%spacker-provisioner-%s", 223 packerPath, PACKERSPACE, PACKERSPACE, provisioner) 224 } 225 } 226 227 for postProcessor := range command.PostProcessors { 228 _, found := (c.PostProcessors)[postProcessor] 229 if !found { 230 log.Printf("Using internal plugin for %s", postProcessor) 231 (c.PostProcessors)[postProcessor] = fmt.Sprintf( 232 "%s%splugin%spacker-post-processor-%s", 233 packerPath, PACKERSPACE, PACKERSPACE, postProcessor) 234 } 235 } 236 237 return nil 238 } 239 240 func (c *config) pluginClient(path string) *plugin.Client { 241 originalPath := path 242 243 // First attempt to find the executable by consulting the PATH. 244 path, err := exec.LookPath(path) 245 if err != nil { 246 // If that doesn't work, look for it in the same directory 247 // as the `packer` executable (us). 248 log.Printf("Plugin could not be found. Checking same directory as executable.") 249 exePath, err := osext.Executable() 250 if err != nil { 251 log.Printf("Couldn't get current exe path: %s", err) 252 } else { 253 log.Printf("Current exe path: %s", exePath) 254 path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath)) 255 } 256 } 257 258 // Check for special case using `packer plugin PLUGIN` 259 args := []string{} 260 if strings.Contains(path, PACKERSPACE) { 261 parts := strings.Split(path, PACKERSPACE) 262 path = parts[0] 263 args = parts[1:] 264 } 265 266 // If everything failed, just use the original path and let the error 267 // bubble through. 268 if path == "" { 269 path = originalPath 270 } 271 272 log.Printf("Creating plugin client for path: %s", path) 273 var config plugin.ClientConfig 274 config.Cmd = exec.Command(path, args...) 275 config.Managed = true 276 config.MinPort = c.PluginMinPort 277 config.MaxPort = c.PluginMaxPort 278 return plugin.NewClient(&config) 279 }