PageRenderTime 30ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/builtin/providers/cloudstack/resource_cloudstack_network_acl_rule.go

https://gitlab.com/biopandemic/terraform
Go | 609 lines | 443 code | 108 blank | 58 comment | 114 complexity | 7fd86a1971a5746fc66460b179ec753f MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. package cloudstack
  2. import (
  3. "bytes"
  4. "fmt"
  5. "regexp"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "github.com/hashicorp/terraform/helper/hashcode"
  10. "github.com/hashicorp/terraform/helper/schema"
  11. "github.com/xanzy/go-cloudstack/cloudstack"
  12. )
  13. func resourceCloudStackNetworkACLRule() *schema.Resource {
  14. return &schema.Resource{
  15. Create: resourceCloudStackNetworkACLRuleCreate,
  16. Read: resourceCloudStackNetworkACLRuleRead,
  17. Update: resourceCloudStackNetworkACLRuleUpdate,
  18. Delete: resourceCloudStackNetworkACLRuleDelete,
  19. Schema: map[string]*schema.Schema{
  20. "aclid": &schema.Schema{
  21. Type: schema.TypeString,
  22. Required: true,
  23. ForceNew: true,
  24. },
  25. "managed": &schema.Schema{
  26. Type: schema.TypeBool,
  27. Optional: true,
  28. Default: false,
  29. },
  30. "rule": &schema.Schema{
  31. Type: schema.TypeSet,
  32. Optional: true,
  33. Elem: &schema.Resource{
  34. Schema: map[string]*schema.Schema{
  35. "action": &schema.Schema{
  36. Type: schema.TypeString,
  37. Optional: true,
  38. Default: "allow",
  39. },
  40. "source_cidr": &schema.Schema{
  41. Type: schema.TypeString,
  42. Required: true,
  43. },
  44. "protocol": &schema.Schema{
  45. Type: schema.TypeString,
  46. Required: true,
  47. },
  48. "icmp_type": &schema.Schema{
  49. Type: schema.TypeInt,
  50. Optional: true,
  51. Computed: true,
  52. },
  53. "icmp_code": &schema.Schema{
  54. Type: schema.TypeInt,
  55. Optional: true,
  56. Computed: true,
  57. },
  58. "ports": &schema.Schema{
  59. Type: schema.TypeSet,
  60. Optional: true,
  61. Elem: &schema.Schema{Type: schema.TypeString},
  62. Set: func(v interface{}) int {
  63. return hashcode.String(v.(string))
  64. },
  65. },
  66. "traffic_type": &schema.Schema{
  67. Type: schema.TypeString,
  68. Optional: true,
  69. Default: "ingress",
  70. },
  71. "uuids": &schema.Schema{
  72. Type: schema.TypeMap,
  73. Computed: true,
  74. },
  75. },
  76. },
  77. Set: resourceCloudStackNetworkACLRuleHash,
  78. },
  79. },
  80. }
  81. }
  82. func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interface{}) error {
  83. // Make sure all required parameters are there
  84. if err := verifyNetworkACLParams(d); err != nil {
  85. return err
  86. }
  87. // We need to set this upfront in order to be able to save a partial state
  88. d.SetId(d.Get("aclid").(string))
  89. // Create all rules that are configured
  90. if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
  91. // Create an empty schema.Set to hold all rules
  92. rules := &schema.Set{
  93. F: resourceCloudStackNetworkACLRuleHash,
  94. }
  95. for _, rule := range rs.List() {
  96. // Create a single rule
  97. err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{}))
  98. // We need to update this first to preserve the correct state
  99. rules.Add(rule)
  100. d.Set("rule", rules)
  101. if err != nil {
  102. return err
  103. }
  104. }
  105. }
  106. return resourceCloudStackNetworkACLRuleRead(d, meta)
  107. }
  108. func resourceCloudStackNetworkACLRuleCreateRule(
  109. d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
  110. cs := meta.(*cloudstack.CloudStackClient)
  111. uuids := rule["uuids"].(map[string]interface{})
  112. // Make sure all required parameters are there
  113. if err := verifyNetworkACLRuleParams(d, rule); err != nil {
  114. return err
  115. }
  116. // Create a new parameter struct
  117. p := cs.NetworkACL.NewCreateNetworkACLParams(rule["protocol"].(string))
  118. // Set the acl ID
  119. p.SetAclid(d.Id())
  120. // Set the action
  121. p.SetAction(rule["action"].(string))
  122. // Set the CIDR list
  123. p.SetCidrlist([]string{rule["source_cidr"].(string)})
  124. // Set the traffic type
  125. p.SetTraffictype(rule["traffic_type"].(string))
  126. // If the protocol is ICMP set the needed ICMP parameters
  127. if rule["protocol"].(string) == "icmp" {
  128. p.SetIcmptype(rule["icmp_type"].(int))
  129. p.SetIcmpcode(rule["icmp_code"].(int))
  130. r, err := Retry(4, retryableACLCreationFunc(cs, p))
  131. if err != nil {
  132. return err
  133. }
  134. uuids["icmp"] = r.(*cloudstack.CreateNetworkACLResponse).Id
  135. rule["uuids"] = uuids
  136. }
  137. // If the protocol is ALL set the needed parameters
  138. if rule["protocol"].(string) == "all" {
  139. r, err := Retry(4, retryableACLCreationFunc(cs, p))
  140. if err != nil {
  141. return err
  142. }
  143. uuids["all"] = r.(*cloudstack.CreateNetworkACLResponse).Id
  144. rule["uuids"] = uuids
  145. }
  146. // If protocol is TCP or UDP, loop through all ports
  147. if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" {
  148. if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
  149. // Create an empty schema.Set to hold all processed ports
  150. ports := &schema.Set{
  151. F: func(v interface{}) int {
  152. return hashcode.String(v.(string))
  153. },
  154. }
  155. for _, port := range ps.List() {
  156. re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
  157. m := re.FindStringSubmatch(port.(string))
  158. startPort, err := strconv.Atoi(m[1])
  159. if err != nil {
  160. return err
  161. }
  162. endPort := startPort
  163. if m[2] != "" {
  164. endPort, err = strconv.Atoi(m[2])
  165. if err != nil {
  166. return err
  167. }
  168. }
  169. p.SetStartport(startPort)
  170. p.SetEndport(endPort)
  171. r, err := Retry(4, retryableACLCreationFunc(cs, p))
  172. if err != nil {
  173. return err
  174. }
  175. ports.Add(port)
  176. rule["ports"] = ports
  177. uuids[port.(string)] = r.(*cloudstack.CreateNetworkACLResponse).Id
  178. rule["uuids"] = uuids
  179. }
  180. }
  181. }
  182. return nil
  183. }
  184. func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface{}) error {
  185. cs := meta.(*cloudstack.CloudStackClient)
  186. // Create an empty schema.Set to hold all rules
  187. rules := &schema.Set{
  188. F: resourceCloudStackNetworkACLRuleHash,
  189. }
  190. // Read all rules that are configured
  191. if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
  192. for _, rule := range rs.List() {
  193. rule := rule.(map[string]interface{})
  194. uuids := rule["uuids"].(map[string]interface{})
  195. if rule["protocol"].(string) == "icmp" {
  196. id, ok := uuids["icmp"]
  197. if !ok {
  198. continue
  199. }
  200. // Get the rule
  201. r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
  202. // If the count == 0, there is no object found for this ID
  203. if err != nil {
  204. if count == 0 {
  205. delete(uuids, "icmp")
  206. continue
  207. }
  208. return err
  209. }
  210. // Update the values
  211. rule["action"] = strings.ToLower(r.Action)
  212. rule["source_cidr"] = r.Cidrlist
  213. rule["protocol"] = r.Protocol
  214. rule["icmp_type"] = r.Icmptype
  215. rule["icmp_code"] = r.Icmpcode
  216. rule["traffic_type"] = strings.ToLower(r.Traffictype)
  217. rules.Add(rule)
  218. }
  219. if rule["protocol"].(string) == "all" {
  220. id, ok := uuids["all"]
  221. if !ok {
  222. continue
  223. }
  224. // Get the rule
  225. r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
  226. // If the count == 0, there is no object found for this ID
  227. if err != nil {
  228. if count == 0 {
  229. delete(uuids, "all")
  230. continue
  231. }
  232. return err
  233. }
  234. // Update the values
  235. rule["action"] = strings.ToLower(r.Action)
  236. rule["source_cidr"] = r.Cidrlist
  237. rule["protocol"] = r.Protocol
  238. rule["traffic_type"] = strings.ToLower(r.Traffictype)
  239. rules.Add(rule)
  240. }
  241. // If protocol is tcp or udp, loop through all ports
  242. if rule["protocol"].(string) == "tcp" || rule["protocol"].(string) == "udp" {
  243. if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
  244. // Create an empty schema.Set to hold all ports
  245. ports := &schema.Set{
  246. F: func(v interface{}) int {
  247. return hashcode.String(v.(string))
  248. },
  249. }
  250. // Loop through all ports and retrieve their info
  251. for _, port := range ps.List() {
  252. id, ok := uuids[port.(string)]
  253. if !ok {
  254. continue
  255. }
  256. // Get the rule
  257. r, count, err := cs.NetworkACL.GetNetworkACLByID(id.(string))
  258. if err != nil {
  259. if count == 0 {
  260. delete(uuids, port.(string))
  261. continue
  262. }
  263. return err
  264. }
  265. // Update the values
  266. rule["action"] = strings.ToLower(r.Action)
  267. rule["source_cidr"] = r.Cidrlist
  268. rule["protocol"] = r.Protocol
  269. rule["traffic_type"] = strings.ToLower(r.Traffictype)
  270. ports.Add(port)
  271. }
  272. // If there is at least one port found, add this rule to the rules set
  273. if ports.Len() > 0 {
  274. rule["ports"] = ports
  275. rules.Add(rule)
  276. }
  277. }
  278. }
  279. }
  280. }
  281. // If this is a managed firewall, add all unknown rules into a single dummy rule
  282. managed := d.Get("managed").(bool)
  283. if managed {
  284. // Get all the rules from the running environment
  285. p := cs.NetworkACL.NewListNetworkACLsParams()
  286. p.SetAclid(d.Id())
  287. p.SetListall(true)
  288. r, err := cs.NetworkACL.ListNetworkACLs(p)
  289. if err != nil {
  290. return err
  291. }
  292. // Add all UUIDs to the uuids map
  293. uuids := make(map[string]interface{}, len(r.NetworkACLs))
  294. for _, r := range r.NetworkACLs {
  295. uuids[r.Id] = r.Id
  296. }
  297. // Delete all expected UUIDs from the uuids map
  298. for _, rule := range rules.List() {
  299. rule := rule.(map[string]interface{})
  300. for _, id := range rule["uuids"].(map[string]interface{}) {
  301. delete(uuids, id.(string))
  302. }
  303. }
  304. if len(uuids) > 0 {
  305. // Make a dummy rule to hold all unknown UUIDs
  306. rule := map[string]interface{}{
  307. "source_cidr": "N/A",
  308. "protocol": "N/A",
  309. "uuids": uuids,
  310. }
  311. // Add the dummy rule to the rules set
  312. rules.Add(rule)
  313. }
  314. }
  315. if rules.Len() > 0 {
  316. d.Set("rule", rules)
  317. } else if !managed {
  318. d.SetId("")
  319. }
  320. return nil
  321. }
  322. func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interface{}) error {
  323. // Make sure all required parameters are there
  324. if err := verifyNetworkACLParams(d); err != nil {
  325. return err
  326. }
  327. // Check if the rule set as a whole has changed
  328. if d.HasChange("rule") {
  329. o, n := d.GetChange("rule")
  330. ors := o.(*schema.Set).Difference(n.(*schema.Set))
  331. nrs := n.(*schema.Set).Difference(o.(*schema.Set))
  332. // Now first loop through all the old rules and delete any obsolete ones
  333. for _, rule := range ors.List() {
  334. // Delete the rule as it no longer exists in the config
  335. err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
  336. if err != nil {
  337. return err
  338. }
  339. }
  340. // Make sure we save the state of the currently configured rules
  341. rules := o.(*schema.Set).Intersection(n.(*schema.Set))
  342. d.Set("rule", rules)
  343. // Then loop through all the currently configured rules and create the new ones
  344. for _, rule := range nrs.List() {
  345. // When successfully deleted, re-create it again if it still exists
  346. err := resourceCloudStackNetworkACLRuleCreateRule(d, meta, rule.(map[string]interface{}))
  347. // We need to update this first to preserve the correct state
  348. rules.Add(rule)
  349. d.Set("rule", rules)
  350. if err != nil {
  351. return err
  352. }
  353. }
  354. }
  355. return resourceCloudStackNetworkACLRuleRead(d, meta)
  356. }
  357. func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta interface{}) error {
  358. // Delete all rules
  359. if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
  360. for _, rule := range rs.List() {
  361. // Delete a single rule
  362. err := resourceCloudStackNetworkACLRuleDeleteRule(d, meta, rule.(map[string]interface{}))
  363. // We need to update this first to preserve the correct state
  364. d.Set("rule", rs)
  365. if err != nil {
  366. return err
  367. }
  368. }
  369. }
  370. return nil
  371. }
  372. func resourceCloudStackNetworkACLRuleDeleteRule(
  373. d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
  374. cs := meta.(*cloudstack.CloudStackClient)
  375. uuids := rule["uuids"].(map[string]interface{})
  376. for k, id := range uuids {
  377. // We don't care about the count here, so just continue
  378. if k == "#" {
  379. continue
  380. }
  381. // Create the parameter struct
  382. p := cs.NetworkACL.NewDeleteNetworkACLParams(id.(string))
  383. // Delete the rule
  384. if _, err := cs.NetworkACL.DeleteNetworkACL(p); err != nil {
  385. // This is a very poor way to be told the ID does no longer exist :(
  386. if strings.Contains(err.Error(), fmt.Sprintf(
  387. "Invalid parameter id value=%s due to incorrect long value format, "+
  388. "or entity does not exist", id.(string))) {
  389. delete(uuids, k)
  390. continue
  391. }
  392. return err
  393. }
  394. // Delete the UUID of this rule
  395. delete(uuids, k)
  396. }
  397. // Update the UUIDs
  398. rule["uuids"] = uuids
  399. return nil
  400. }
  401. func resourceCloudStackNetworkACLRuleHash(v interface{}) int {
  402. var buf bytes.Buffer
  403. m := v.(map[string]interface{})
  404. // This is a little ugly, but it's needed because these arguments have
  405. // a default value that needs to be part of the string to hash
  406. var action, trafficType string
  407. if a, ok := m["action"]; ok {
  408. action = a.(string)
  409. } else {
  410. action = "allow"
  411. }
  412. if t, ok := m["traffic_type"]; ok {
  413. trafficType = t.(string)
  414. } else {
  415. trafficType = "ingress"
  416. }
  417. buf.WriteString(fmt.Sprintf(
  418. "%s-%s-%s-%s-",
  419. action,
  420. m["source_cidr"].(string),
  421. m["protocol"].(string),
  422. trafficType))
  423. if v, ok := m["icmp_type"]; ok {
  424. buf.WriteString(fmt.Sprintf("%d-", v.(int)))
  425. }
  426. if v, ok := m["icmp_code"]; ok {
  427. buf.WriteString(fmt.Sprintf("%d-", v.(int)))
  428. }
  429. // We need to make sure to sort the strings below so that we always
  430. // generate the same hash code no matter what is in the set.
  431. if v, ok := m["ports"]; ok {
  432. vs := v.(*schema.Set).List()
  433. s := make([]string, len(vs))
  434. for i, raw := range vs {
  435. s[i] = raw.(string)
  436. }
  437. sort.Strings(s)
  438. for _, v := range s {
  439. buf.WriteString(fmt.Sprintf("%s-", v))
  440. }
  441. }
  442. return hashcode.String(buf.String())
  443. }
  444. func verifyNetworkACLParams(d *schema.ResourceData) error {
  445. managed := d.Get("managed").(bool)
  446. _, rules := d.GetOk("rule")
  447. if !rules && !managed {
  448. return fmt.Errorf(
  449. "You must supply at least one 'rule' when not using the 'managed' firewall feature")
  450. }
  451. return nil
  452. }
  453. func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
  454. action := rule["action"].(string)
  455. if action != "allow" && action != "deny" {
  456. return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values")
  457. }
  458. protocol := rule["protocol"].(string)
  459. switch protocol {
  460. case "icmp":
  461. if _, ok := rule["icmp_type"]; !ok {
  462. return fmt.Errorf(
  463. "Parameter icmp_type is a required parameter when using protocol 'icmp'")
  464. }
  465. if _, ok := rule["icmp_code"]; !ok {
  466. return fmt.Errorf(
  467. "Parameter icmp_code is a required parameter when using protocol 'icmp'")
  468. }
  469. case "all":
  470. // No additional test are needed, so just leave this empty...
  471. case "tcp", "udp":
  472. if _, ok := rule["ports"]; !ok {
  473. return fmt.Errorf(
  474. "Parameter ports is a required parameter when *not* using protocol 'icmp'")
  475. }
  476. default:
  477. _, err := strconv.ParseInt(protocol, 0, 0)
  478. if err != nil {
  479. return fmt.Errorf(
  480. "%s is not a valid protocol. Valid options are 'tcp', 'udp', "+
  481. "'icmp', 'all' or a valid protocol number", protocol)
  482. }
  483. }
  484. traffic := rule["traffic_type"].(string)
  485. if traffic != "ingress" && traffic != "egress" {
  486. return fmt.Errorf(
  487. "Parameter traffic_type only accepts 'ingress' or 'egress' as values")
  488. }
  489. return nil
  490. }
  491. func retryableACLCreationFunc(
  492. cs *cloudstack.CloudStackClient,
  493. p *cloudstack.CreateNetworkACLParams) func() (interface{}, error) {
  494. return func() (interface{}, error) {
  495. r, err := cs.NetworkACL.CreateNetworkACL(p)
  496. if err != nil {
  497. return nil, err
  498. }
  499. return r, nil
  500. }
  501. }