blob: 8b4ae37bd547ea52731177c0221e1bb4dcbd4905 [file] [log] [blame]
//
// 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/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_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)
d.Partial(true)
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()
}
// Compute/set the display text
displaytext, ok := d.GetOk("display_text")
if !ok {
displaytext = name
}
// Create a new parameter struct
p := cs.Network.NewCreateNetworkParams(displaytext.(string), name, networkofferingid, zoneid)
// 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 assiciate 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.SetPartial("name")
d.SetPartial("display_text")
d.SetPartial("cidr")
d.SetPartial("gateway")
d.SetPartial("startip")
d.SetPartial("endip")
d.SetPartial("network_domain")
d.SetPartial("network_offering")
d.SetPartial("vlan")
d.SetPartial("vpc_id")
d.SetPartial("acl_id")
d.SetPartial("project")
d.SetPartial("zone")
d.SetId(r.Id)
// Set tags if necessary
if err = setTags(cs, d, "network"); err != nil {
return fmt.Errorf("Error setting tags: %v", err)
}
d.SetPartial("tags")
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_id", ip.Id)
// Set the additional partial
d.SetPartial("source_nat_ip")
d.SetPartial("source_nat_ip_id")
}
d.Partial(false)
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
}