cloudstack/resource_cloudstack_network.go (332 lines of code) (raw):

// // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // package cloudstack import ( "fmt" "log" "net" "strconv" "strings" "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) const none = "none" func resourceCloudStackNetwork() *schema.Resource { aclidSchema := &schema.Schema{ Type: schema.TypeString, Optional: true, Default: none, } aclidSchema.StateFunc = func(v interface{}) string { value := v.(string) if value == none { aclidSchema.ForceNew = true } else { aclidSchema.ForceNew = false } return value } return &schema.Resource{ Create: resourceCloudStackNetworkCreate, Read: resourceCloudStackNetworkRead, Update: resourceCloudStackNetworkUpdate, Delete: resourceCloudStackNetworkDelete, Importer: &schema.ResourceImporter{ State: importStatePassthrough, }, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, }, "display_text": { Type: schema.TypeString, Optional: true, Computed: true, }, "cidr": { Type: schema.TypeString, Required: true, ForceNew: true, }, "gateway": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "startip": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "endip": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "network_domain": { Type: schema.TypeString, Optional: true, Computed: true, }, "network_offering": { Type: schema.TypeString, Required: true, }, "vlan": { Type: schema.TypeInt, Optional: true, ForceNew: true, }, "vpc_id": { Type: schema.TypeString, Optional: true, ForceNew: true, }, "acl_id": aclidSchema, "project": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, "source_nat_ip": { Type: schema.TypeBool, Optional: true, ForceNew: true, }, "source_nat_ip_address": { Type: schema.TypeString, Computed: true, }, "source_nat_ip_id": { Type: schema.TypeString, Computed: true, }, "zone": { Type: schema.TypeString, Required: true, ForceNew: true, }, "tags": tagsSchema(), }, } } func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) name := d.Get("name").(string) // Retrieve the network_offering ID networkofferingid, e := retrieveID(cs, "network_offering", d.Get("network_offering").(string)) if e != nil { return e.Error() } // Retrieve the zone ID zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string)) if e != nil { return e.Error() } // Create a new parameter struct p := cs.Network.NewCreateNetworkParams(name, networkofferingid, zoneid) if displaytext, ok := d.GetOk("display_text"); ok { p.SetDisplaytext(displaytext.(string)) } else { p.SetDisplaytext(name) } // Get the network offering to check if it supports specifying IP ranges no, _, err := cs.NetworkOffering.GetNetworkOfferingByID(networkofferingid) if err != nil { return err } m, err := parseCIDR(d, no.Specifyipranges) if err != nil { return err } // Set the needed IP config p.SetGateway(m["gateway"]) p.SetNetmask(m["netmask"]) // Only set the start IP if we have one if startip, ok := m["startip"]; ok { p.SetStartip(startip) } // Only set the end IP if we have one if endip, ok := m["endip"]; ok { p.SetEndip(endip) } // Set the network domain if we have one if networkDomain, ok := d.GetOk("network_domain"); ok { p.SetNetworkdomain(networkDomain.(string)) } if vlan, ok := d.GetOk("vlan"); ok { p.SetVlan(strconv.Itoa(vlan.(int))) } // Check is this network needs to be created in a VPC if vpcid, ok := d.GetOk("vpc_id"); ok { // Set the vpc id p.SetVpcid(vpcid.(string)) // Since we're in a VPC, check if we want to associate an ACL list if aclid, ok := d.GetOk("acl_id"); ok && aclid.(string) != none { // Set the acl ID p.SetAclid(aclid.(string)) } } // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } // Create the new network r, err := cs.Network.CreateNetwork(p) if err != nil { return fmt.Errorf("Error creating network %s: %s", name, err) } d.SetId(r.Id) // Set tags if necessary if err = setTags(cs, d, "network"); err != nil { return fmt.Errorf("Error setting tags: %v", err) } if d.Get("source_nat_ip").(bool) { // Create a new parameter struct p := cs.Address.NewAssociateIpAddressParams() // Set required options p.SetNetworkid(r.Id) p.SetZoneid(zoneid) if vpcid, ok := d.GetOk("vpc_id"); ok { // Set the vpcid p.SetVpcid(vpcid.(string)) } // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } // Associate a new IP address ip, err := cs.Address.AssociateIpAddress(p) if err != nil { return fmt.Errorf("Error associating a new IP address: %s", err) } d.Set("source_nat_ip_address", ip.Ipaddress) d.Set("source_nat_ip_id", ip.Id) // Set the additional partial } return resourceCloudStackNetworkRead(d, meta) } func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) // Get the virtual machine details n, count, err := cs.Network.GetNetworkByID( d.Id(), cloudstack.WithProject(d.Get("project").(string)), ) if err != nil { if count == 0 { log.Printf( "[DEBUG] Network %s does no longer exist", d.Get("name").(string)) d.SetId("") return nil } return err } d.Set("name", n.Name) d.Set("display_text", n.Displaytext) d.Set("cidr", n.Cidr) d.Set("gateway", n.Gateway) d.Set("network_domain", n.Networkdomain) d.Set("vpc_id", n.Vpcid) if n.Aclid == "" { n.Aclid = none } d.Set("acl_id", n.Aclid) tags := make(map[string]interface{}) for _, tag := range n.Tags { tags[tag.Key] = tag.Value } d.Set("tags", tags) setValueOrID(d, "network_offering", n.Networkofferingname, n.Networkofferingid) setValueOrID(d, "project", n.Project, n.Projectid) setValueOrID(d, "zone", n.Zonename, n.Zoneid) if d.Get("source_nat_ip").(bool) { ip, count, err := cs.Address.GetPublicIpAddressByID( d.Get("source_nat_ip_id").(string), cloudstack.WithProject(d.Get("project").(string)), ) if err != nil { if count == 0 { log.Printf( "[DEBUG] Source NAT IP with ID %s is no longer associated", d.Id()) d.Set("source_nat_ip", false) d.Set("source_nat_ip_id", "") return nil } return err } if n.Id != ip.Associatednetworkid { d.Set("source_nat_ip", false) d.Set("source_nat_ip_id", "") } } return nil } func resourceCloudStackNetworkUpdate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) name := d.Get("name").(string) // Create a new parameter struct p := cs.Network.NewUpdateNetworkParams(d.Id()) // Check if the name or display text is changed if d.HasChange("name") || d.HasChange("display_text") { p.SetName(name) // Compute/set the display text displaytext := d.Get("display_text").(string) if displaytext == "" { displaytext = name } p.SetDisplaytext(displaytext) } // Check if the cidr is changed if d.HasChange("cidr") { p.SetGuestvmcidr(d.Get("cidr").(string)) } // Check if the network domain is changed if d.HasChange("network_domain") { p.SetNetworkdomain(d.Get("network_domain").(string)) } // Check if the network offering is changed if d.HasChange("network_offering") { // Retrieve the network_offering ID networkofferingid, e := retrieveID(cs, "network_offering", d.Get("network_offering").(string)) if e != nil { return e.Error() } // Set the new network offering p.SetNetworkofferingid(networkofferingid) } // Update the network _, err := cs.Network.UpdateNetwork(p) if err != nil { return fmt.Errorf( "Error updating network %s: %s", name, err) } // Replace the ACL if the ID has changed if d.HasChange("acl_id") { p := cs.NetworkACL.NewReplaceNetworkACLListParams(d.Get("acl_id").(string)) p.SetNetworkid(d.Id()) _, err := cs.NetworkACL.ReplaceNetworkACLList(p) if err != nil { return fmt.Errorf("Error replacing ACL: %s", err) } } // Update tags if they have changed if d.HasChange("tags") { if err := updateTags(cs, d, "Network"); err != nil { return fmt.Errorf("Error updating tags on ACL %s: %s", name, err) } } return resourceCloudStackNetworkRead(d, meta) } func resourceCloudStackNetworkDelete(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) // Create a new parameter struct p := cs.Network.NewDeleteNetworkParams(d.Id()) // Delete the network _, err := cs.Network.DeleteNetwork(p) if err != nil { // This is a very poor way to be told the ID does no longer exist :( if strings.Contains(err.Error(), fmt.Sprintf( "Invalid parameter id value=%s due to incorrect long value format, "+ "or entity does not exist", d.Id())) { return nil } return fmt.Errorf("Error deleting network %s: %s", d.Get("name").(string), err) } return nil } func parseCIDR(d *schema.ResourceData, specifyiprange bool) (map[string]string, error) { m := make(map[string]string, 4) cidr := d.Get("cidr").(string) ip, ipnet, err := net.ParseCIDR(cidr) if err != nil { return nil, fmt.Errorf("Unable to parse cidr %s: %s", cidr, err) } msk := ipnet.Mask sub := ip.Mask(msk) m["netmask"] = fmt.Sprintf("%d.%d.%d.%d", msk[0], msk[1], msk[2], msk[3]) if gateway, ok := d.GetOk("gateway"); ok { m["gateway"] = gateway.(string) } else { m["gateway"] = fmt.Sprintf("%d.%d.%d.%d", sub[0], sub[1], sub[2], sub[3]+1) } if startip, ok := d.GetOk("startip"); ok { m["startip"] = startip.(string) } else if specifyiprange { m["startip"] = fmt.Sprintf("%d.%d.%d.%d", sub[0], sub[1], sub[2], sub[3]+2) } if endip, ok := d.GetOk("endip"); ok { m["endip"] = endip.(string) } else if specifyiprange { m["endip"] = fmt.Sprintf("%d.%d.%d.%d", sub[0]+(0xff-msk[0]), sub[1]+(0xff-msk[1]), sub[2]+(0xff-msk[2]), sub[3]+(0xff-msk[3]-1)) } return m, nil }