diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be9dac55..29237dc36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ FEATURES: * **New Resource**: `opennebula_cluster` (#227) +* resources/opennebula_cluster: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_group: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_image: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_security_group: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_template: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_vm_group: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_user: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_virtual_machine: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_virtual_network: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_virtual_router: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_virtual_router_instance: add `template_section` to manage vectors with an unique key (#359) +* resources/opennebula_virtual_router_instance_template: add `template_section` to manage vectors with an unique key (#359) DEPRECATION: diff --git a/opennebula/resource_opennebula_cluster.go b/opennebula/resource_opennebula_cluster.go index 91b478930..6985f1129 100644 --- a/opennebula/resource_opennebula_cluster.go +++ b/opennebula/resource_opennebula_cluster.go @@ -57,9 +57,10 @@ func resourceOpennebulaCluster() *schema.Resource { Type: schema.TypeInt, }, }, - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -151,6 +152,11 @@ func resourceOpennebulaClusterCreate(ctx context.Context, d *schema.ResourceData tpl := dyn.NewTemplate() + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, tpl) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) @@ -279,6 +285,11 @@ func resourceOpennebulaClusterRead(ctx context.Context, d *schema.ResourceData, func flattenClusterTemplate(d *schema.ResourceData, meta interface{}, clusterTpl *cluster.Template) error { config := meta.(*Configuration) + err := flattenTemplateSection(d, meta, &clusterTpl.Template) + if err != nil { + return err + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -492,6 +503,13 @@ func resourceOpennebulaClusterUpdate(ctx context.Context, d *schema.ResourceData } } + if d.HasChange("template_section") { + + updateTemplateSection(d, &newTpl.Template) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") diff --git a/opennebula/resource_opennebula_group.go b/opennebula/resource_opennebula_group.go index d0d73536e..e14290938 100644 --- a/opennebula/resource_opennebula_group.go +++ b/opennebula/resource_opennebula_group.go @@ -136,9 +136,10 @@ func resourceOpennebulaGroup() *schema.Resource { }, }, }, - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -258,6 +259,11 @@ func resourceOpennebulaGroupCreate(ctx context.Context, d *schema.ResourceData, tpl.Elements = append(tpl.Elements, opennebulaVec) } + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, tpl) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) @@ -504,6 +510,11 @@ func flattenGroupTemplate(d *schema.ResourceData, meta interface{}, groupTpl *dy } + err := flattenTemplateSection(d, meta, groupTpl) + if err != nil { + return err + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -614,6 +625,11 @@ func resourceOpennebulaGroupUpdate(ctx context.Context, d *schema.ResourceData, opennebulaVec := makeOpenNebulaVec(opennebula[0].(map[string]interface{})) newTpl.Elements = append(newTpl.Elements, opennebulaVec) } + } + + if d.HasChange("template_section") { + + updateTemplateSection(d, &newTpl) update = true } diff --git a/opennebula/resource_opennebula_group_test.go b/opennebula/resource_opennebula_group_test.go index 5d076e383..265af5ddf 100644 --- a/opennebula/resource_opennebula_group_test.go +++ b/opennebula/resource_opennebula_group_test.go @@ -45,6 +45,12 @@ func TestAccGroup(t *testing.T) { resource.TestCheckResourceAttr("opennebula_group.group", "tags.%", "2"), resource.TestCheckResourceAttr("opennebula_group.group", "tags.testkey1", "testvalue1"), resource.TestCheckResourceAttr("opennebula_group.group", "tags.testkey2", "testvalue2"), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_group.group", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey1": "testvalue1", + "tags.testkey2": "testvalue2", + }), ), }, { @@ -76,6 +82,12 @@ func TestAccGroup(t *testing.T) { resource.TestCheckResourceAttr("opennebula_group.group", "tags.%", "2"), resource.TestCheckResourceAttr("opennebula_group.group", "tags.testkey2", "testvalue2"), resource.TestCheckResourceAttr("opennebula_group.group", "tags.testkey3", "testvalue3"), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_group.group", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey2": "testvalue2", + "tags.testkey3": "testvalue3", + }), ), }, { @@ -92,6 +104,12 @@ func TestAccGroup(t *testing.T) { resource.TestCheckResourceAttr("opennebula_group.group", "tags.%", "2"), resource.TestCheckResourceAttr("opennebula_group.group", "tags.testkey2", "testvalue2"), resource.TestCheckResourceAttr("opennebula_group.group", "tags.testkey3", "testvalue3"), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_group.group", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey2": "testvalue2", + "tags.testkey3": "testvalue3", + }), ), }, { @@ -132,6 +150,7 @@ func TestAccGroup(t *testing.T) { "views": "cloud", }), resource.TestCheckResourceAttr("opennebula_group.group2", "tags.%", "0"), + resource.TestCheckResourceAttr("opennebula_group.group2", "template_section.#", "0"), ), }, }, @@ -204,6 +223,14 @@ resource "opennebula_group" "group" { testkey1 = "testvalue1" testkey2 = "testvalue2" } + + template_section { + name = "test_vec_key" + tags = { + testkey1 = "testvalue1" + testkey2 = "testvalue2" + } + } } ` @@ -235,6 +262,14 @@ resource "opennebula_group" "group" { testkey2 = "testvalue2" testkey3 = "testvalue3" } + + template_section { + name = "test_vec_key" + tags = { + testkey2 = "testvalue2" + testkey3 = "testvalue3" + } + } } ` @@ -262,6 +297,14 @@ resource "opennebula_group" "group" { testkey2 = "testvalue2" testkey3 = "testvalue3" } + + template_section { + name = "test_vec_key" + tags = { + testkey2 = "testvalue2" + testkey3 = "testvalue3" + } + } } ` diff --git a/opennebula/resource_opennebula_image.go b/opennebula/resource_opennebula_image.go index 22de0623d..8838ae47b 100644 --- a/opennebula/resource_opennebula_image.go +++ b/opennebula/resource_opennebula_image.go @@ -185,9 +185,10 @@ func resourceOpennebulaImage() *schema.Resource { Optional: true, Description: "Name of the Group that onws the Image, If empty, it uses caller group", }, - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -520,6 +521,16 @@ func resourceOpennebulaImageRead(ctx context.Context, d *schema.ResourceData, me d.Set("type", image.Type) } + err = flattenTemplateSection(d, meta, &image.Template.Template) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to flatten template section", + Detail: fmt.Sprintf("image (ID: %s): %s", d.Id(), err), + }) + return diags + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) for i, _ := range image.Template.Elements { @@ -751,6 +762,13 @@ func resourceOpennebulaImageUpdate(ctx context.Context, d *schema.ResourceData, update = true } + if d.HasChange("template_section") { + + updateTemplateSection(d, &tpl.Template) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") @@ -944,6 +962,11 @@ func generateImageTemplate(d *schema.ResourceData, meta interface{}) (string, er tpl.Add(imk.Target, val.(string)) } + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, &tpl.Template) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) diff --git a/opennebula/resource_opennebula_security_group.go b/opennebula/resource_opennebula_security_group.go index e2ea98ec2..00f1ec16c 100644 --- a/opennebula/resource_opennebula_security_group.go +++ b/opennebula/resource_opennebula_security_group.go @@ -167,9 +167,10 @@ func resourceOpennebulaSecurityGroup() *schema.Resource { ConflictsWith: []string{"gid"}, Description: "Name of the Group that onws the Security Group, If empty, it uses caller group", }, - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -292,6 +293,11 @@ func flattenSecurityGroupTags(d *schema.ResourceData, meta interface{}, sgTpl *s config := meta.(*Configuration) + err := flattenTemplateSection(d, meta, &sgTpl.Template) + if err != nil { + return err + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -467,6 +473,13 @@ func resourceOpennebulaSecurityGroupUpdate(ctx context.Context, d *schema.Resour rulesUpdate = true } + if d.HasChange("template_section") { + + updateTemplateSection(d, &tpl.Template) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") @@ -684,6 +697,11 @@ func generateSecurityGroupTemplate(d *schema.ResourceData, meta interface{}) str tpl.Add(sgk.Description, description) } + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, &tpl.Template) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) diff --git a/opennebula/resource_opennebula_template.go b/opennebula/resource_opennebula_template.go index 2471deeea..87078e063 100644 --- a/opennebula/resource_opennebula_template.go +++ b/opennebula/resource_opennebula_template.go @@ -787,6 +787,13 @@ func resourceOpennebulaTemplateUpdateCustom(ctx context.Context, d *schema.Resou update = true } + if d.HasChange("template_section") { + + updateTemplateSection(d, &newTpl.Template) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") diff --git a/opennebula/resource_opennebula_template_test.go b/opennebula/resource_opennebula_template_test.go index 5f0284c95..eae8b3479 100644 --- a/opennebula/resource_opennebula_template_test.go +++ b/opennebula/resource_opennebula_template_test.go @@ -53,6 +53,12 @@ func TestAccTemplate(t *testing.T) { GroupU: 1, GroupM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_template.template", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey1": "testvalue1", + "tags.testkey2": "testvalue2", + }), ), }, { @@ -86,6 +92,12 @@ func TestAccTemplate(t *testing.T) { GroupU: 1, GroupM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_template.template", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey1": "testvalue1", + "tags.testkey2": "testvalue2", + }), ), }, { @@ -122,6 +134,12 @@ func TestAccTemplate(t *testing.T) { GroupU: 1, OtherM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_template.template", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey2": "testvalue2", + "tags.testkey3": "testvalue3", + }), ), }, { @@ -152,6 +170,11 @@ func TestAccTemplate(t *testing.T) { GroupU: 1, OtherM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_template.template", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "1", + "tags.testkey3": "testvalue3", + }), ), }, }, @@ -258,6 +281,14 @@ resource "opennebula_template" "template" { BLOG_TITLE="M|text|Blog Title" } + template_section { + name = "test_vec_key" + tags = { + testkey1 = "testvalue1" + testkey2 = "testvalue2" + } + } + } ` @@ -298,6 +329,14 @@ resource "opennebula_template" "template" { sched_requirements = "FREE_CPU > 50" + template_section { + name = "test_vec_key" + tags = { + testkey1 = "testvalue1" + testkey2 = "testvalue2" + } + } + } ` @@ -341,6 +380,14 @@ resource "opennebula_template" "template" { sched_requirements = "CLUSTER_ID!=\"123\"" + template_section { + name = "test_vec_key" + tags = { + testkey2 = "testvalue2" + testkey3 = "testvalue3" + } + } + } ` @@ -374,5 +421,12 @@ resource "opennebula_template" "template" { env = "dev" customer = "test" } + + template_section { + name = "test_vec_key" + tags = { + testkey3 = "testvalue3" + } + } } ` diff --git a/opennebula/resource_opennebula_template_vm_group.go b/opennebula/resource_opennebula_template_vm_group.go index a82b08818..d770942ff 100644 --- a/opennebula/resource_opennebula_template_vm_group.go +++ b/opennebula/resource_opennebula_template_vm_group.go @@ -135,9 +135,10 @@ func resourceOpennebulaVMGroup() *schema.Resource { Optional: true, Description: "Name of the Group that onws the Template VM Group, If empty, it uses caller group", }, - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -322,6 +323,12 @@ func resourceOpennebulaVMGroupRead(ctx context.Context, d *schema.ResourceData, func flattenVMGroupTags(d *schema.ResourceData, meta interface{}, tpl *dyn.Template) error { config := meta.(*Configuration) + + err := flattenTemplateSection(d, meta, tpl) + if err != nil { + return err + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -494,6 +501,13 @@ func resourceOpennebulaVMGroupUpdate(ctx context.Context, d *schema.ResourceData update = true } + if d.HasChange("template_section") { + + updateTemplateSection(d, &newTpl) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") @@ -605,6 +619,11 @@ func generateVMGroup(d *schema.ResourceData, meta interface{}) (*dyn.Template, e generateVMGroupRoles(d, tpl) + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, tpl) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) diff --git a/opennebula/resource_opennebula_user.go b/opennebula/resource_opennebula_user.go index 65175ea7e..a4d0fe3dc 100644 --- a/opennebula/resource_opennebula_user.go +++ b/opennebula/resource_opennebula_user.go @@ -70,10 +70,11 @@ func resourceOpennebulaUser() *schema.Resource { Type: schema.TypeInt, }, }, - "quotas": quotasSchema(), - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "quotas": quotasSchema(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -172,6 +173,11 @@ func resourceOpennebulaUserCreate(ctx context.Context, d *schema.ResourceData, m tpl := dyn.NewTemplate() + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, tpl) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) @@ -315,6 +321,12 @@ func flattenUserGroups(d *schema.ResourceData, user *user.User) error { func flattenUserTemplate(d *schema.ResourceData, meta interface{}, userTpl *dyn.Template) error { config := meta.(*Configuration) + + err := flattenTemplateSection(d, meta, userTpl) + if err != nil { + return err + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -493,6 +505,13 @@ func resourceOpennebulaUserUpdate(ctx context.Context, d *schema.ResourceData, m update := false newTpl := userInfos.Template + if d.HasChange("template_section") { + + updateTemplateSection(d, &newTpl) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") diff --git a/opennebula/resource_opennebula_virtual_machine.go b/opennebula/resource_opennebula_virtual_machine.go index 5e2a40123..26cd01a4b 100644 --- a/opennebula/resource_opennebula_virtual_machine.go +++ b/opennebula/resource_opennebula_virtual_machine.go @@ -1251,6 +1251,13 @@ func resourceOpennebulaVirtualMachineUpdateCustom(ctx context.Context, d *schema update = true } + if d.HasChange("template_section") { + + updateTemplateSection(d, &tpl.Template) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") diff --git a/opennebula/resource_opennebula_virtual_machine_test.go b/opennebula/resource_opennebula_virtual_machine_test.go index cfd736c0d..46e078b69 100644 --- a/opennebula/resource_opennebula_virtual_machine_test.go +++ b/opennebula/resource_opennebula_virtual_machine_test.go @@ -56,6 +56,12 @@ func TestAccVirtualMachine(t *testing.T) { GroupU: 1, OtherM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_virtual_machine.test", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey1": "testvalue1", + "tags.testkey2": "testvalue2", + }), ), }, { @@ -96,6 +102,12 @@ func TestAccVirtualMachine(t *testing.T) { GroupU: 1, GroupM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_virtual_machine.test", "template_section.*", map[string]string{ + "name": "test_vec_key", + "tags.%": "2", + "tags.testkey1": "testvalue_updated", + "tags.testkey3": "testvalue3", + }), ), }, { @@ -172,6 +184,7 @@ func TestAccVirtualMachine(t *testing.T) { GroupU: 1, GroupM: 1, }), + resource.TestCheckResourceAttr("opennebula_virtual_machine.test", "template_section.#", "0"), ), }, { @@ -212,6 +225,11 @@ func TestAccVirtualMachine(t *testing.T) { GroupU: 1, GroupM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_virtual_machine.test", "template_section.*", map[string]string{ + "name": "test_vec_key2", + "tags.%": "1", + "tags.testkey1": "testvalue2", + }), ), }, { @@ -251,6 +269,13 @@ func TestAccVirtualMachine(t *testing.T) { GroupU: 1, GroupM: 1, }), + resource.TestCheckTypeSetElemNestedAttrs("opennebula_virtual_machine.test", "template_section.*", map[string]string{ + "name": "test_vec_key3", + "tags.%": "3", + "tags.testkey1": "testvalue1", + "tags.testkey2": "testvalue2", + "tags.testkey3": "testvalue3", + }), ), }, }, @@ -792,6 +817,14 @@ resource "opennebula_virtual_machine" "test" { sched_requirements = "FREE_CPU > 50" + template_section { + name = "test_vec_key" + tags = { + testkey1 = "testvalue1" + testkey2 = "testvalue2" + } + } + timeout = 5 } ` @@ -869,6 +902,14 @@ resource "opennebula_virtual_machine" "test" { sched_requirements = "FREE_CPU > 50" timeout = 4 + + template_section { + name = "test_vec_key" + tags = { + testkey1 = "testvalue_updated" + testkey3 = "testvalue3" + } + } } ` @@ -907,6 +948,14 @@ resource "opennebula_virtual_machine" "test" { sched_requirements = "FREE_CPU > 50" timeout = 4 + + template_section { + name = "test_vec_key" + tags = { + testkey1 = "testvalue_updated" + testkey3 = "testvalue3" + } + } } ` @@ -980,6 +1029,13 @@ resource "opennebula_virtual_machine" "test" { sched_requirements = "CLUSTER_ID!=\"123\"" timeout = 4 + + template_section { + name = "test_vec_key2" + tags = { + testkey1 = "testvalue2" + } + } } ` @@ -1017,6 +1073,15 @@ resource "opennebula_virtual_machine" "test" { sched_requirements = "CLUSTER_ID!=\"123\"" timeout = 4 + + template_section { + name = "test_vec_key3" + tags = { + testkey1 = "testvalue1" + testkey2 = "testvalue2" + testkey3 = "testvalue3" + } + } } ` diff --git a/opennebula/resource_opennebula_virtual_network.go b/opennebula/resource_opennebula_virtual_network.go index abd63df1b..c23636121 100644 --- a/opennebula/resource_opennebula_virtual_network.go +++ b/opennebula/resource_opennebula_virtual_network.go @@ -266,10 +266,11 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource { Optional: true, Description: "Name of the Group that onws the Virtual Network, If empty, it uses caller group", }, - "lock": lockSchema(), - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "lock": lockSchema(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -765,6 +766,11 @@ func generateVnTemplate(d *schema.ResourceData, meta interface{}) (string, error tpl.Add("DESCRIPTION", desc.(string)) } + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, &tpl.Template) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) @@ -1020,6 +1026,12 @@ func flattenVnetARs(d *schema.ResourceData, vn *vn.VirtualNetwork) error { func flattenVnetTemplate(d *schema.ResourceData, meta interface{}, vnTpl *vn.Template) error { config := meta.(*Configuration) + + err := flattenTemplateSection(d, meta, &vnTpl.Template) + if err != nil { + return err + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -1228,6 +1240,13 @@ func resourceOpennebulaVirtualNetworkUpdate(ctx context.Context, d *schema.Resou changes = true } + if d.HasChange("template_section") { + + updateTemplateSection(d, &tpl.Template) + + changes = true + } + if d.HasChange("tags") { tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { diff --git a/opennebula/resource_opennebula_virtual_router.go b/opennebula/resource_opennebula_virtual_router.go index c4102b4c9..5a224f646 100644 --- a/opennebula/resource_opennebula_virtual_router.go +++ b/opennebula/resource_opennebula_virtual_router.go @@ -95,10 +95,11 @@ func resourceOpennebulaVirtualRouter() *schema.Resource { Optional: true, Description: "A description of the entity", }, - "lock": lockSchema(), - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), + "lock": lockSchema(), + "tags": tagsSchema(), + "default_tags": defaultTagsSchemaComputed(), + "tags_all": tagsSchemaComputed(), + "template_section": templateSectionSchema(), }, } } @@ -275,6 +276,17 @@ func resourceOpennebulaVirtualRouterRead(ctx context.Context, d *schema.Resource } config := meta.(*Configuration) + + err = flattenTemplateSection(d, meta, &vr.Template.Template) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to flatten template section", + Detail: fmt.Sprintf("virtual router (ID: %s): %s", d.Id(), err), + }) + return diags + } + tags := make(map[string]interface{}) tagsAll := make(map[string]interface{}) @@ -465,6 +477,13 @@ func resourceOpennebulaVirtualRouterUpdate(ctx context.Context, d *schema.Resour newTpl.Add("DESCRIPTION", d.Get("description").(string)) } + if d.HasChange("template_section") { + + updateTemplateSection(d, &newTpl.Template) + + update = true + } + if d.HasChange("tags") { oldTagsIf, newTagsIf := d.GetChange("tags") @@ -572,6 +591,11 @@ func generateVirtualRouter(d *schema.ResourceData, meta interface{}) string { tpl.Add("DESCRIPTION", descr.(string)) } + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, &tpl.Template) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) diff --git a/opennebula/shared_schemas.go b/opennebula/shared_schemas.go index 83b838e34..08756bdad 100644 --- a/opennebula/shared_schemas.go +++ b/opennebula/shared_schemas.go @@ -139,6 +139,7 @@ func commonInstanceSchema() map[string]*schema.Schema { "sched_requirements": schedReqSchema(), "sched_ds_requirements": schedDSReqSchema(), "description": descriptionSchema(), + "template_section": templateSectionSchema(), } } @@ -620,6 +621,11 @@ func generateVMTemplate(d *schema.ResourceData, tpl *vm.Template) error { tpl.VCPU(vmvcpu.(int)) } + vectorsInterface := d.Get("template_section").(*schema.Set).List() + if len(vectorsInterface) > 0 { + addTemplateVectors(vectorsInterface, &tpl.Template) + } + tagsInterface := d.Get("tags").(map[string]interface{}) for k, v := range tagsInterface { tpl.AddPair(strings.ToUpper(k), v) @@ -864,6 +870,11 @@ func flattenVMUserTemplate(d *schema.ResourceData, meta interface{}, vmTemplate // add default_tags to tags_all config := meta.(*Configuration) + err := flattenTemplateSection(d, meta, vmTemplate) + if err != nil { + return err + } + tagsAll := make(map[string]interface{}) // Get default tags diff --git a/opennebula/template_section.go b/opennebula/template_section.go new file mode 100644 index 000000000..444c110eb --- /dev/null +++ b/opennebula/template_section.go @@ -0,0 +1,139 @@ +package opennebula + +import ( + "strings" + + "github.com/OpenNebula/one/src/oca/go/src/goca/dynamic" + dyn "github.com/OpenNebula/one/src/oca/go/src/goca/dynamic" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func templateSectionSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Description: "Add custom section to the resource", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + } +} + +func addTemplateVectors(vectorsInterface []interface{}, tpl *dyn.Template) { + + for _, vectorIf := range vectorsInterface { + vector := vectorIf.(map[string]interface{}) + vecName := strings.ToUpper(vector["name"].(string)) + vecTags := vector["tags"].(map[string]interface{}) + + vec := tpl.AddVector(strings.ToUpper(vecName)) + for k, v := range vecTags { + vec.AddPair(k, v) + } + } +} + +func flattenTemplateSection(d *schema.ResourceData, meta interface{}, tpl *dynamic.Template) error { + + if vectorsInterface, ok := d.GetOk("template_section"); ok { + + templateSection := make([]interface{}, 0) + + for _, vectorIf := range vectorsInterface.(*schema.Set).List() { + vector := vectorIf.(map[string]interface{}) + vecName := vector["name"].(string) + vecTags := vector["tags"].(map[string]interface{}) + + // Suppose vector key unicity + vectorTpl, err := tpl.GetVector(strings.ToUpper(vecName)) + if err != nil { + continue + } + + tags := make(map[string]interface{}) + for _, pair := range vectorTpl.Pairs { + for k, _ := range vecTags { + if strings.ToUpper(k) != pair.Key() { + continue + } + tags[k] = pair.Value + break + } + } + + templateSection = append(templateSection, map[string]interface{}{ + "name": vecName, + "tags": tags, + }) + + } + + err := d.Set("template_section", templateSection) + if err != nil { + return err + } + } + + return nil +} + +func updateTemplateSection(d *schema.ResourceData, newTpl *dyn.Template) { + + oldVectorsIf, newVectorsIf := d.GetChange("template_section") + oldVectors := oldVectorsIf.(*schema.Set).List() + newVectors := newVectorsIf.(*schema.Set).List() + + // Here we suppose vector key unicity + // delete vectors + for _, oldVectorIf := range oldVectors { + oldVector := oldVectorIf.(map[string]interface{}) + oldVectorName := oldVector["name"].(string) + + if len(newVectors) == 0 { + newTpl.Del(strings.ToUpper(oldVectorName)) + } + + // if a new vector has the same name, keep it + for _, newVectorIf := range newVectors { + newVector := newVectorIf.(map[string]interface{}) + + if oldVectorName == newVector["name"].(string) { + continue + } + newTpl.Del(strings.ToUpper(oldVectorName)) + } + + } + + // add/update vectors + for _, newVectorIf := range newVectors { + newVector := newVectorIf.(map[string]interface{}) + newVectorName := strings.ToUpper(newVector["name"].(string)) + + tags := newVector["tags"].(map[string]interface{}) + + // it seems there is a problem, sometimes an empty map is present in newVectors list + // https://github.com/hashicorp/terraform-plugin-sdk/issues/588 + if len(newVectorName) == 0 && len(tags) == 0 { + continue + } + + newTpl.Del(strings.ToUpper(newVectorName)) + newVec := newTpl.AddVector(newVectorName) + for k, v := range tags { + newVec.AddPair(k, v) + } + } +} diff --git a/website/docs/r/cluster.html.markdown b/website/docs/r/cluster.html.markdown index f081e5bd4..0af4c4c1a 100644 --- a/website/docs/r/cluster.html.markdown +++ b/website/docs/r/cluster.html.markdown @@ -29,6 +29,14 @@ resource "opennebula_cluster" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } + } ``` @@ -41,6 +49,7 @@ The following arguments are supported: * `datastores` - (Optional) List of hosts user IDs part of the cluster. * `virtual_networks` - (Optional) List of hosts user IDs part of the cluster. * `tags` - (Optional) Cluster tags (Key = value) +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ## Attribute Reference diff --git a/website/docs/r/group.html.markdown b/website/docs/r/group.html.markdown index 7ef71533d..9e38991bb 100644 --- a/website/docs/r/group.html.markdown +++ b/website/docs/r/group.html.markdown @@ -52,6 +52,14 @@ resource "opennebula_group" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } + } ``` @@ -65,6 +73,7 @@ The following arguments are supported: * `sunstone` - (Optional) Allow users and group admins to access specific views. See [Sunstone parameters](#sunstone-parameters) below for details * `opennebula` - (Optional) OpenNebula core configuration. See [Opennebula parameters](#opennebula-parameters) below for details * `tags` - (Optional) Group tags (Key = value) +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Quotas parameters @@ -109,7 +118,7 @@ The following arguments are supported: * `running_vms` - (Optional) Number of Virtual Machines allowed in `RUNNING` state. Defaults to `default quota`. * `system_disk_size` - (Optional) Maximum disk global size (in MB) allowed on a `SYSTEM` datastore. Defaults to `default quota`. -#### Sunstone parameters +### Sunstone parameters * `default_view` - (Optional) Default Sunstone view for regular users. * `views` - (Optional) List of available views for regular users. @@ -122,6 +131,13 @@ The following arguments are supported: * `default_image_persistent_new` - (Optional) Control the default value for the `persistent` attribute on image creation ( only new images). * `api_list_order` - (Optional) Sets order of elements by ID in list API calls: asc or desc respectively for ascending or descending order. +### Template section parameters + +`template_section` supports the following arguments: + +* `name` - (Optional) The vector name. +* `tags` - (Optional) Collection of custom tags. + ## Attribute Reference The following attribute is exported: diff --git a/website/docs/r/image.html.markdown b/website/docs/r/image.html.markdown index cd31dfc95..403f599ab 100644 --- a/website/docs/r/image.html.markdown +++ b/website/docs/r/image.html.markdown @@ -46,6 +46,13 @@ resource "opennebula_image" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -66,6 +73,13 @@ resource "opennebula_image" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -122,6 +136,7 @@ The following arguments are supported: * `group` - (Optional) Name of the group which owns the image. Defaults to the caller primary group. * `tags` - (Optional) Image tags (Key = value) * `timeout` - (Deprecated) Timeout (in Minutes) for Image availability. Defaults to 10 minutes. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ## Attribute Reference diff --git a/website/docs/r/security_group.html.markdown b/website/docs/r/security_group.html.markdown index a6966dd98..5cb230036 100644 --- a/website/docs/r/security_group.html.markdown +++ b/website/docs/r/security_group.html.markdown @@ -40,6 +40,13 @@ resource "opennebula_security_group" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -54,6 +61,7 @@ The following arguments are supported: * `rule` - (Required) List of rules. See [Rule parameters](#rule-parameters) below for details * `group` - (Optional) Name of the group which owns the security group. Defaults to the caller primary group. * `tags` - (Optional) Security group tags (Key = Value). +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Rule parameters diff --git a/website/docs/r/template.html.markdown b/website/docs/r/template.html.markdown index 57b9ee132..06f38ab26 100644 --- a/website/docs/r/template.html.markdown +++ b/website/docs/r/template.html.markdown @@ -69,6 +69,13 @@ resource "opennebula_template" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -97,6 +104,7 @@ The following arguments are supported: * `tags` - (Optional) Template tags (Key = Value). * `template` - (Deprecated) Text describing the OpenNebula template object, in Opennebula's XML string format. * `lock` - (Optional) Lock the template with a specific lock level. Supported values: `USE`, `MANAGE`, `ADMIN`, `ALL` or `UNLOCK`. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Graphics parameters diff --git a/website/docs/r/user.html.markdown b/website/docs/r/user.html.markdown index 63b6663c5..d97caf344 100644 --- a/website/docs/r/user.html.markdown +++ b/website/docs/r/user.html.markdown @@ -56,6 +56,13 @@ resource "opennebula_user" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -70,6 +77,7 @@ The following arguments are supported: * `groups` - (Optional) List of secondary groups ID of the user. * `quotas` - (Optional) See [Quotas parameters](#quotas-parameters) below for details * `tags` - (Optional) Group tags (Key = value) +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Quotas parameters diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 04ae2bf47..b9423d4f4 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -69,6 +69,13 @@ resource "opennebula_virtual_machine" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -99,6 +106,7 @@ The following arguments are supported: * `lock` - (Optional) Lock the VM with a specific lock level. Supported values: `USE`, `MANAGE`, `ADMIN`, `ALL` or `UNLOCK`. * `on_disk_change` - (Optional) Select the behavior for changing disk images. Supported values: `RECREATE` or `SWAP` (default). `RECREATE` forces recreation of the vm and `SWAP` adopts the standard behavior of hot-swapping the disks. NOTE: This property does not affect the behavior of adding new disks. * `hard_shutdown` - (Optional) If the VM doesn't have ACPI support, it immediately poweroff/terminate/reboot/undeploy the VM. Defaults to false. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Graphics parameters diff --git a/website/docs/r/virtual_machine_group.html.markdown b/website/docs/r/virtual_machine_group.html.markdown index d522b95e9..b507d50e7 100644 --- a/website/docs/r/virtual_machine_group.html.markdown +++ b/website/docs/r/virtual_machine_group.html.markdown @@ -30,6 +30,13 @@ resource "opennebula_virtual_machine_group" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -42,6 +49,7 @@ The following arguments are supported: * `role` - (Required) List of roles. See [Role parameters](#role-parameters) below for details. * `group` - (Optional) Name of the group which owns the virtual machine group. Defaults to the caller primary group. * `tags` - (Optional) Virtual Machine group tags. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Role parameters diff --git a/website/docs/r/virtual_network.html.markdown b/website/docs/r/virtual_network.html.markdown index 0930e9557..a76919977 100644 --- a/website/docs/r/virtual_network.html.markdown +++ b/website/docs/r/virtual_network.html.markdown @@ -56,6 +56,13 @@ resource "opennebula_virtual_network" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -89,6 +96,7 @@ The following arguments are supported: * `group` - (Optional) Name of the group which owns the virtual network. Defaults to the caller primary group. * `tags` - (Optional) Virtual Network tags (Key = Value). * `lock` - (Optional) Lock the virtual network with a specific lock level. Supported values: `USE`, `MANAGE`, `ADMIN`, `ALL` or `UNLOCK`. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Address Range parameters diff --git a/website/docs/r/virtual_router.html.markdown b/website/docs/r/virtual_router.html.markdown index 1ef6c92f2..70d96a3bd 100644 --- a/website/docs/r/virtual_router.html.markdown +++ b/website/docs/r/virtual_router.html.markdown @@ -26,6 +26,13 @@ resource "opennebula_virtual_router" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -39,6 +46,7 @@ The following arguments are supported: * `group` - (Optional) Name of the group which owns the virtual router. Defaults to the caller primary group. * `description` - (Optional) Description of the virtual router. * `lock` - (Optional) Lock the VM with a specific lock level. Supported values: `USE`, `MANAGE`, `ADMIN`, `ALL` or `UNLOCK`. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ## Attribute Reference diff --git a/website/docs/r/virtual_router_instance.markdown b/website/docs/r/virtual_router_instance.markdown index 7156941ff..80579a152 100644 --- a/website/docs/r/virtual_router_instance.markdown +++ b/website/docs/r/virtual_router_instance.markdown @@ -69,6 +69,13 @@ resource "opennebula_virtual_router_instance" "example" { tags = { environment = "example" } + + template_section { + name = "test1" + tags = { + tag1 = "value1" + } + } } ``` diff --git a/website/docs/r/virtual_router_instance_template.markdown b/website/docs/r/virtual_router_instance_template.markdown index 3541bfef3..979219de6 100644 --- a/website/docs/r/virtual_router_instance_template.markdown +++ b/website/docs/r/virtual_router_instance_template.markdown @@ -40,6 +40,13 @@ resource "opennebula_virtual_router_instance_template" "example" { tags = { environment = "example" } + + template_section { + name = "test" + tags = { + tag1 = "value1" + } + } } ``` @@ -64,6 +71,7 @@ resource "opennebula_virtual_router_instance_template" "example" { * `sched_ds_requirements` - (Optional) Storage placement requirements to deploy the resource following specific rule. * `tags` - (Optional) Template tags (Key = Value). * `lock` - (Optional) Lock the template with a specific lock level. Supported values: `USE`, `MANAGE`, `ADMIN`, `ALL` or `UNLOCK`. +* `template_section` - (Optional) Allow to add a custom vector. See [Template section parameters](#template-section-parameters) ### Graphics parameters