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