github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/builtin/providers/triton/resource_machine.go (about) 1 package triton 2 3 import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "time" 8 9 "github.com/hashicorp/terraform/helper/schema" 10 "github.com/joyent/gosdc/cloudapi" 11 ) 12 13 var ( 14 machineStateRunning = "running" 15 machineStateStopped = "stopped" 16 machineStateDeleted = "deleted" 17 18 machineStateChangeTimeout = 10 * time.Minute 19 machineStateChangeCheckInterval = 10 * time.Second 20 21 resourceMachineMetadataKeys = map[string]string{ 22 // semantics: "schema_name": "metadata_name" 23 "root_authorized_keys": "root_authorized_keys", 24 "user_script": "user-script", 25 "user_data": "user-data", 26 "administrator_pw": "administrator-pw", 27 } 28 ) 29 30 func resourceMachine() *schema.Resource { 31 return &schema.Resource{ 32 Create: resourceMachineCreate, 33 Exists: resourceMachineExists, 34 Read: resourceMachineRead, 35 Update: resourceMachineUpdate, 36 Delete: resourceMachineDelete, 37 38 Schema: map[string]*schema.Schema{ 39 "name": { 40 Description: "friendly name", 41 Type: schema.TypeString, 42 Optional: true, 43 Computed: true, 44 ValidateFunc: resourceMachineValidateName, 45 }, 46 "type": { 47 Description: "machine type (smartmachine or virtualmachine)", 48 Type: schema.TypeString, 49 Computed: true, 50 }, 51 "state": { 52 Description: "current state of the machine", 53 Type: schema.TypeString, 54 Computed: true, 55 }, 56 "dataset": { 57 Description: "dataset URN the machine was provisioned with", 58 Type: schema.TypeString, 59 Computed: true, 60 }, 61 "memory": { 62 Description: "amount of memory the machine has (in Mb)", 63 Type: schema.TypeInt, 64 Computed: true, 65 }, 66 "disk": { 67 Description: "amount of disk the machine has (in Gb)", 68 Type: schema.TypeInt, 69 Computed: true, 70 }, 71 "ips": { 72 Description: "IP addresses the machine has", 73 Type: schema.TypeList, 74 Computed: true, 75 Elem: &schema.Schema{ 76 Type: schema.TypeString, 77 }, 78 }, 79 "tags": { 80 Description: "machine tags", 81 Type: schema.TypeMap, 82 Optional: true, 83 }, 84 "created": { 85 Description: "when the machine was created", 86 Type: schema.TypeString, 87 Computed: true, 88 }, 89 "updated": { 90 Description: "when the machine was update", 91 Type: schema.TypeString, 92 Computed: true, 93 }, 94 "package": { 95 Description: "name of the package to use on provisioning", 96 Type: schema.TypeString, 97 Required: true, 98 }, 99 "image": { 100 Description: "image UUID", 101 Type: schema.TypeString, 102 Required: true, 103 ForceNew: true, 104 // TODO: validate that the UUID is valid 105 }, 106 "primaryip": { 107 Description: "the primary (public) IP address for the machine", 108 Type: schema.TypeString, 109 Computed: true, 110 }, 111 "networks": { 112 Description: "desired network IDs", 113 Type: schema.TypeList, 114 Optional: true, 115 Computed: true, 116 // TODO: this really should ForceNew but the Network IDs don't seem to 117 // be returned by the API, meaning if we track them here TF will replace 118 // the resource on every run. 119 // ForceNew: true, 120 Elem: &schema.Schema{ 121 Type: schema.TypeString, 122 }, 123 }, 124 "firewall_enabled": { 125 Description: "enable firewall for this machine", 126 Type: schema.TypeBool, 127 Optional: true, 128 Default: false, 129 }, 130 131 // computed resources from metadata 132 "root_authorized_keys": { 133 Description: "authorized keys for the root user on this machine", 134 Type: schema.TypeString, 135 Optional: true, 136 Computed: true, 137 }, 138 "user_script": { 139 Description: "user script to run on boot (every boot on SmartMachines)", 140 Type: schema.TypeString, 141 Optional: true, 142 Computed: true, 143 }, 144 "user_data": { 145 Description: "copied to machine on boot", 146 Type: schema.TypeString, 147 Optional: true, 148 Computed: true, 149 }, 150 "administrator_pw": { 151 Description: "administrator's initial password (Windows only)", 152 Type: schema.TypeString, 153 Optional: true, 154 Computed: true, 155 }, 156 }, 157 } 158 } 159 160 func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { 161 client := meta.(*cloudapi.Client) 162 163 var networks []string 164 for _, network := range d.Get("networks").([]interface{}) { 165 networks = append(networks, network.(string)) 166 } 167 168 metadata := map[string]string{} 169 for schemaName, metadataKey := range resourceMachineMetadataKeys { 170 if v, ok := d.GetOk(schemaName); ok { 171 metadata[metadataKey] = v.(string) 172 } 173 } 174 175 tags := map[string]string{} 176 for k, v := range d.Get("tags").(map[string]interface{}) { 177 tags[k] = v.(string) 178 } 179 180 machine, err := client.CreateMachine(cloudapi.CreateMachineOpts{ 181 Name: d.Get("name").(string), 182 Package: d.Get("package").(string), 183 Image: d.Get("image").(string), 184 Networks: networks, 185 Metadata: metadata, 186 Tags: tags, 187 FirewallEnabled: d.Get("firewall_enabled").(bool), 188 }) 189 if err != nil { 190 return err 191 } 192 193 err = waitForMachineState(client, machine.Id, machineStateRunning, machineStateChangeTimeout) 194 if err != nil { 195 return err 196 } 197 198 // refresh state after it provisions 199 d.SetId(machine.Id) 200 err = resourceMachineRead(d, meta) 201 if err != nil { 202 return err 203 } 204 205 return nil 206 } 207 208 func resourceMachineExists(d *schema.ResourceData, meta interface{}) (bool, error) { 209 client := meta.(*cloudapi.Client) 210 211 machine, err := client.GetMachine(d.Id()) 212 213 return machine != nil && err == nil, err 214 } 215 216 func resourceMachineRead(d *schema.ResourceData, meta interface{}) error { 217 client := meta.(*cloudapi.Client) 218 219 machine, err := client.GetMachine(d.Id()) 220 if err != nil { 221 return err 222 } 223 224 d.SetId(machine.Id) 225 d.Set("name", machine.Name) 226 d.Set("type", machine.Type) 227 d.Set("state", machine.State) 228 d.Set("dataset", machine.Dataset) 229 d.Set("memory", machine.Memory) 230 d.Set("disk", machine.Disk) 231 d.Set("ips", machine.IPs) 232 d.Set("tags", machine.Tags) 233 d.Set("created", machine.Created) 234 d.Set("updated", machine.Updated) 235 d.Set("package", machine.Package) 236 d.Set("image", machine.Image) 237 d.Set("primaryip", machine.PrimaryIP) 238 d.Set("networks", machine.Networks) 239 d.Set("firewall_enabled", machine.FirewallEnabled) 240 241 // computed attributes from metadata 242 for schemaName, metadataKey := range resourceMachineMetadataKeys { 243 d.Set(schemaName, machine.Metadata[metadataKey]) 244 } 245 246 return nil 247 } 248 249 func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { 250 client := meta.(*cloudapi.Client) 251 252 d.Partial(true) 253 254 if d.HasChange("name") { 255 if err := client.RenameMachine(d.Id(), d.Get("name").(string)); err != nil { 256 return err 257 } 258 259 err := waitFor( 260 func() (bool, error) { 261 machine, err := client.GetMachine(d.Id()) 262 return machine.Name == d.Get("name").(string), err 263 }, 264 machineStateChangeCheckInterval, 265 1*time.Minute, 266 ) 267 if err != nil { 268 return err 269 } 270 271 d.SetPartial("name") 272 } 273 274 if d.HasChange("tags") { 275 tags := map[string]string{} 276 for k, v := range d.Get("tags").(map[string]interface{}) { 277 tags[k] = v.(string) 278 } 279 280 var err error 281 if len(tags) == 0 { 282 err = client.DeleteMachineTags(d.Id()) 283 } else { 284 _, err = client.ReplaceMachineTags(d.Id(), tags) 285 } 286 if err != nil { 287 return err 288 } 289 290 err = waitFor( 291 func() (bool, error) { 292 machine, err := client.GetMachine(d.Id()) 293 return reflect.DeepEqual(machine.Tags, tags), err 294 }, 295 machineStateChangeCheckInterval, 296 1*time.Minute, 297 ) 298 if err != nil { 299 return err 300 } 301 302 d.SetPartial("tags") 303 } 304 305 if d.HasChange("package") { 306 if err := client.ResizeMachine(d.Id(), d.Get("package").(string)); err != nil { 307 return err 308 } 309 310 err := waitFor( 311 func() (bool, error) { 312 machine, err := client.GetMachine(d.Id()) 313 return machine.Package == d.Get("package").(string) && machine.State == machineStateRunning, err 314 }, 315 machineStateChangeCheckInterval, 316 machineStateChangeTimeout, 317 ) 318 if err != nil { 319 return err 320 } 321 322 d.SetPartial("package") 323 } 324 325 if d.HasChange("firewall_enabled") { 326 var err error 327 if d.Get("firewall_enabled").(bool) { 328 err = client.EnableFirewallMachine(d.Id()) 329 } else { 330 err = client.DisableFirewallMachine(d.Id()) 331 } 332 if err != nil { 333 return err 334 } 335 336 d.SetPartial("firewall_enabled") 337 } 338 339 // metadata stuff 340 metadata := map[string]string{} 341 for schemaName, metadataKey := range resourceMachineMetadataKeys { 342 if d.HasChange(schemaName) { 343 metadata[metadataKey] = d.Get(schemaName).(string) 344 } 345 } 346 if len(metadata) > 0 { 347 _, err := client.UpdateMachineMetadata(d.Id(), metadata) 348 if err != nil { 349 return err 350 } 351 352 err = waitFor( 353 func() (bool, error) { 354 machine, err := client.GetMachine(d.Id()) 355 return reflect.DeepEqual(machine.Metadata, metadata), err 356 }, 357 machineStateChangeCheckInterval, 358 1*time.Minute, 359 ) 360 if err != nil { 361 return err 362 } 363 364 for schemaName := range resourceMachineMetadataKeys { 365 if d.HasChange(schemaName) { 366 d.SetPartial(schemaName) 367 } 368 } 369 } 370 371 d.Partial(false) 372 373 err := resourceMachineRead(d, meta) 374 if err != nil { 375 return err 376 } 377 378 return nil 379 } 380 381 func resourceMachineDelete(d *schema.ResourceData, meta interface{}) error { 382 client := meta.(*cloudapi.Client) 383 384 state, err := readMachineState(client, d.Id()) 385 if state != machineStateStopped { 386 err = client.StopMachine(d.Id()) 387 if err != nil { 388 return err 389 } 390 391 waitForMachineState(client, d.Id(), machineStateStopped, machineStateChangeTimeout) 392 } 393 394 err = client.DeleteMachine(d.Id()) 395 if err != nil { 396 return err 397 } 398 399 waitForMachineState(client, d.Id(), machineStateDeleted, machineStateChangeTimeout) 400 return nil 401 } 402 403 func readMachineState(api *cloudapi.Client, id string) (string, error) { 404 machine, err := api.GetMachine(id) 405 if err != nil { 406 return "", err 407 } 408 409 return machine.State, nil 410 } 411 412 // waitForMachineState waits for a machine to be in the desired state (waiting 413 // some seconds between each poll). If it doesn't reach the state within the 414 // duration specified in `timeout`, it returns ErrMachineStateTimeout. 415 func waitForMachineState(api *cloudapi.Client, id, state string, timeout time.Duration) error { 416 return waitFor( 417 func() (bool, error) { 418 currentState, err := readMachineState(api, id) 419 return currentState == state, err 420 }, 421 machineStateChangeCheckInterval, 422 machineStateChangeTimeout, 423 ) 424 } 425 426 func resourceMachineValidateName(value interface{}, name string) (warnings []string, errors []error) { 427 warnings = []string{} 428 errors = []error{} 429 430 r := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\_\.\-]*$`) 431 if !r.Match([]byte(value.(string))) { 432 errors = append(errors, fmt.Errorf(`"%s" is not a valid %s`, value.(string), name)) 433 } 434 435 return warnings, errors 436 }