github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/googlecompute/config.go (about) 1 package googlecompute 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "time" 8 9 "github.com/hashicorp/packer/common" 10 "github.com/hashicorp/packer/common/uuid" 11 "github.com/hashicorp/packer/helper/communicator" 12 "github.com/hashicorp/packer/helper/config" 13 "github.com/hashicorp/packer/packer" 14 "github.com/hashicorp/packer/template/interpolate" 15 ) 16 17 var reImageFamily = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`) 18 19 // Config is the configuration structure for the GCE builder. It stores 20 // both the publicly settable state as well as the privately generated 21 // state of the config object. 22 type Config struct { 23 common.PackerConfig `mapstructure:",squash"` 24 Comm communicator.Config `mapstructure:",squash"` 25 26 AccountFile string `mapstructure:"account_file"` 27 ProjectId string `mapstructure:"project_id"` 28 29 AcceleratorType string `mapstructure:"accelerator_type"` 30 AcceleratorCount int64 `mapstructure:"accelerator_count"` 31 Address string `mapstructure:"address"` 32 DiskName string `mapstructure:"disk_name"` 33 DiskSizeGb int64 `mapstructure:"disk_size"` 34 DiskType string `mapstructure:"disk_type"` 35 ImageName string `mapstructure:"image_name"` 36 ImageDescription string `mapstructure:"image_description"` 37 ImageFamily string `mapstructure:"image_family"` 38 ImageLabels map[string]string `mapstructure:"image_labels"` 39 InstanceName string `mapstructure:"instance_name"` 40 Labels map[string]string `mapstructure:"labels"` 41 MachineType string `mapstructure:"machine_type"` 42 Metadata map[string]string `mapstructure:"metadata"` 43 Network string `mapstructure:"network"` 44 NetworkProjectId string `mapstructure:"network_project_id"` 45 OmitExternalIP bool `mapstructure:"omit_external_ip"` 46 OnHostMaintenance string `mapstructure:"on_host_maintenance"` 47 Preemptible bool `mapstructure:"preemptible"` 48 RawStateTimeout string `mapstructure:"state_timeout"` 49 Region string `mapstructure:"region"` 50 Scopes []string `mapstructure:"scopes"` 51 SourceImage string `mapstructure:"source_image"` 52 SourceImageFamily string `mapstructure:"source_image_family"` 53 SourceImageProjectId string `mapstructure:"source_image_project_id"` 54 StartupScriptFile string `mapstructure:"startup_script_file"` 55 Subnetwork string `mapstructure:"subnetwork"` 56 Tags []string `mapstructure:"tags"` 57 UseInternalIP bool `mapstructure:"use_internal_ip"` 58 Zone string `mapstructure:"zone"` 59 60 Account AccountFile 61 stateTimeout time.Duration 62 imageAlreadyExists bool 63 ctx interpolate.Context 64 } 65 66 func NewConfig(raws ...interface{}) (*Config, []string, error) { 67 c := new(Config) 68 err := config.Decode(c, &config.DecodeOpts{ 69 Interpolate: true, 70 InterpolateContext: &c.ctx, 71 InterpolateFilter: &interpolate.RenderFilter{ 72 Exclude: []string{ 73 "run_command", 74 }, 75 }, 76 }, raws...) 77 if err != nil { 78 return nil, nil, err 79 } 80 81 var errs *packer.MultiError 82 83 // Set defaults. 84 if c.Network == "" { 85 c.Network = "default" 86 } 87 88 if c.DiskSizeGb == 0 { 89 c.DiskSizeGb = 10 90 } 91 92 if c.DiskType == "" { 93 c.DiskType = "pd-standard" 94 } 95 96 if c.ImageDescription == "" { 97 c.ImageDescription = "Created by Packer" 98 } 99 100 if c.OnHostMaintenance == "MIGRATE" && c.Preemptible { 101 errs = packer.MultiErrorAppend(errs, 102 errors.New("on_host_maintenance must be TERMINATE when using preemptible instances.")) 103 } 104 // Setting OnHostMaintenance Correct Defaults 105 // "MIGRATE" : Possible and default if Preemptible is false 106 // "TERMINATE": Required if Preemptible is true 107 if c.Preemptible { 108 c.OnHostMaintenance = "TERMINATE" 109 } else { 110 if c.OnHostMaintenance == "" { 111 c.OnHostMaintenance = "MIGRATE" 112 } 113 } 114 115 // Make sure user sets a valid value for on_host_maintenance option 116 if !(c.OnHostMaintenance == "MIGRATE" || c.OnHostMaintenance == "TERMINATE") { 117 errs = packer.MultiErrorAppend(errs, 118 errors.New("on_host_maintenance must be one of MIGRATE or TERMINATE.")) 119 } 120 121 if c.ImageName == "" { 122 img, err := interpolate.Render("packer-{{timestamp}}", nil) 123 if err != nil { 124 errs = packer.MultiErrorAppend(errs, 125 fmt.Errorf("Unable to parse image name: %s ", err)) 126 } else { 127 c.ImageName = img 128 } 129 } 130 131 if len(c.ImageFamily) > 63 { 132 errs = packer.MultiErrorAppend(errs, 133 errors.New("Invalid image family: Must not be longer than 63 characters")) 134 } 135 136 if c.ImageFamily != "" { 137 if !reImageFamily.MatchString(c.ImageFamily) { 138 errs = packer.MultiErrorAppend(errs, 139 errors.New("Invalid image family: The first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash")) 140 } 141 142 } 143 144 if c.InstanceName == "" { 145 c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) 146 } 147 148 if c.DiskName == "" { 149 c.DiskName = c.InstanceName 150 } 151 152 if c.MachineType == "" { 153 c.MachineType = "n1-standard-1" 154 } 155 156 if c.RawStateTimeout == "" { 157 c.RawStateTimeout = "5m" 158 } 159 160 if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { 161 errs = packer.MultiErrorAppend(errs, es...) 162 } 163 164 // Process required parameters. 165 if c.ProjectId == "" { 166 errs = packer.MultiErrorAppend( 167 errs, errors.New("a project_id must be specified")) 168 } 169 170 if c.Scopes == nil { 171 c.Scopes = []string{ 172 "https://www.googleapis.com/auth/userinfo.email", 173 "https://www.googleapis.com/auth/compute", 174 "https://www.googleapis.com/auth/devstorage.full_control", 175 } 176 } 177 178 if c.SourceImage == "" && c.SourceImageFamily == "" { 179 errs = packer.MultiErrorAppend( 180 errs, errors.New("a source_image or source_image_family must be specified")) 181 } 182 183 if c.Zone == "" { 184 errs = packer.MultiErrorAppend( 185 errs, errors.New("a zone must be specified")) 186 } 187 if c.Region == "" && len(c.Zone) > 2 { 188 // get region from Zone 189 region := c.Zone[:len(c.Zone)-2] 190 c.Region = region 191 } 192 193 err = c.CalcTimeout() 194 if err != nil { 195 errs = packer.MultiErrorAppend(errs, err) 196 } 197 198 if c.AccountFile != "" { 199 if err := ProcessAccountFile(&c.Account, c.AccountFile); err != nil { 200 errs = packer.MultiErrorAppend(errs, err) 201 } 202 } 203 204 if c.OmitExternalIP && c.Address != "" { 205 errs = packer.MultiErrorAppend(fmt.Errorf("you can not specify an external address when 'omit_external_ip' is true")) 206 } 207 208 if c.OmitExternalIP && !c.UseInternalIP { 209 errs = packer.MultiErrorAppend(fmt.Errorf("'use_internal_ip' must be true if 'omit_external_ip' is true")) 210 } 211 212 if c.AcceleratorCount > 0 && len(c.AcceleratorType) == 0 { 213 errs = packer.MultiErrorAppend(fmt.Errorf("'accelerator_type' must be set when 'accelerator_count' is more than 0")) 214 } 215 216 if c.AcceleratorCount > 0 && c.OnHostMaintenance != "TERMINATE" { 217 errs = packer.MultiErrorAppend(fmt.Errorf("'on_host_maintenance' must be set to 'TERMINATE' when 'accelerator_count' is more than 0")) 218 } 219 220 // Check for any errors. 221 if errs != nil && len(errs.Errors) > 0 { 222 return nil, nil, errs 223 } 224 225 return c, nil, nil 226 } 227 228 func (c *Config) CalcTimeout() error { 229 stateTimeout, err := time.ParseDuration(c.RawStateTimeout) 230 if err != nil { 231 return fmt.Errorf("Failed parsing state_timeout: %s", err) 232 } 233 c.stateTimeout = stateTimeout 234 return nil 235 }