github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/packet/resource_packet_device.go (about) 1 package packet 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/hashicorp/terraform/helper/resource" 9 "github.com/hashicorp/terraform/helper/schema" 10 "github.com/packethost/packngo" 11 ) 12 13 func resourcePacketDevice() *schema.Resource { 14 return &schema.Resource{ 15 Create: resourcePacketDeviceCreate, 16 Read: resourcePacketDeviceRead, 17 Update: resourcePacketDeviceUpdate, 18 Delete: resourcePacketDeviceDelete, 19 20 Schema: map[string]*schema.Schema{ 21 "project_id": &schema.Schema{ 22 Type: schema.TypeString, 23 Required: true, 24 ForceNew: true, 25 }, 26 27 "hostname": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 }, 32 33 "operating_system": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "facility": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 45 "plan": &schema.Schema{ 46 Type: schema.TypeString, 47 Required: true, 48 ForceNew: true, 49 }, 50 51 "billing_cycle": &schema.Schema{ 52 Type: schema.TypeString, 53 Required: true, 54 ForceNew: true, 55 }, 56 57 "state": &schema.Schema{ 58 Type: schema.TypeString, 59 Computed: true, 60 }, 61 62 "locked": &schema.Schema{ 63 Type: schema.TypeBool, 64 Computed: true, 65 }, 66 67 "network": &schema.Schema{ 68 Type: schema.TypeList, 69 Computed: true, 70 Elem: &schema.Resource{ 71 Schema: map[string]*schema.Schema{ 72 "address": &schema.Schema{ 73 Type: schema.TypeString, 74 Computed: true, 75 }, 76 77 "gateway": &schema.Schema{ 78 Type: schema.TypeString, 79 Computed: true, 80 }, 81 82 "family": &schema.Schema{ 83 Type: schema.TypeInt, 84 Computed: true, 85 }, 86 87 "cidr": &schema.Schema{ 88 Type: schema.TypeInt, 89 Computed: true, 90 }, 91 92 "public": &schema.Schema{ 93 Type: schema.TypeBool, 94 Computed: true, 95 }, 96 }, 97 }, 98 }, 99 100 "created": &schema.Schema{ 101 Type: schema.TypeString, 102 Computed: true, 103 }, 104 105 "updated": &schema.Schema{ 106 Type: schema.TypeString, 107 Computed: true, 108 }, 109 110 "user_data": &schema.Schema{ 111 Type: schema.TypeString, 112 Optional: true, 113 }, 114 115 "tags": &schema.Schema{ 116 Type: schema.TypeList, 117 Optional: true, 118 Elem: &schema.Schema{Type: schema.TypeString}, 119 }, 120 }, 121 } 122 } 123 124 func resourcePacketDeviceCreate(d *schema.ResourceData, meta interface{}) error { 125 client := meta.(*packngo.Client) 126 127 createRequest := &packngo.DeviceCreateRequest{ 128 HostName: d.Get("hostname").(string), 129 Plan: d.Get("plan").(string), 130 Facility: d.Get("facility").(string), 131 OS: d.Get("operating_system").(string), 132 BillingCycle: d.Get("billing_cycle").(string), 133 ProjectID: d.Get("project_id").(string), 134 } 135 136 if attr, ok := d.GetOk("user_data"); ok { 137 createRequest.UserData = attr.(string) 138 } 139 140 tags := d.Get("tags.#").(int) 141 if tags > 0 { 142 createRequest.Tags = make([]string, 0, tags) 143 for i := 0; i < tags; i++ { 144 key := fmt.Sprintf("tags.%d", i) 145 createRequest.Tags = append(createRequest.Tags, d.Get(key).(string)) 146 } 147 } 148 149 newDevice, _, err := client.Devices.Create(createRequest) 150 if err != nil { 151 return friendlyError(err) 152 } 153 154 d.SetId(newDevice.ID) 155 156 // Wait for the device so we can get the networking attributes that show up after a while. 157 _, err = waitForDeviceAttribute(d, "active", []string{"queued", "provisioning"}, "state", meta) 158 if err != nil { 159 if isForbidden(err) { 160 // If the device doesn't get to the active state, we can't recover it from here. 161 d.SetId("") 162 163 return errors.New("provisioning time limit exceeded; the Packet team will investigate") 164 } 165 return err 166 } 167 168 return resourcePacketDeviceRead(d, meta) 169 } 170 171 func resourcePacketDeviceRead(d *schema.ResourceData, meta interface{}) error { 172 client := meta.(*packngo.Client) 173 174 device, _, err := client.Devices.Get(d.Id()) 175 if err != nil { 176 err = friendlyError(err) 177 178 // If the device somehow already destroyed, mark as succesfully gone. 179 if isNotFound(err) { 180 d.SetId("") 181 return nil 182 } 183 184 return err 185 } 186 187 d.Set("name", device.Hostname) 188 d.Set("plan", device.Plan.Slug) 189 d.Set("facility", device.Facility.Code) 190 d.Set("operating_system", device.OS.Slug) 191 d.Set("state", device.State) 192 d.Set("billing_cycle", device.BillingCycle) 193 d.Set("locked", device.Locked) 194 d.Set("created", device.Created) 195 d.Set("updated", device.Updated) 196 197 tags := make([]string, 0, len(device.Tags)) 198 for _, tag := range device.Tags { 199 tags = append(tags, tag) 200 } 201 d.Set("tags", tags) 202 203 var ( 204 host string 205 networks = make([]map[string]interface{}, 0, 1) 206 ) 207 for _, ip := range device.Network { 208 network := map[string]interface{}{ 209 "address": ip.Address, 210 "gateway": ip.Gateway, 211 "family": ip.AddressFamily, 212 "cidr": ip.Cidr, 213 "public": ip.Public, 214 } 215 networks = append(networks, network) 216 217 if ip.AddressFamily == 4 && ip.Public == true { 218 host = ip.Address 219 } 220 } 221 d.Set("network", networks) 222 223 if host != "" { 224 d.SetConnInfo(map[string]string{ 225 "type": "ssh", 226 "host": host, 227 }) 228 } 229 230 return nil 231 } 232 233 func resourcePacketDeviceUpdate(d *schema.ResourceData, meta interface{}) error { 234 client := meta.(*packngo.Client) 235 236 if d.HasChange("locked") { 237 var action func(string) (*packngo.Response, error) 238 if d.Get("locked").(bool) { 239 action = client.Devices.Lock 240 } else { 241 action = client.Devices.Unlock 242 } 243 if _, err := action(d.Id()); err != nil { 244 return friendlyError(err) 245 } 246 } 247 248 return resourcePacketDeviceRead(d, meta) 249 } 250 251 func resourcePacketDeviceDelete(d *schema.ResourceData, meta interface{}) error { 252 client := meta.(*packngo.Client) 253 254 if _, err := client.Devices.Delete(d.Id()); err != nil { 255 return friendlyError(err) 256 } 257 258 return nil 259 } 260 261 func waitForDeviceAttribute(d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}) (interface{}, error) { 262 stateConf := &resource.StateChangeConf{ 263 Pending: pending, 264 Target: []string{target}, 265 Refresh: newDeviceStateRefreshFunc(d, attribute, meta), 266 Timeout: 60 * time.Minute, 267 Delay: 10 * time.Second, 268 MinTimeout: 3 * time.Second, 269 } 270 return stateConf.WaitForState() 271 } 272 273 func newDeviceStateRefreshFunc(d *schema.ResourceData, attribute string, meta interface{}) resource.StateRefreshFunc { 274 client := meta.(*packngo.Client) 275 276 return func() (interface{}, string, error) { 277 if err := resourcePacketDeviceRead(d, meta); err != nil { 278 return nil, "", err 279 } 280 281 if attr, ok := d.GetOk(attribute); ok { 282 device, _, err := client.Devices.Get(d.Id()) 283 if err != nil { 284 return nil, "", friendlyError(err) 285 } 286 return &device, attr.(string), nil 287 } 288 289 return nil, "", nil 290 } 291 } 292 293 // powerOnAndWait Powers on the device and waits for it to be active. 294 func powerOnAndWait(d *schema.ResourceData, meta interface{}) error { 295 client := meta.(*packngo.Client) 296 _, err := client.Devices.PowerOn(d.Id()) 297 if err != nil { 298 return friendlyError(err) 299 } 300 301 _, err = waitForDeviceAttribute(d, "active", []string{"off"}, "state", client) 302 return err 303 }