forked from hashicorp/packer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
230 lines (195 loc) · 6.04 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
package main
import (
"encoding/json"
"io"
"log"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/mitchellh/osext"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer/plugin"
)
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
}
// ConfigFile returns the default path to the configuration file. On
// Unix-like systems this is the ".packerconfig" file in the home directory.
// On Windows, this is the "packer.config" file in the application data
// directory.
func ConfigFile() (string, error) {
return configFile()
}
// ConfigDir returns the configuration directory for Packer.
func ConfigDir() (string, error) {
return configDir()
}
// 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 {
// 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 := 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
}
}
// Last, look in the CWD.
if err := c.discover("."); 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) 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))
}
}
// 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)
config.Managed = true
config.MinPort = c.PluginMinPort
config.MaxPort = c.PluginMaxPort
return plugin.NewClient(&config)
}