forked from hashicorp/packer
/
run_config.go
188 lines (154 loc) · 6.41 KB
/
run_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
package openstack
import (
"errors"
"fmt"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/template/interpolate"
)
// RunConfig contains configuration for running an instance from a source
// image and details on how to access that launched image.
type RunConfig struct {
Comm communicator.Config `mapstructure:",squash"`
SourceImage string `mapstructure:"source_image"`
SourceImageName string `mapstructure:"source_image_name"`
SourceImageFilters ImageFilter `mapstructure:"source_image_filter"`
Flavor string `mapstructure:"flavor"`
AvailabilityZone string `mapstructure:"availability_zone"`
RackconnectWait bool `mapstructure:"rackconnect_wait"`
FloatingIPNetwork string `mapstructure:"floating_ip_network"`
FloatingIP string `mapstructure:"floating_ip"`
ReuseIPs bool `mapstructure:"reuse_ips"`
SecurityGroups []string `mapstructure:"security_groups"`
Networks []string `mapstructure:"networks"`
Ports []string `mapstructure:"ports"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
InstanceName string `mapstructure:"instance_name"`
InstanceMetadata map[string]string `mapstructure:"instance_metadata"`
ConfigDrive bool `mapstructure:"config_drive"`
// Used for BC, value will be passed to the "floating_ip_network"
FloatingIPPool string `mapstructure:"floating_ip_pool"`
UseBlockStorageVolume bool `mapstructure:"use_blockstorage_volume"`
VolumeName string `mapstructure:"volume_name"`
VolumeType string `mapstructure:"volume_type"`
VolumeAvailabilityZone string `mapstructure:"volume_availability_zone"`
// Not really used, but here for BC
OpenstackProvider string `mapstructure:"openstack_provider"`
UseFloatingIp bool `mapstructure:"use_floating_ip"`
sourceImageOpts images.ListOpts
}
type ImageFilter struct {
Filters ImageFilterOptions `mapstructure:"filters"`
MostRecent bool `mapstructure:"most_recent"`
}
type ImageFilterOptions struct {
Name string `mapstructure:"name"`
Owner string `mapstructure:"owner"`
Tags []string `mapstructure:"tags"`
Visibility string `mapstructure:"visibility"`
}
func (f *ImageFilterOptions) Empty() bool {
return f.Name == "" && f.Owner == "" && len(f.Tags) == 0 && f.Visibility == ""
}
func (f *ImageFilterOptions) Build() (*images.ListOpts, error) {
opts := images.ListOpts{}
// Set defaults for status, member_status, and sort
opts.Status = images.ImageStatusActive
opts.MemberStatus = images.ImageMemberStatusAccepted
opts.Sort = "created_at:desc"
var err error
if f.Name != "" {
opts.Name = f.Name
}
if f.Owner != "" {
opts.Owner = f.Owner
}
if len(f.Tags) > 0 {
opts.Tags = f.Tags
}
if f.Visibility != "" {
v, err := getImageVisibility(f.Visibility)
if err == nil {
opts.Visibility = *v
}
}
return &opts, err
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// If we are not given an explicit ssh_keypair_name or
// ssh_private_key_file, then create a temporary one, but only if the
// temporary_key_pair_name has not been provided and we are not using
// ssh_password.
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
if c.FloatingIPPool != "" && c.FloatingIPNetwork == "" {
c.FloatingIPNetwork = c.FloatingIPPool
}
// Validation
errs := c.Comm.Prepare(ctx)
if c.Comm.SSHKeyPairName != "" {
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
errs = append(errs, errors.New("A ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
} else if c.Comm.SSHPrivateKeyFile == "" && !c.Comm.SSHAgentAuth {
errs = append(errs, errors.New("A ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
}
}
if c.SourceImage == "" && c.SourceImageName == "" && c.SourceImageFilters.Filters.Empty() {
errs = append(errs, errors.New("Either a source_image, a source_image_name, or source_image_filter must be specified"))
} else if len(c.SourceImage) > 0 && len(c.SourceImageName) > 0 {
errs = append(errs, errors.New("Only a source_image or a source_image_name can be specified, not both."))
}
if c.Flavor == "" {
errs = append(errs, errors.New("A flavor must be specified"))
}
if c.Comm.SSHIPVersion != "" && c.Comm.SSHIPVersion != "4" && c.Comm.SSHIPVersion != "6" {
errs = append(errs, errors.New("SSH IP version must be either 4 or 6"))
}
for key, value := range c.InstanceMetadata {
if len(key) > 255 {
errs = append(errs, fmt.Errorf("Instance metadata key too long (max 255 bytes): %s", key))
}
if len(value) > 255 {
errs = append(errs, fmt.Errorf("Instance metadata value too long (max 255 bytes): %s", value))
}
}
if c.UseBlockStorageVolume {
// Use Compute instance availability zone for the Block Storage volume if
// it's not provided.
if c.VolumeAvailabilityZone == "" {
c.VolumeAvailabilityZone = c.AvailabilityZone
}
// Use random name for the Block Storage volume if it's not provided.
if c.VolumeName == "" {
c.VolumeName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
}
// if neither ID or image name is provided outside the filter, build the filter
if len(c.SourceImage) == 0 && len(c.SourceImageName) == 0 {
listOpts, filterErr := c.SourceImageFilters.Filters.Build()
if filterErr != nil {
errs = append(errs, filterErr)
}
c.sourceImageOpts = *listOpts
}
return errs
}
// Retrieve the specific ImageVisibility using the exported const from images
func getImageVisibility(visibility string) (*images.ImageVisibility, error) {
visibilities := [...]images.ImageVisibility{
images.ImageVisibilityPublic,
images.ImageVisibilityPrivate,
images.ImageVisibilityCommunity,
images.ImageVisibilityShared,
}
for _, v := range visibilities {
if string(v) == visibility {
return &v, nil
}
}
return nil, fmt.Errorf("Not a valid visibility: %s", visibility)
}