diff --git a/opennebula/resource_opennebula_template.go b/opennebula/resource_opennebula_template.go index afeea1015..296fe8cd0 100644 --- a/opennebula/resource_opennebula_template.go +++ b/opennebula/resource_opennebula_template.go @@ -410,9 +410,45 @@ func resourceOpennebulaTemplateReadCustom(ctx context.Context, d *schema.Resourc d.Set("gid", tpl.GID) d.Set("uname", tpl.UName) d.Set("gname", tpl.GName) + d.Set("group", tpl.GName) d.Set("reg_time", tpl.RegTime) d.Set("permissions", permissionsUnixString(*tpl.Permissions)) + for _, value := range tpl.Template.Elements { + if vector, ok := value.(*dyn.Vector); ok { + getVector := make(map[string]interface{}, 0) + // In 'tpl.Template.Elements', there are uppercase keys, and in + // 'commonTemplateSchemas()' there are lower-case keys. + // In 'tpl.Template.Elements', there is a key 'CPU_MODEL', but in + // 'commonTemplateSchemas()' this key is called 'cpumodel'. + vector_key := strings.Replace(strings.ToLower(vector.XMLName.Local), "_", "", -1) + if _, inCommonSchema := commonTemplateSchemas()[vector_key]; !inCommonSchema { + continue + } + switch commonTemplateSchemas()[vector_key].Type.String() { + case "TypeList", "TypeSet": + for _, pair := range vector.Pairs { + getVector[strings.ToLower(pair.XMLName.Local)] = pair.Value + } + + d.Set(vector_key, append(make([]interface{}, 0), getVector)) + case "TypeMap": + for _, pair := range vector.Pairs { + switch pair.XMLName.Local { + case "SET_HOSTNAME", "NETWORK": + // In documentation about the resource 'opennebula_template', + // there are uppercase-keys in the context-field. For most other + // fields, the documentation uses lowercase-keys. + getVector[pair.XMLName.Local] = pair.Value + default: + getVector[strings.ToLower(pair.XMLName.Local)] = pair.Value + } + } + d.Set(strings.ToLower(vector.XMLName.Local), getVector) + } + } + } + err = flattenTemplateDisks(d, &tpl.Template) if err != nil { diags = append(diags, diag.Diagnostic{ diff --git a/opennebula/resource_opennebula_template_test.go b/opennebula/resource_opennebula_template_test.go index 3dad57f85..bca85dedc 100644 --- a/opennebula/resource_opennebula_template_test.go +++ b/opennebula/resource_opennebula_template_test.go @@ -25,6 +25,8 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "permissions", "660"), resource.TestCheckResourceAttr("opennebula_template.template", "group", "oneadmin"), resource.TestCheckResourceAttr("opennebula_template.template", "cpu", "0.5"), + resource.TestCheckResourceAttr("opennebula_template.template", "vcpu", "1"), + resource.TestCheckResourceAttr("opennebula_template.template", "memory", "512"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.keymap", "en-us"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.listen", "0.0.0.0"), @@ -32,6 +34,9 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "os.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.arch", "x86_64"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.boot", ""), + resource.TestCheckResourceAttr("opennebula_template.template", "context.%", "2"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.dns_hostname", "yes"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.NETWORK", "YES"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.%", "2"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.env", "prod"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.customer", "test"), @@ -61,6 +66,11 @@ func TestAccTemplate(t *testing.T) { }), ), }, + { + ResourceName: "opennebula_template.template", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccTemplateCPUModel, Check: resource.ComposeTestCheckFunc( @@ -68,6 +78,8 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "permissions", "660"), resource.TestCheckResourceAttr("opennebula_template.template", "group", "oneadmin"), resource.TestCheckResourceAttr("opennebula_template.template", "cpu", "0.5"), + resource.TestCheckResourceAttr("opennebula_template.template", "vcpu", "1"), + resource.TestCheckResourceAttr("opennebula_template.template", "memory", "512"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.keymap", "en-us"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.listen", "0.0.0.0"), @@ -75,6 +87,9 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "os.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.arch", "x86_64"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.boot", ""), + resource.TestCheckResourceAttr("opennebula_template.template", "context.%", "2"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.dns_hostname", "yes"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.NETWORK", "YES"), resource.TestCheckResourceAttr("opennebula_template.template", "cpumodel.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "cpumodel.0.model", "host-passthrough"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.%", "2"), @@ -100,6 +115,11 @@ func TestAccTemplate(t *testing.T) { }), ), }, + { + ResourceName: "opennebula_template.template", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccTemplateConfigUpdate, Check: resource.ComposeTestCheckFunc( @@ -107,6 +127,8 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "permissions", "642"), resource.TestCheckResourceAttr("opennebula_template.template", "group", "oneadmin"), resource.TestCheckResourceAttr("opennebula_template.template", "cpu", "1"), + resource.TestCheckResourceAttr("opennebula_template.template", "vcpu", "1"), + resource.TestCheckResourceAttr("opennebula_template.template", "memory", "768"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.keymap", "en-us"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.listen", "0.0.0.0"), @@ -114,6 +136,9 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "os.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.arch", "x86_64"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.boot", ""), + resource.TestCheckResourceAttr("opennebula_template.template", "context.%", "2"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.dns_hostname", "yes"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.NETWORK", "YES"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.%", "3"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.env", "dev"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.customer", "test"), @@ -142,6 +167,11 @@ func TestAccTemplate(t *testing.T) { }), ), }, + { + ResourceName: "opennebula_template.template", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccTemplateConfigDelete, Check: resource.ComposeTestCheckFunc( @@ -149,6 +179,8 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "permissions", "642"), resource.TestCheckResourceAttr("opennebula_template.template", "group", "oneadmin"), resource.TestCheckResourceAttr("opennebula_template.template", "cpu", "1"), + resource.TestCheckResourceAttr("opennebula_template.template", "vcpu", "1"), + resource.TestCheckResourceAttr("opennebula_template.template", "memory", "768"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.keymap", "en-us"), resource.TestCheckResourceAttr("opennebula_template.template", "graphics.0.listen", "0.0.0.0"), @@ -156,10 +188,14 @@ func TestAccTemplate(t *testing.T) { resource.TestCheckResourceAttr("opennebula_template.template", "os.#", "1"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.arch", "x86_64"), resource.TestCheckResourceAttr("opennebula_template.template", "os.0.boot", ""), + resource.TestCheckResourceAttr("opennebula_template.template", "context.%", "2"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.dns_hostname", "yes"), + resource.TestCheckResourceAttr("opennebula_template.template", "context.NETWORK", "YES"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.%", "2"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.env", "dev"), resource.TestCheckResourceAttr("opennebula_template.template", "tags.customer", "test"), resource.TestCheckNoResourceAttr("opennebula_template.template", "tags.version"), + resource.TestCheckNoResourceAttr("opennebula_template.template", "features"), resource.TestCheckResourceAttrSet("opennebula_template.template", "uid"), resource.TestCheckResourceAttrSet("opennebula_template.template", "gid"), resource.TestCheckResourceAttrSet("opennebula_template.template", "uname"), @@ -177,6 +213,16 @@ func TestAccTemplate(t *testing.T) { }), ), }, + { + ResourceName: "opennebula_template.template", + ImportState: true, + ImportStateVerify: true, + // The fields 'description' and 'sched_requirements' unfortunately need to be ignored during state-verify. + // During the first teststeps, these fields are defined, and in the last teststp, these fields are deleted. + // However, these fields are still present in the Terraform-state. For troubleshooting, note that + // d.State().Attributes["description"] and d.GetOk("description") give different results, where this may be unexpected. + ImportStateVerifyIgnore: []string{"description", "sched_requirements"}, + }, }, }) } @@ -256,7 +302,7 @@ resource "opennebula_template" "template" { context = { dns_hostname = "yes" - network = "YES" + NETWORK = "YES" } graphics { @@ -304,7 +350,7 @@ resource "opennebula_template" "template" { context = { dns_hostname = "yes" - network = "YES" + NETWORK = "YES" } graphics { @@ -358,7 +404,7 @@ resource "opennebula_template" "template" { context = { dns_hostname = "yes" - network = "YES" + NETWORK = "YES" } graphics { @@ -403,7 +449,7 @@ resource "opennebula_template" "template" { context = { dns_hostname = "yes" - network = "YES" + NETWORK = "YES" } graphics { diff --git a/opennebula/resource_opennebula_virtual_machine_test.go b/opennebula/resource_opennebula_virtual_machine_test.go index 80b46ecbb..5dfb2288e 100644 --- a/opennebula/resource_opennebula_virtual_machine_test.go +++ b/opennebula/resource_opennebula_virtual_machine_test.go @@ -2124,7 +2124,7 @@ resource "opennebula_template" "template" { context = { dns_hostname = "yes" - network = "YES" + NETWORK = "YES" } graphics { diff --git a/opennebula/resource_opennebula_virtual_router_test.go b/opennebula/resource_opennebula_virtual_router_test.go index 9893d8ca6..28184fd19 100644 --- a/opennebula/resource_opennebula_virtual_router_test.go +++ b/opennebula/resource_opennebula_virtual_router_test.go @@ -231,7 +231,7 @@ resource "opennebula_virtual_router_instance_template" "test" { context = { dns_hostname = "yes" - network = "YES" + NETWORK = "YES" } graphics { diff --git a/opennebula/shared_schemas.go b/opennebula/shared_schemas.go index ae0b46356..8a04647ca 100644 --- a/opennebula/shared_schemas.go +++ b/opennebula/shared_schemas.go @@ -880,6 +880,8 @@ func flattenVMUserTemplate(d *schema.ResourceData, meta interface{}, inheritedTa // We read attributes only if they are described in the VM description // to avoid a diff due to template attribute inheritence + // However, when importing a Terraform-managed template from Opennebula, + // not all parameters can be expected to present in d. // add default_tags to tags_all config := meta.(*Configuration) @@ -903,17 +905,24 @@ func flattenVMUserTemplate(d *schema.ResourceData, meta interface{}, inheritedTa d.Set("default_tags", config.defaultTags) tags := make(map[string]interface{}) - // Get only tags described in the configuration - if tagsInterface, ok := d.GetOk("tags"); ok { - for k, _ := range tagsInterface.(map[string]interface{}) { - tagValue, err := vmTemplate.GetStr(strings.ToUpper(k)) - if err != nil { - return err - } - tags[k] = tagValue - tagsAll[k] = tagValue + // Read all tags in the template, and add them to d. + for element, _ := range vmTemplate.Elements { + pair, ok := vmTemplate.Elements[element].(*dynamic.Pair) + if !ok { + continue } + // The template 'vmTemplate' contains uppercase-keys, The + // common schema 'commonTemplateSchemas()' contains lowercase-keys. + // If a key in 'vmTemplate.Elements' is not contained in + // 'commonTemplateSchemas()', it is assumed the key is a tag. + if _, ok := commonTemplateSchemas()[strings.ToLower(pair.Key())]; !ok { + tags[strings.ToLower(pair.Key())] = pair.Value + tagsAll[strings.ToLower(pair.Key())] = pair.Value + } + } + + if len(tags) > 0 { err := d.Set("tags", tags) if err != nil { return err