github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/builtin/providers/cloudstack/resource_cloudstack_port_forward.go (about) 1 package cloudstack 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/terraform/helper/hashcode" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/xanzy/go-cloudstack/cloudstack" 13 ) 14 15 func resourceCloudStackPortForward() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceCloudStackPortForwardCreate, 18 Read: resourceCloudStackPortForwardRead, 19 Update: resourceCloudStackPortForwardUpdate, 20 Delete: resourceCloudStackPortForwardDelete, 21 22 Schema: map[string]*schema.Schema{ 23 "ipaddress": &schema.Schema{ 24 Type: schema.TypeString, 25 Required: true, 26 ForceNew: true, 27 }, 28 29 "managed": &schema.Schema{ 30 Type: schema.TypeBool, 31 Optional: true, 32 Default: false, 33 }, 34 35 "forward": &schema.Schema{ 36 Type: schema.TypeSet, 37 Required: true, 38 Elem: &schema.Resource{ 39 Schema: map[string]*schema.Schema{ 40 "protocol": &schema.Schema{ 41 Type: schema.TypeString, 42 Required: true, 43 }, 44 45 "private_port": &schema.Schema{ 46 Type: schema.TypeInt, 47 Required: true, 48 }, 49 50 "public_port": &schema.Schema{ 51 Type: schema.TypeInt, 52 Required: true, 53 }, 54 55 "virtual_machine": &schema.Schema{ 56 Type: schema.TypeString, 57 Required: true, 58 }, 59 60 "uuid": &schema.Schema{ 61 Type: schema.TypeString, 62 Computed: true, 63 }, 64 }, 65 }, 66 Set: resourceCloudStackPortForwardHash, 67 }, 68 }, 69 } 70 } 71 72 func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error { 73 cs := meta.(*cloudstack.CloudStackClient) 74 75 // Retrieve the ipaddress UUID 76 ipaddressid, e := retrieveUUID(cs, "ipaddress", d.Get("ipaddress").(string)) 77 if e != nil { 78 return e.Error() 79 } 80 81 // We need to set this upfront in order to be able to save a partial state 82 d.SetId(ipaddressid) 83 84 // Create all forwards that are configured 85 if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 { 86 87 // Create an empty schema.Set to hold all forwards 88 forwards := &schema.Set{ 89 F: resourceCloudStackPortForwardHash, 90 } 91 92 for _, forward := range rs.List() { 93 // Create a single forward 94 err := resourceCloudStackPortForwardCreateForward(d, meta, forward.(map[string]interface{})) 95 96 // We need to update this first to preserve the correct state 97 forwards.Add(forward) 98 d.Set("forward", forwards) 99 100 if err != nil { 101 return err 102 } 103 } 104 } 105 106 return resourceCloudStackPortForwardRead(d, meta) 107 } 108 109 func resourceCloudStackPortForwardCreateForward( 110 d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error { 111 cs := meta.(*cloudstack.CloudStackClient) 112 113 // Make sure all required parameters are there 114 if err := verifyPortForwardParams(d, forward); err != nil { 115 return err 116 } 117 118 // Retrieve the virtual_machine UUID 119 vm, _, err := cs.VirtualMachine.GetVirtualMachineByName(forward["virtual_machine"].(string)) 120 if err != nil { 121 return err 122 } 123 124 // Create a new parameter struct 125 p := cs.Firewall.NewCreatePortForwardingRuleParams(d.Id(), forward["private_port"].(int), 126 forward["protocol"].(string), forward["public_port"].(int), vm.Id) 127 128 // Set the network ID of the default network, needed when public IP address 129 // is not associated with any Guest network yet (VPC case) 130 p.SetNetworkid(vm.Nic[0].Networkid) 131 132 // Do not open the firewall automatically in any case 133 p.SetOpenfirewall(false) 134 135 r, err := cs.Firewall.CreatePortForwardingRule(p) 136 if err != nil { 137 return err 138 } 139 140 forward["uuid"] = r.Id 141 142 return nil 143 } 144 145 func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error { 146 cs := meta.(*cloudstack.CloudStackClient) 147 148 // Create an empty schema.Set to hold all forwards 149 forwards := &schema.Set{ 150 F: resourceCloudStackPortForwardHash, 151 } 152 153 // Read all forwards that are configured 154 if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 { 155 for _, forward := range rs.List() { 156 forward := forward.(map[string]interface{}) 157 158 id, ok := forward["uuid"] 159 if !ok || id.(string) == "" { 160 continue 161 } 162 163 // Get the forward 164 r, count, err := cs.Firewall.GetPortForwardingRuleByID(id.(string)) 165 // If the count == 0, there is no object found for this UUID 166 if err != nil { 167 if count == 0 { 168 forward["uuid"] = "" 169 continue 170 } 171 172 return err 173 } 174 175 privPort, err := strconv.Atoi(r.Privateport) 176 if err != nil { 177 return err 178 } 179 180 pubPort, err := strconv.Atoi(r.Publicport) 181 if err != nil { 182 return err 183 } 184 185 // Update the values 186 forward["protocol"] = r.Protocol 187 forward["private_port"] = privPort 188 forward["public_port"] = pubPort 189 forward["virtual_machine"] = r.Virtualmachinename 190 forwards.Add(forward) 191 } 192 } 193 194 // If this is a managed resource, add all unknown forwards to dummy forwards 195 managed := d.Get("managed").(bool) 196 if managed { 197 // Get all the forwards from the running environment 198 p := cs.Firewall.NewListPortForwardingRulesParams() 199 p.SetIpaddressid(d.Id()) 200 p.SetListall(true) 201 202 r, err := cs.Firewall.ListPortForwardingRules(p) 203 if err != nil { 204 return err 205 } 206 207 // Add all UUIDs to the uuids map 208 uuids := make(map[string]interface{}, len(r.PortForwardingRules)) 209 for _, r := range r.PortForwardingRules { 210 uuids[r.Id] = r.Id 211 } 212 213 // Delete all expected UUIDs from the uuids map 214 for _, forward := range forwards.List() { 215 forward := forward.(map[string]interface{}) 216 217 for _, id := range forward["uuids"].(map[string]interface{}) { 218 delete(uuids, id.(string)) 219 } 220 } 221 222 for uuid, _ := range uuids { 223 // Make a dummy forward to hold the unknown UUID 224 forward := map[string]interface{}{ 225 "protocol": "N/A", 226 "private_port": 0, 227 "public_port": 0, 228 "virtual_machine": uuid, 229 "uuid": uuid, 230 } 231 232 // Add the dummy forward to the forwards set 233 forwards.Add(forward) 234 } 235 } 236 237 if forwards.Len() > 0 { 238 d.Set("forward", forwards) 239 } else if !managed { 240 d.SetId("") 241 } 242 243 return nil 244 } 245 246 func resourceCloudStackPortForwardUpdate(d *schema.ResourceData, meta interface{}) error { 247 // Check if the forward set as a whole has changed 248 if d.HasChange("forward") { 249 o, n := d.GetChange("forward") 250 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 251 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 252 253 // Now first loop through all the old forwards and delete any obsolete ones 254 for _, forward := range ors.List() { 255 // Delete the forward as it no longer exists in the config 256 err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{})) 257 if err != nil { 258 return err 259 } 260 } 261 262 // Make sure we save the state of the currently configured forwards 263 forwards := o.(*schema.Set).Intersection(n.(*schema.Set)) 264 d.Set("forward", forwards) 265 266 // Then loop through all the currently configured forwards and create the new ones 267 for _, forward := range nrs.List() { 268 err := resourceCloudStackPortForwardCreateForward( 269 d, meta, forward.(map[string]interface{})) 270 271 // We need to update this first to preserve the correct state 272 forwards.Add(forward) 273 d.Set("forward", forwards) 274 275 if err != nil { 276 return err 277 } 278 } 279 } 280 281 return resourceCloudStackPortForwardRead(d, meta) 282 } 283 284 func resourceCloudStackPortForwardDelete(d *schema.ResourceData, meta interface{}) error { 285 // Delete all forwards 286 if rs := d.Get("forward").(*schema.Set); rs.Len() > 0 { 287 for _, forward := range rs.List() { 288 // Delete a single forward 289 err := resourceCloudStackPortForwardDeleteForward(d, meta, forward.(map[string]interface{})) 290 291 // We need to update this first to preserve the correct state 292 d.Set("forward", rs) 293 294 if err != nil { 295 return err 296 } 297 } 298 } 299 300 return nil 301 } 302 303 func resourceCloudStackPortForwardDeleteForward( 304 d *schema.ResourceData, meta interface{}, forward map[string]interface{}) error { 305 cs := meta.(*cloudstack.CloudStackClient) 306 307 // Create the parameter struct 308 p := cs.Firewall.NewDeletePortForwardingRuleParams(forward["uuid"].(string)) 309 310 // Delete the forward 311 if _, err := cs.Firewall.DeletePortForwardingRule(p); err != nil { 312 // This is a very poor way to be told the UUID does no longer exist :( 313 if !strings.Contains(err.Error(), fmt.Sprintf( 314 "Invalid parameter id value=%s due to incorrect long value format, "+ 315 "or entity does not exist", forward["uuid"].(string))) { 316 return err 317 } 318 } 319 320 forward["uuid"] = "" 321 322 return nil 323 } 324 325 func resourceCloudStackPortForwardHash(v interface{}) int { 326 var buf bytes.Buffer 327 m := v.(map[string]interface{}) 328 buf.WriteString(fmt.Sprintf( 329 "%s-%d-%d-%s", 330 m["protocol"].(string), 331 m["private_port"].(int), 332 m["public_port"].(int), 333 m["virtual_machine"].(string))) 334 335 return hashcode.String(buf.String()) 336 } 337 338 func verifyPortForwardParams(d *schema.ResourceData, forward map[string]interface{}) error { 339 protocol := forward["protocol"].(string) 340 if protocol != "tcp" && protocol != "udp" { 341 return fmt.Errorf( 342 "%s is not a valid protocol. Valid options are 'tcp' and 'udp'", protocol) 343 } 344 return nil 345 }