github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/opc/resource_instance.go (about)

     1  package opc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/go-oracle-terraform/compute"
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  	"github.com/hashicorp/terraform/helper/validation"
    15  )
    16  
    17  func resourceInstance() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceInstanceCreate,
    20  		Read:   resourceInstanceRead,
    21  		Delete: resourceInstanceDelete,
    22  		Importer: &schema.ResourceImporter{
    23  			State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
    24  				combined := strings.Split(d.Id(), "/")
    25  				if len(combined) != 2 {
    26  					return nil, fmt.Errorf("Invalid ID specified. Must be in the form of instance_name/instance_id. Got: %s", d.Id())
    27  				}
    28  				d.Set("name", combined[0])
    29  				d.SetId(combined[1])
    30  				return []*schema.ResourceData{d}, nil
    31  			},
    32  		},
    33  
    34  		Schema: map[string]*schema.Schema{
    35  			/////////////////////////
    36  			// Required Attributes //
    37  			/////////////////////////
    38  			"name": {
    39  				Type:     schema.TypeString,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"shape": {
    45  				Type:     schema.TypeString,
    46  				Required: true,
    47  				ForceNew: true,
    48  			},
    49  
    50  			/////////////////////////
    51  			// Optional Attributes //
    52  			/////////////////////////
    53  			"instance_attributes": {
    54  				Type:         schema.TypeString,
    55  				Optional:     true,
    56  				ForceNew:     true,
    57  				ValidateFunc: validation.ValidateJsonString,
    58  			},
    59  
    60  			"boot_order": {
    61  				Type:     schema.TypeList,
    62  				Optional: true,
    63  				ForceNew: true,
    64  				Elem:     &schema.Schema{Type: schema.TypeInt},
    65  			},
    66  
    67  			"hostname": {
    68  				Type:     schema.TypeString,
    69  				Optional: true,
    70  				Computed: true,
    71  				ForceNew: true,
    72  			},
    73  
    74  			"image_list": {
    75  				Type:     schema.TypeString,
    76  				Optional: true,
    77  				ForceNew: true,
    78  			},
    79  
    80  			"label": {
    81  				Type:     schema.TypeString,
    82  				Optional: true,
    83  				Computed: true,
    84  				ForceNew: true,
    85  			},
    86  
    87  			"networking_info": {
    88  				Type:     schema.TypeSet,
    89  				Optional: true,
    90  				Computed: true,
    91  				ForceNew: true,
    92  				Elem: &schema.Resource{
    93  					Schema: map[string]*schema.Schema{
    94  						"dns": {
    95  							// Required for Shared Network Interface, will default if unspecified, however
    96  							// Optional for IP Network Interface
    97  							Type:     schema.TypeList,
    98  							Optional: true,
    99  							Computed: true,
   100  							ForceNew: true,
   101  							Elem:     &schema.Schema{Type: schema.TypeString},
   102  						},
   103  
   104  						"index": {
   105  							Type:     schema.TypeInt,
   106  							ForceNew: true,
   107  							Required: true,
   108  						},
   109  
   110  						"ip_address": {
   111  							// Optional, IP Network only
   112  							Type:     schema.TypeString,
   113  							ForceNew: true,
   114  							Optional: true,
   115  						},
   116  
   117  						"ip_network": {
   118  							// Required for an IP Network Interface
   119  							Type:     schema.TypeString,
   120  							ForceNew: true,
   121  							Optional: true,
   122  						},
   123  
   124  						"mac_address": {
   125  							// Optional, IP Network Only
   126  							Type:     schema.TypeString,
   127  							ForceNew: true,
   128  							Computed: true,
   129  							Optional: true,
   130  						},
   131  
   132  						"name_servers": {
   133  							// Optional, IP Network + Shared Network
   134  							Type:     schema.TypeList,
   135  							Optional: true,
   136  							ForceNew: true,
   137  							Elem:     &schema.Schema{Type: schema.TypeString},
   138  						},
   139  
   140  						"nat": {
   141  							// Optional for IP Network
   142  							// Required for Shared Network
   143  							Type:     schema.TypeList,
   144  							Optional: true,
   145  							ForceNew: true,
   146  							Elem:     &schema.Schema{Type: schema.TypeString},
   147  						},
   148  
   149  						"search_domains": {
   150  							// Optional, IP Network + Shared Network
   151  							Type:     schema.TypeList,
   152  							Optional: true,
   153  							ForceNew: true,
   154  							Elem:     &schema.Schema{Type: schema.TypeString},
   155  						},
   156  
   157  						"sec_lists": {
   158  							// Required, Shared Network only. Will default if unspecified however
   159  							Type:     schema.TypeList,
   160  							Optional: true,
   161  							Computed: true,
   162  							ForceNew: true,
   163  							Elem:     &schema.Schema{Type: schema.TypeString},
   164  						},
   165  
   166  						"shared_network": {
   167  							Type:     schema.TypeBool,
   168  							Optional: true,
   169  							ForceNew: true,
   170  							Default:  false,
   171  						},
   172  
   173  						"vnic": {
   174  							// Optional, IP Network only.
   175  							Type:     schema.TypeString,
   176  							ForceNew: true,
   177  							Optional: true,
   178  						},
   179  
   180  						"vnic_sets": {
   181  							// Optional, IP Network only.
   182  							Type:     schema.TypeList,
   183  							Optional: true,
   184  							ForceNew: true,
   185  							Elem:     &schema.Schema{Type: schema.TypeString},
   186  						},
   187  					},
   188  				},
   189  				Set: func(v interface{}) int {
   190  					var buf bytes.Buffer
   191  					m := v.(map[string]interface{})
   192  					buf.WriteString(fmt.Sprintf("%d-", m["index"].(int)))
   193  					buf.WriteString(fmt.Sprintf("%s-", m["vnic"].(string)))
   194  					buf.WriteString(fmt.Sprintf("%s-", m["nat"]))
   195  					return hashcode.String(buf.String())
   196  				},
   197  			},
   198  
   199  			"reverse_dns": {
   200  				Type:     schema.TypeBool,
   201  				Optional: true,
   202  				Default:  true,
   203  				ForceNew: true,
   204  			},
   205  
   206  			"ssh_keys": {
   207  				Type:     schema.TypeList,
   208  				Optional: true,
   209  				ForceNew: true,
   210  				Elem:     &schema.Schema{Type: schema.TypeString},
   211  			},
   212  
   213  			"storage": {
   214  				Type:     schema.TypeSet,
   215  				Optional: true,
   216  				ForceNew: true,
   217  				Elem: &schema.Resource{
   218  					Schema: map[string]*schema.Schema{
   219  						"index": {
   220  							Type:         schema.TypeInt,
   221  							Required:     true,
   222  							ForceNew:     true,
   223  							ValidateFunc: validation.IntBetween(1, 10),
   224  						},
   225  						"volume": {
   226  							Type:     schema.TypeString,
   227  							Required: true,
   228  							ForceNew: true,
   229  						},
   230  						"name": {
   231  							Type:     schema.TypeString,
   232  							Computed: true,
   233  						},
   234  					},
   235  				},
   236  			},
   237  
   238  			"tags": tagsForceNewSchema(),
   239  
   240  			/////////////////////////
   241  			// Computed Attributes //
   242  			/////////////////////////
   243  			"attributes": {
   244  				Type:     schema.TypeString,
   245  				Computed: true,
   246  			},
   247  
   248  			"availability_domain": {
   249  				Type:     schema.TypeString,
   250  				Computed: true,
   251  			},
   252  
   253  			"domain": {
   254  				Type:     schema.TypeString,
   255  				Computed: true,
   256  			},
   257  
   258  			"entry": {
   259  				Type:     schema.TypeInt,
   260  				Computed: true,
   261  			},
   262  
   263  			"fingerprint": {
   264  				Type:     schema.TypeString,
   265  				Computed: true,
   266  			},
   267  
   268  			"image_format": {
   269  				Type:     schema.TypeString,
   270  				Computed: true,
   271  			},
   272  
   273  			"ip_address": {
   274  				Type:     schema.TypeString,
   275  				Computed: true,
   276  			},
   277  
   278  			"placement_requirements": {
   279  				Type:     schema.TypeList,
   280  				Computed: true,
   281  				Elem:     &schema.Schema{Type: schema.TypeString},
   282  			},
   283  
   284  			"platform": {
   285  				Type:     schema.TypeString,
   286  				Computed: true,
   287  			},
   288  
   289  			"priority": {
   290  				Type:     schema.TypeString,
   291  				Computed: true,
   292  			},
   293  
   294  			"quota_reservation": {
   295  				Type:     schema.TypeString,
   296  				Computed: true,
   297  			},
   298  
   299  			"relationships": {
   300  				Type:     schema.TypeList,
   301  				Computed: true,
   302  				Elem:     &schema.Schema{Type: schema.TypeString},
   303  			},
   304  
   305  			"resolvers": {
   306  				Type:     schema.TypeList,
   307  				Computed: true,
   308  				Elem:     &schema.Schema{Type: schema.TypeString},
   309  			},
   310  
   311  			"site": {
   312  				Type:     schema.TypeString,
   313  				Computed: true,
   314  			},
   315  
   316  			"start_time": {
   317  				Type:     schema.TypeString,
   318  				Computed: true,
   319  			},
   320  
   321  			"state": {
   322  				Type:     schema.TypeString,
   323  				Computed: true,
   324  			},
   325  
   326  			"vcable": {
   327  				Type:     schema.TypeString,
   328  				Computed: true,
   329  			},
   330  
   331  			"virtio": {
   332  				Type:     schema.TypeBool,
   333  				Computed: true,
   334  			},
   335  
   336  			"vnc_address": {
   337  				Type:     schema.TypeString,
   338  				Computed: true,
   339  			},
   340  		},
   341  	}
   342  }
   343  
   344  func resourceInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   345  	client := meta.(*compute.Client).Instances()
   346  
   347  	// Get Required Attributes
   348  	input := &compute.CreateInstanceInput{
   349  		Name:  d.Get("name").(string),
   350  		Shape: d.Get("shape").(string),
   351  	}
   352  
   353  	// Get optional instance attributes
   354  	attributes, attrErr := getInstanceAttributes(d)
   355  	if attrErr != nil {
   356  		return attrErr
   357  	}
   358  
   359  	if attributes != nil {
   360  		input.Attributes = attributes
   361  	}
   362  
   363  	if bootOrder := getIntList(d, "boot_order"); len(bootOrder) > 0 {
   364  		input.BootOrder = bootOrder
   365  	}
   366  
   367  	if v, ok := d.GetOk("hostname"); ok {
   368  		input.Hostname = v.(string)
   369  	}
   370  
   371  	if v, ok := d.GetOk("image_list"); ok {
   372  		input.ImageList = v.(string)
   373  	}
   374  
   375  	if v, ok := d.GetOk("label"); ok {
   376  		input.Label = v.(string)
   377  	}
   378  
   379  	interfaces, err := readNetworkInterfacesFromConfig(d)
   380  	if err != nil {
   381  		return err
   382  	}
   383  	if interfaces != nil {
   384  		input.Networking = interfaces
   385  	}
   386  
   387  	if v, ok := d.GetOk("reverse_dns"); ok {
   388  		input.ReverseDNS = v.(bool)
   389  	}
   390  
   391  	if sshKeys := getStringList(d, "ssh_keys"); len(sshKeys) > 0 {
   392  		input.SSHKeys = sshKeys
   393  	}
   394  
   395  	storage := getStorageAttachments(d)
   396  	if len(storage) > 0 {
   397  		input.Storage = storage
   398  	}
   399  
   400  	if tags := getStringList(d, "tags"); len(tags) > 0 {
   401  		input.Tags = tags
   402  	}
   403  
   404  	result, err := client.CreateInstance(input)
   405  	if err != nil {
   406  		return fmt.Errorf("Error creating instance %s: %s", input.Name, err)
   407  	}
   408  
   409  	log.Printf("[DEBUG] Created instance %s: %#v", input.Name, result.ID)
   410  
   411  	d.SetId(result.ID)
   412  
   413  	return resourceInstanceRead(d, meta)
   414  }
   415  
   416  func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error {
   417  	client := meta.(*compute.Client).Instances()
   418  
   419  	name := d.Get("name").(string)
   420  
   421  	input := &compute.GetInstanceInput{
   422  		ID:   d.Id(),
   423  		Name: name,
   424  	}
   425  
   426  	log.Printf("[DEBUG] Reading state of instance %s", name)
   427  	result, err := client.GetInstance(input)
   428  	if err != nil {
   429  		// Instance doesn't exist
   430  		if compute.WasNotFoundError(err) {
   431  			log.Printf("[DEBUG] Instance %s not found", name)
   432  			d.SetId("")
   433  			return nil
   434  		}
   435  		return fmt.Errorf("Error reading instance %s: %s", name, err)
   436  	}
   437  
   438  	log.Printf("[DEBUG] Instance '%s' found", name)
   439  
   440  	// Update attributes
   441  	return updateInstanceAttributes(d, result)
   442  }
   443  
   444  func updateInstanceAttributes(d *schema.ResourceData, instance *compute.InstanceInfo) error {
   445  	d.Set("name", instance.Name)
   446  	d.Set("shape", instance.Shape)
   447  
   448  	if err := setInstanceAttributes(d, instance.Attributes); err != nil {
   449  		return err
   450  	}
   451  
   452  	if attrs, ok := d.GetOk("instance_attributes"); ok && attrs != nil {
   453  		d.Set("instance_attributes", attrs.(string))
   454  	}
   455  
   456  	if err := setIntList(d, "boot_order", instance.BootOrder); err != nil {
   457  		return err
   458  	}
   459  	d.Set("hostname", instance.Hostname)
   460  	d.Set("image_list", instance.ImageList)
   461  	d.Set("label", instance.Label)
   462  
   463  	if err := readNetworkInterfaces(d, instance.Networking); err != nil {
   464  		return err
   465  	}
   466  
   467  	d.Set("reverse_dns", instance.ReverseDNS)
   468  	if err := setStringList(d, "ssh_keys", instance.SSHKeys); err != nil {
   469  		return err
   470  	}
   471  
   472  	if err := readStorageAttachments(d, instance.Storage); err != nil {
   473  		return err
   474  	}
   475  
   476  	if err := setStringList(d, "tags", instance.Tags); err != nil {
   477  		return err
   478  	}
   479  	d.Set("availability_domain", instance.AvailabilityDomain)
   480  	d.Set("domain", instance.Domain)
   481  	d.Set("entry", instance.Entry)
   482  	d.Set("fingerprint", instance.Fingerprint)
   483  	d.Set("image_format", instance.ImageFormat)
   484  	d.Set("ip_address", instance.IPAddress)
   485  
   486  	if err := setStringList(d, "placement_requirements", instance.PlacementRequirements); err != nil {
   487  		return err
   488  	}
   489  
   490  	d.Set("platform", instance.Platform)
   491  	d.Set("priority", instance.Priority)
   492  	d.Set("quota_reservation", instance.QuotaReservation)
   493  
   494  	if err := setStringList(d, "relationships", instance.Relationships); err != nil {
   495  		return err
   496  	}
   497  
   498  	if err := setStringList(d, "resolvers", instance.Resolvers); err != nil {
   499  		return err
   500  	}
   501  
   502  	d.Set("site", instance.Site)
   503  	d.Set("start_time", instance.StartTime)
   504  	d.Set("state", instance.State)
   505  
   506  	if err := setStringList(d, "tags", instance.Tags); err != nil {
   507  		return err
   508  	}
   509  
   510  	d.Set("vcable", instance.VCableID)
   511  	d.Set("virtio", instance.Virtio)
   512  	d.Set("vnc_address", instance.VNC)
   513  
   514  	return nil
   515  }
   516  
   517  func resourceInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   518  	client := meta.(*compute.Client).Instances()
   519  
   520  	name := d.Get("name").(string)
   521  
   522  	input := &compute.DeleteInstanceInput{
   523  		ID:   d.Id(),
   524  		Name: name,
   525  	}
   526  	log.Printf("[DEBUG] Deleting instance %s", name)
   527  
   528  	if err := client.DeleteInstance(input); err != nil {
   529  		return fmt.Errorf("Error deleting instance %s: %s", name, err)
   530  	}
   531  
   532  	return nil
   533  }
   534  
   535  func getStorageAttachments(d *schema.ResourceData) []compute.StorageAttachmentInput {
   536  	storageAttachments := []compute.StorageAttachmentInput{}
   537  	storage := d.Get("storage").(*schema.Set)
   538  	for _, i := range storage.List() {
   539  		attrs := i.(map[string]interface{})
   540  		storageAttachments = append(storageAttachments, compute.StorageAttachmentInput{
   541  			Index:  attrs["index"].(int),
   542  			Volume: attrs["volume"].(string),
   543  		})
   544  	}
   545  	return storageAttachments
   546  }
   547  
   548  // Parses instance_attributes from a string to a map[string]interface and returns any errors.
   549  func getInstanceAttributes(d *schema.ResourceData) (map[string]interface{}, error) {
   550  	var attrs map[string]interface{}
   551  
   552  	// Empty instance attributes
   553  	attributes, ok := d.GetOk("instance_attributes")
   554  	if !ok {
   555  		return attrs, nil
   556  	}
   557  
   558  	if err := json.Unmarshal([]byte(attributes.(string)), &attrs); err != nil {
   559  		return attrs, fmt.Errorf("Cannot parse attributes as json: %s", err)
   560  	}
   561  
   562  	return attrs, nil
   563  }
   564  
   565  // Reads attributes from the returned instance object, and sets the computed attributes string
   566  // as JSON
   567  func setInstanceAttributes(d *schema.ResourceData, attributes map[string]interface{}) error {
   568  	// Shouldn't ever get nil attributes on an instance, but protect against the case either way
   569  	if attributes == nil {
   570  		return nil
   571  	}
   572  
   573  	b, err := json.Marshal(attributes)
   574  	if err != nil {
   575  		return fmt.Errorf("Error marshalling returned attributes: %s", err)
   576  	}
   577  	return d.Set("attributes", string(b))
   578  }
   579  
   580  // Populates and validates shared network and ip network interfaces to return the of map
   581  // objects needed to create/update an instance's networking_info
   582  func readNetworkInterfacesFromConfig(d *schema.ResourceData) (map[string]compute.NetworkingInfo, error) {
   583  	interfaces := make(map[string]compute.NetworkingInfo)
   584  
   585  	if v, ok := d.GetOk("networking_info"); ok {
   586  		vL := v.(*schema.Set).List()
   587  		for _, v := range vL {
   588  			ni := v.(map[string]interface{})
   589  			index, ok := ni["index"].(int)
   590  			if !ok {
   591  				return nil, fmt.Errorf("Index not specified for network interface: %v", ni)
   592  			}
   593  
   594  			deviceIndex := fmt.Sprintf("eth%d", index)
   595  
   596  			// Verify that the network interface doesn't already exist
   597  			if _, ok := interfaces[deviceIndex]; ok {
   598  				return nil, fmt.Errorf("Duplicate Network interface at eth%d already specified", index)
   599  			}
   600  
   601  			// Determine if we're creating a shared network interface or an IP Network interface
   602  			info := compute.NetworkingInfo{}
   603  			var err error
   604  			if ni["shared_network"].(bool) {
   605  				// Populate shared network parameters
   606  				info, err = readSharedNetworkFromConfig(ni)
   607  				// Set 'model' since we're configuring a shared network interface
   608  				info.Model = compute.NICDefaultModel
   609  			} else {
   610  				// Populate IP Network Parameters
   611  				info, err = readIPNetworkFromConfig(ni)
   612  			}
   613  			if err != nil {
   614  				return nil, err
   615  			}
   616  			// And you may find yourself in a beautiful house, with a beautiful wife
   617  			// And you may ask yourself, well, how did I get here?
   618  			interfaces[deviceIndex] = info
   619  		}
   620  	}
   621  
   622  	return interfaces, nil
   623  }
   624  
   625  // Reads a networking_info config block as a shared network interface
   626  func readSharedNetworkFromConfig(ni map[string]interface{}) (compute.NetworkingInfo, error) {
   627  	info := compute.NetworkingInfo{}
   628  	// Validate the shared network
   629  	if err := validateSharedNetwork(ni); err != nil {
   630  		return info, err
   631  	}
   632  	// Populate shared network fields; checking type casting
   633  	dns := []string{}
   634  	if v, ok := ni["dns"]; ok && v != nil {
   635  		for _, d := range v.([]interface{}) {
   636  			dns = append(dns, d.(string))
   637  		}
   638  		if len(dns) > 0 {
   639  			info.DNS = dns
   640  		}
   641  	}
   642  
   643  	if v, ok := ni["model"].(string); ok && v != "" {
   644  		info.Model = compute.NICModel(v)
   645  	}
   646  
   647  	nats := []string{}
   648  	if v, ok := ni["nat"]; ok && v != nil {
   649  		for _, nat := range v.([]interface{}) {
   650  			nats = append(nats, nat.(string))
   651  		}
   652  		if len(nats) > 0 {
   653  			info.Nat = nats
   654  		}
   655  	}
   656  
   657  	slists := []string{}
   658  	if v, ok := ni["sec_lists"]; ok && v != nil {
   659  		for _, slist := range v.([]interface{}) {
   660  			slists = append(slists, slist.(string))
   661  		}
   662  		if len(slists) > 0 {
   663  			info.SecLists = slists
   664  		}
   665  	}
   666  
   667  	nservers := []string{}
   668  	if v, ok := ni["name_servers"]; ok && v != nil {
   669  		for _, nserver := range v.([]interface{}) {
   670  			nservers = append(nservers, nserver.(string))
   671  		}
   672  		if len(nservers) > 0 {
   673  			info.NameServers = nservers
   674  		}
   675  	}
   676  
   677  	sdomains := []string{}
   678  	if v, ok := ni["search_domains"]; ok && v != nil {
   679  		for _, sdomain := range v.([]interface{}) {
   680  			sdomains = append(sdomains, sdomain.(string))
   681  		}
   682  		if len(sdomains) > 0 {
   683  			info.SearchDomains = sdomains
   684  		}
   685  	}
   686  
   687  	return info, nil
   688  }
   689  
   690  // Unfortunately this cannot take place during plan-phase, because we currently cannot have a validation
   691  // function based off of multiple fields in the supplied schema.
   692  func validateSharedNetwork(ni map[string]interface{}) error {
   693  	// A Shared Networking Interface MUST have the following attributes set:
   694  	// - "nat"
   695  	// The following attributes _cannot_ be set for a shared network:
   696  	// - "ip_address"
   697  	// - "ip_network"
   698  	// - "mac_address"
   699  	// - "vnic"
   700  	// - "vnic_sets"
   701  
   702  	if _, ok := ni["nat"]; !ok {
   703  		return fmt.Errorf("'nat' field needs to be set for a Shared Networking Interface")
   704  	}
   705  
   706  	// Strings only
   707  	nilAttrs := []string{
   708  		"ip_address",
   709  		"ip_network",
   710  		"mac_address",
   711  		"vnic",
   712  	}
   713  
   714  	for _, v := range nilAttrs {
   715  		if d, ok := ni[v]; ok && d.(string) != "" {
   716  			return fmt.Errorf("%q field cannot be set in a Shared Networking Interface", v)
   717  		}
   718  	}
   719  	if _, ok := ni["vnic_sets"].([]string); ok {
   720  		return fmt.Errorf("%q field cannot be set in a Shared Networking Interface", "vnic_sets")
   721  	}
   722  
   723  	return nil
   724  }
   725  
   726  // Populates fields for an IP Network
   727  func readIPNetworkFromConfig(ni map[string]interface{}) (compute.NetworkingInfo, error) {
   728  	info := compute.NetworkingInfo{}
   729  	// Validate the IP Network
   730  	if err := validateIPNetwork(ni); err != nil {
   731  		return info, err
   732  	}
   733  	// Populate fields
   734  	if v, ok := ni["ip_network"].(string); ok && v != "" {
   735  		info.IPNetwork = v
   736  	}
   737  
   738  	dns := []string{}
   739  	if v, ok := ni["dns"]; ok && v != nil {
   740  		for _, d := range v.([]interface{}) {
   741  			dns = append(dns, d.(string))
   742  		}
   743  		if len(dns) > 0 {
   744  			info.DNS = dns
   745  		}
   746  	}
   747  
   748  	if v, ok := ni["ip_address"].(string); ok && v != "" {
   749  		info.IPAddress = v
   750  	}
   751  
   752  	if v, ok := ni["mac_address"].(string); ok && v != "" {
   753  		info.MACAddress = v
   754  	}
   755  
   756  	nservers := []string{}
   757  	if v, ok := ni["name_servers"]; ok && v != nil {
   758  		for _, nserver := range v.([]interface{}) {
   759  			nservers = append(nservers, nserver.(string))
   760  		}
   761  		if len(nservers) > 0 {
   762  			info.NameServers = nservers
   763  		}
   764  	}
   765  
   766  	nats := []string{}
   767  	if v, ok := ni["nat"]; ok && v != nil {
   768  		for _, nat := range v.([]interface{}) {
   769  			nats = append(nats, nat.(string))
   770  		}
   771  		if len(nats) > 0 {
   772  			info.Nat = nats
   773  		}
   774  	}
   775  
   776  	sdomains := []string{}
   777  	if v, ok := ni["search_domains"]; ok && v != nil {
   778  		for _, sdomain := range v.([]interface{}) {
   779  			sdomains = append(sdomains, sdomain.(string))
   780  		}
   781  		if len(sdomains) > 0 {
   782  			info.SearchDomains = sdomains
   783  		}
   784  	}
   785  
   786  	if v, ok := ni["vnic"].(string); ok && v != "" {
   787  		info.Vnic = v
   788  	}
   789  
   790  	vnicSets := []string{}
   791  	if v, ok := ni["vnic_sets"]; ok && v != nil {
   792  		for _, vnic := range v.([]interface{}) {
   793  			vnicSets = append(vnicSets, vnic.(string))
   794  		}
   795  		if len(vnicSets) > 0 {
   796  			info.VnicSets = vnicSets
   797  		}
   798  	}
   799  
   800  	return info, nil
   801  }
   802  
   803  // Validates an IP Network config block
   804  func validateIPNetwork(ni map[string]interface{}) error {
   805  	// An IP Networking Interface MUST have the following attributes set:
   806  	// - "ip_network"
   807  
   808  	// Required to be set
   809  	if d, ok := ni["ip_network"]; !ok || d.(string) == "" {
   810  		return fmt.Errorf("'ip_network' field is required for an IP Network interface")
   811  	}
   812  
   813  	return nil
   814  }
   815  
   816  // Reads network interfaces from the config
   817  func readNetworkInterfaces(d *schema.ResourceData, ifaces map[string]compute.NetworkingInfo) error {
   818  	result := make([]map[string]interface{}, 0)
   819  
   820  	// Nil check for import case
   821  	if ifaces == nil {
   822  		return d.Set("networking_info", result)
   823  	}
   824  
   825  	for index, iface := range ifaces {
   826  		res := make(map[string]interface{})
   827  		// The index returned from the SDK holds the full device_index from the instance.
   828  		// For users convenience, we simply allow them to specify the integer equivalent of the device_index
   829  		// so a user could implement several network interfaces via `count`.
   830  		// Convert the full device_index `ethN` to `N` as an integer.
   831  		index := strings.TrimPrefix(index, "eth")
   832  		indexInt, err := strconv.Atoi(index)
   833  		if err != nil {
   834  			return err
   835  		}
   836  		res["index"] = indexInt
   837  
   838  		// Set the proper attributes for this specific network interface
   839  		if iface.DNS != nil {
   840  			res["dns"] = iface.DNS
   841  		}
   842  		if iface.IPAddress != "" {
   843  			res["ip_address"] = iface.IPAddress
   844  		}
   845  		if iface.IPNetwork != "" {
   846  			res["ip_network"] = iface.IPNetwork
   847  		}
   848  		if iface.MACAddress != "" {
   849  			res["mac_address"] = iface.MACAddress
   850  		}
   851  		if iface.Model != "" {
   852  			// Model can only be set on Shared networks
   853  			res["shared_network"] = true
   854  		}
   855  		if iface.NameServers != nil {
   856  			res["name_servers"] = iface.NameServers
   857  		}
   858  		if iface.Nat != nil {
   859  			res["nat"] = iface.Nat
   860  		}
   861  		if iface.SearchDomains != nil {
   862  			res["search_domains"] = iface.SearchDomains
   863  		}
   864  		if iface.SecLists != nil {
   865  			res["sec_lists"] = iface.SecLists
   866  		}
   867  		if iface.Vnic != "" {
   868  			res["vnic"] = iface.Vnic
   869  			// VNIC can only be set on an IP Network
   870  			res["shared_network"] = false
   871  		}
   872  		if iface.VnicSets != nil {
   873  			res["vnic_sets"] = iface.VnicSets
   874  		}
   875  
   876  		result = append(result, res)
   877  	}
   878  
   879  	return d.Set("networking_info", result)
   880  }
   881  
   882  // Flattens the returned slice of storage attachments to a map
   883  func readStorageAttachments(d *schema.ResourceData, attachments []compute.StorageAttachment) error {
   884  	result := make([]map[string]interface{}, 0)
   885  
   886  	if attachments == nil || len(attachments) == 0 {
   887  		return d.Set("storage", nil)
   888  	}
   889  
   890  	for _, attachment := range attachments {
   891  		res := make(map[string]interface{})
   892  		res["index"] = attachment.Index
   893  		res["volume"] = attachment.StorageVolumeName
   894  		res["name"] = attachment.Name
   895  		result = append(result, res)
   896  	}
   897  	return d.Set("storage", result)
   898  }