PageRenderTime 4775ms CodeModel.GetById 119ms RepoModel.GetById 1ms app.codeStats 0ms

/builtin/providers/cloudstack/resource_cloudstack_egress_firewall.go

https://gitlab.com/biopandemic/terraform
Go | 508 lines | 365 code | 94 blank | 49 comment | 93 complexity | 45a2b641ce84fe2448f4339364f3178a 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 resourceCloudStackEgressFirewall() *schema.Resource {
  14. return &schema.Resource{
  15. Create: resourceCloudStackEgressFirewallCreate,
  16. Read: resourceCloudStackEgressFirewallRead,
  17. Update: resourceCloudStackEgressFirewallUpdate,
  18. Delete: resourceCloudStackEgressFirewallDelete,
  19. Schema: map[string]*schema.Schema{
  20. "network": &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. "source_cidr": &schema.Schema{
  36. Type: schema.TypeString,
  37. Required: true,
  38. },
  39. "protocol": &schema.Schema{
  40. Type: schema.TypeString,
  41. Required: true,
  42. },
  43. "icmp_type": &schema.Schema{
  44. Type: schema.TypeInt,
  45. Optional: true,
  46. Computed: true,
  47. },
  48. "icmp_code": &schema.Schema{
  49. Type: schema.TypeInt,
  50. Optional: true,
  51. Computed: true,
  52. },
  53. "ports": &schema.Schema{
  54. Type: schema.TypeSet,
  55. Optional: true,
  56. Elem: &schema.Schema{Type: schema.TypeString},
  57. Set: func(v interface{}) int {
  58. return hashcode.String(v.(string))
  59. },
  60. },
  61. "uuids": &schema.Schema{
  62. Type: schema.TypeMap,
  63. Computed: true,
  64. },
  65. },
  66. },
  67. Set: resourceCloudStackEgressFirewallRuleHash,
  68. },
  69. },
  70. }
  71. }
  72. func resourceCloudStackEgressFirewallCreate(d *schema.ResourceData, meta interface{}) error {
  73. cs := meta.(*cloudstack.CloudStackClient)
  74. // Make sure all required parameters are there
  75. if err := verifyEgressFirewallParams(d); err != nil {
  76. return err
  77. }
  78. // Retrieve the network ID
  79. networkid, e := retrieveID(cs, "network", d.Get("network").(string))
  80. if e != nil {
  81. return e.Error()
  82. }
  83. // We need to set this upfront in order to be able to save a partial state
  84. d.SetId(networkid)
  85. // Create all rules that are configured
  86. if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
  87. // Create an empty schema.Set to hold all rules
  88. rules := &schema.Set{
  89. F: resourceCloudStackEgressFirewallRuleHash,
  90. }
  91. for _, rule := range rs.List() {
  92. // Create a single rule
  93. err := resourceCloudStackEgressFirewallCreateRule(d, meta, rule.(map[string]interface{}))
  94. // We need to update this first to preserve the correct state
  95. rules.Add(rule)
  96. d.Set("rule", rules)
  97. if err != nil {
  98. return err
  99. }
  100. }
  101. }
  102. return resourceCloudStackEgressFirewallRead(d, meta)
  103. }
  104. func resourceCloudStackEgressFirewallCreateRule(
  105. d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
  106. cs := meta.(*cloudstack.CloudStackClient)
  107. uuids := rule["uuids"].(map[string]interface{})
  108. // Make sure all required rule parameters are there
  109. if err := verifyEgressFirewallRuleParams(d, rule); err != nil {
  110. return err
  111. }
  112. // Create a new parameter struct
  113. p := cs.Firewall.NewCreateEgressFirewallRuleParams(d.Id(), rule["protocol"].(string))
  114. // Set the CIDR list
  115. p.SetCidrlist([]string{rule["source_cidr"].(string)})
  116. // If the protocol is ICMP set the needed ICMP parameters
  117. if rule["protocol"].(string) == "icmp" {
  118. p.SetIcmptype(rule["icmp_type"].(int))
  119. p.SetIcmpcode(rule["icmp_code"].(int))
  120. r, err := cs.Firewall.CreateEgressFirewallRule(p)
  121. if err != nil {
  122. return err
  123. }
  124. uuids["icmp"] = r.Id
  125. rule["uuids"] = uuids
  126. }
  127. // If protocol is not ICMP, loop through all ports
  128. if rule["protocol"].(string) != "icmp" {
  129. if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
  130. // Create an empty schema.Set to hold all processed ports
  131. ports := &schema.Set{
  132. F: func(v interface{}) int {
  133. return hashcode.String(v.(string))
  134. },
  135. }
  136. for _, port := range ps.List() {
  137. re := regexp.MustCompile(`^(\d+)(?:-(\d+))?$`)
  138. m := re.FindStringSubmatch(port.(string))
  139. startPort, err := strconv.Atoi(m[1])
  140. if err != nil {
  141. return err
  142. }
  143. endPort := startPort
  144. if m[2] != "" {
  145. endPort, err = strconv.Atoi(m[2])
  146. if err != nil {
  147. return err
  148. }
  149. }
  150. p.SetStartport(startPort)
  151. p.SetEndport(endPort)
  152. r, err := cs.Firewall.CreateEgressFirewallRule(p)
  153. if err != nil {
  154. return err
  155. }
  156. ports.Add(port)
  157. rule["ports"] = ports
  158. uuids[port.(string)] = r.Id
  159. rule["uuids"] = uuids
  160. }
  161. }
  162. }
  163. return nil
  164. }
  165. func resourceCloudStackEgressFirewallRead(d *schema.ResourceData, meta interface{}) error {
  166. cs := meta.(*cloudstack.CloudStackClient)
  167. // Create an empty schema.Set to hold all rules
  168. rules := &schema.Set{
  169. F: resourceCloudStackEgressFirewallRuleHash,
  170. }
  171. // Read all rules that are configured
  172. if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
  173. for _, rule := range rs.List() {
  174. rule := rule.(map[string]interface{})
  175. uuids := rule["uuids"].(map[string]interface{})
  176. if rule["protocol"].(string) == "icmp" {
  177. id, ok := uuids["icmp"]
  178. if !ok {
  179. continue
  180. }
  181. // Get the rule
  182. r, count, err := cs.Firewall.GetEgressFirewallRuleByID(id.(string))
  183. // If the count == 0, there is no object found for this ID
  184. if err != nil {
  185. if count == 0 {
  186. delete(uuids, "icmp")
  187. continue
  188. }
  189. return err
  190. }
  191. // Update the values
  192. rule["source_cidr"] = r.Cidrlist
  193. rule["protocol"] = r.Protocol
  194. rule["icmp_type"] = r.Icmptype
  195. rule["icmp_code"] = r.Icmpcode
  196. rules.Add(rule)
  197. }
  198. // If protocol is not ICMP, loop through all ports
  199. if rule["protocol"].(string) != "icmp" {
  200. if ps := rule["ports"].(*schema.Set); ps.Len() > 0 {
  201. // Create an empty schema.Set to hold all ports
  202. ports := &schema.Set{
  203. F: func(v interface{}) int {
  204. return hashcode.String(v.(string))
  205. },
  206. }
  207. // Loop through all ports and retrieve their info
  208. for _, port := range ps.List() {
  209. id, ok := uuids[port.(string)]
  210. if !ok {
  211. continue
  212. }
  213. // Get the rule
  214. r, count, err := cs.Firewall.GetEgressFirewallRuleByID(id.(string))
  215. if err != nil {
  216. if count == 0 {
  217. delete(uuids, port.(string))
  218. continue
  219. }
  220. return err
  221. }
  222. // Update the values
  223. rule["source_cidr"] = r.Cidrlist
  224. rule["protocol"] = r.Protocol
  225. ports.Add(port)
  226. }
  227. // If there is at least one port found, add this rule to the rules set
  228. if ports.Len() > 0 {
  229. rule["ports"] = ports
  230. rules.Add(rule)
  231. }
  232. }
  233. }
  234. }
  235. }
  236. // If this is a managed firewall, add all unknown rules into a single dummy rule
  237. managed := d.Get("managed").(bool)
  238. if managed {
  239. // Get all the rules from the running environment
  240. p := cs.Firewall.NewListEgressFirewallRulesParams()
  241. p.SetNetworkid(d.Id())
  242. p.SetListall(true)
  243. r, err := cs.Firewall.ListEgressFirewallRules(p)
  244. if err != nil {
  245. return err
  246. }
  247. // Add all UUIDs to the uuids map
  248. uuids := make(map[string]interface{}, len(r.EgressFirewallRules))
  249. for _, r := range r.EgressFirewallRules {
  250. uuids[r.Id] = r.Id
  251. }
  252. // Delete all expected UUIDs from the uuids map
  253. for _, rule := range rules.List() {
  254. rule := rule.(map[string]interface{})
  255. for _, id := range rule["uuids"].(map[string]interface{}) {
  256. delete(uuids, id.(string))
  257. }
  258. }
  259. if len(uuids) > 0 {
  260. // Make a dummy rule to hold all unknown UUIDs
  261. rule := map[string]interface{}{
  262. "source_cidr": "N/A",
  263. "protocol": "N/A",
  264. "uuids": uuids,
  265. }
  266. // Add the dummy rule to the rules set
  267. rules.Add(rule)
  268. }
  269. }
  270. if rules.Len() > 0 {
  271. d.Set("rule", rules)
  272. } else if !managed {
  273. d.SetId("")
  274. }
  275. return nil
  276. }
  277. func resourceCloudStackEgressFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
  278. // Make sure all required parameters are there
  279. if err := verifyEgressFirewallParams(d); err != nil {
  280. return err
  281. }
  282. // Check if the rule set as a whole has changed
  283. if d.HasChange("rule") {
  284. o, n := d.GetChange("rule")
  285. ors := o.(*schema.Set).Difference(n.(*schema.Set))
  286. nrs := n.(*schema.Set).Difference(o.(*schema.Set))
  287. // Now first loop through all the old rules and delete any obsolete ones
  288. for _, rule := range ors.List() {
  289. // Delete the rule as it no longer exists in the config
  290. err := resourceCloudStackEgressFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
  291. if err != nil {
  292. return err
  293. }
  294. }
  295. // Make sure we save the state of the currently configured rules
  296. rules := o.(*schema.Set).Intersection(n.(*schema.Set))
  297. d.Set("rule", rules)
  298. // Then loop through all the currently configured rules and create the new ones
  299. for _, rule := range nrs.List() {
  300. // When successfully deleted, re-create it again if it still exists
  301. err := resourceCloudStackEgressFirewallCreateRule(
  302. d, meta, rule.(map[string]interface{}))
  303. // We need to update this first to preserve the correct state
  304. rules.Add(rule)
  305. d.Set("rule", rules)
  306. if err != nil {
  307. return err
  308. }
  309. }
  310. }
  311. return resourceCloudStackEgressFirewallRead(d, meta)
  312. }
  313. func resourceCloudStackEgressFirewallDelete(d *schema.ResourceData, meta interface{}) error {
  314. // Delete all rules
  315. if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
  316. for _, rule := range rs.List() {
  317. // Delete a single rule
  318. err := resourceCloudStackEgressFirewallDeleteRule(d, meta, rule.(map[string]interface{}))
  319. // We need to update this first to preserve the correct state
  320. d.Set("rule", rs)
  321. if err != nil {
  322. return err
  323. }
  324. }
  325. }
  326. return nil
  327. }
  328. func resourceCloudStackEgressFirewallDeleteRule(
  329. d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
  330. cs := meta.(*cloudstack.CloudStackClient)
  331. uuids := rule["uuids"].(map[string]interface{})
  332. for k, id := range uuids {
  333. // We don't care about the count here, so just continue
  334. if k == "#" {
  335. continue
  336. }
  337. // Create the parameter struct
  338. p := cs.Firewall.NewDeleteEgressFirewallRuleParams(id.(string))
  339. // Delete the rule
  340. if _, err := cs.Firewall.DeleteEgressFirewallRule(p); err != nil {
  341. // This is a very poor way to be told the ID does no longer exist :(
  342. if strings.Contains(err.Error(), fmt.Sprintf(
  343. "Invalid parameter id value=%s due to incorrect long value format, "+
  344. "or entity does not exist", id.(string))) {
  345. delete(uuids, k)
  346. continue
  347. }
  348. return err
  349. }
  350. // Delete the UUID of this rule
  351. delete(uuids, k)
  352. }
  353. // Update the UUIDs
  354. rule["uuids"] = uuids
  355. return nil
  356. }
  357. func resourceCloudStackEgressFirewallRuleHash(v interface{}) int {
  358. var buf bytes.Buffer
  359. m := v.(map[string]interface{})
  360. buf.WriteString(fmt.Sprintf(
  361. "%s-%s-", m["source_cidr"].(string), m["protocol"].(string)))
  362. if v, ok := m["icmp_type"]; ok {
  363. buf.WriteString(fmt.Sprintf("%d-", v.(int)))
  364. }
  365. if v, ok := m["icmp_code"]; ok {
  366. buf.WriteString(fmt.Sprintf("%d-", v.(int)))
  367. }
  368. // We need to make sure to sort the strings below so that we always
  369. // generate the same hash code no matter what is in the set.
  370. if v, ok := m["ports"]; ok {
  371. vs := v.(*schema.Set).List()
  372. s := make([]string, len(vs))
  373. for i, raw := range vs {
  374. s[i] = raw.(string)
  375. }
  376. sort.Strings(s)
  377. for _, v := range s {
  378. buf.WriteString(fmt.Sprintf("%s-", v))
  379. }
  380. }
  381. return hashcode.String(buf.String())
  382. }
  383. func verifyEgressFirewallParams(d *schema.ResourceData) error {
  384. managed := d.Get("managed").(bool)
  385. _, rules := d.GetOk("rule")
  386. if !rules && !managed {
  387. return fmt.Errorf(
  388. "You must supply at least one 'rule' when not using the 'managed' firewall feature")
  389. }
  390. return nil
  391. }
  392. func verifyEgressFirewallRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
  393. protocol := rule["protocol"].(string)
  394. if protocol != "tcp" && protocol != "udp" && protocol != "icmp" {
  395. return fmt.Errorf(
  396. "%s is not a valid protocol. Valid options are 'tcp', 'udp' and 'icmp'", protocol)
  397. }
  398. if protocol == "icmp" {
  399. if _, ok := rule["icmp_type"]; !ok {
  400. return fmt.Errorf(
  401. "Parameter icmp_type is a required parameter when using protocol 'icmp'")
  402. }
  403. if _, ok := rule["icmp_code"]; !ok {
  404. return fmt.Errorf(
  405. "Parameter icmp_code is a required parameter when using protocol 'icmp'")
  406. }
  407. } else {
  408. if _, ok := rule["ports"]; !ok {
  409. return fmt.Errorf(
  410. "Parameter port is a required parameter when using protocol 'tcp' or 'udp'")
  411. }
  412. }
  413. return nil
  414. }