diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a4e89b..4d5b9852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ FEATURES: * resources/opennebula_virtual_network_address_range: add `ipam` field (#192) * resources/opennebula_virtual_network_address_range: add `custom` section to allow to pass user defined custom attributes (#376) +* **New Data Source**: `opennebula_templates` (#322) BUG FIXES: diff --git a/opennebula/data_opennebula_template.go b/opennebula/data_opennebula_template.go index 719ed5b8..765ebfb2 100644 --- a/opennebula/data_opennebula_template.go +++ b/opennebula/data_opennebula_template.go @@ -11,105 +11,114 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataOpennebulaTemplate() *schema.Resource { - return &schema.Resource{ - ReadContext: datasourceOpennebulaTemplateRead, +func commonDatasourceTemplateSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "has_cpu": { + Type: schema.TypeBool, + Optional: true, + Description: "Indicate if template has CPU defined", + }, + "has_vcpu": { + Type: schema.TypeBool, + Optional: true, + Description: "Indicate if template has VCPU defined", + }, + "has_memory": { + Type: schema.TypeBool, + Optional: true, + Description: "Indicate if template has memory defined", + }, + "cpu": func() *schema.Schema { + s := cpuSchema() - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeInt, - Optional: true, - Default: -1, - Description: "Id of the template", - }, - "name": { - Type: schema.TypeString, - Optional: true, - Description: "Name of the Template", - }, - "has_cpu": { - Type: schema.TypeBool, - Optional: true, - Description: "Indicate if template has CPU defined", - }, - "has_vcpu": { - Type: schema.TypeBool, - Optional: true, - Description: "Indicate if template has VCPU defined", - }, - "has_memory": { - Type: schema.TypeBool, - Optional: true, - Description: "Indicate if template has memory defined", - }, - "cpu": func() *schema.Schema { - s := cpuSchema() + s.ValidateFunc = func(v interface{}, k string) (ws []string, errs []error) { + value := v.(float64) - s.ValidateFunc = func(v interface{}, k string) (ws []string, errs []error) { - value := v.(float64) + if value == 0 { + errs = append(errs, errors.New("cpu should be strictly greater than 0")) + } - if value == 0 { - errs = append(errs, errors.New("cpu should be strictly greater than 0")) - } + return + } + return s + }(), + "vcpu": func() *schema.Schema { + s := vcpuSchema() - return + s.ValidateFunc = func(v interface{}, k string) (ws []string, errs []error) { + value := v.(int) + + if value == 0 { + errs = append(errs, errors.New("vcpu should be strictly greater than 0")) } - return s - }(), - "vcpu": func() *schema.Schema { - s := vcpuSchema() - s.ValidateFunc = func(v interface{}, k string) (ws []string, errs []error) { - value := v.(int) + return + } + return s + }(), + "memory": func() *schema.Schema { + s := memorySchema() - if value == 0 { - errs = append(errs, errors.New("vcpu should be strictly greater than 0")) - } + s.ValidateFunc = func(v interface{}, k string) (ws []string, errs []error) { + value := v.(int) - return + if value == 0 { + errs = append(errs, errors.New("memory should be strictly greater than 0")) } - return s - }(), - "memory": func() *schema.Schema { - s := memorySchema() - s.ValidateFunc = func(v interface{}, k string) (ws []string, errs []error) { - value := v.(int) + return + } + return s + }(), + "tags": tagsSchema(), + } +} - if value == 0 { - errs = append(errs, errors.New("memory should be strictly greater than 0")) - } +func dataOpennebulaTemplate() *schema.Resource { + return &schema.Resource{ + ReadContext: datasourceOpennebulaTemplateRead, - return - } - return s - }(), - "disk": func() *schema.Schema { - s := diskSchema() - s.Computed = true - s.Optional = false - return s - }(), - "nic": func() *schema.Schema { - s := nicSchema() - s.Computed = true - s.Optional = false - return s - }(), - "vmgroup": func() *schema.Schema { - s := vmGroupSchema() - s.Computed = true - s.Optional = false - s.MaxItems = 0 - s.Description = "Virtual Machine Group to associate with during VM creation only." - return s - }(), - "tags": tagsSchema(), - }, + Schema: mergeSchemas( + commonDatasourceTemplateSchema(), + map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Optional: true, + Default: -1, + Description: "Id of the template", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the Template", + }, + "disk": func() *schema.Schema { + s := diskSchema() + s.Computed = true + s.Optional = false + return s + }(), + "nic": func() *schema.Schema { + s := nicSchema() + s.Computed = true + s.Optional = false + return s + }(), + "vmgroup": func() *schema.Schema { + s := vmGroupSchema() + s.Computed = true + s.Optional = false + s.MaxItems = 0 + s.Description = "Virtual Machine Group to associate with during VM creation only." + return s + }(), + }, + ), } } -func templateFilter(d *schema.ResourceData, meta interface{}) (*templateSc.Template, error) { +// shared with opennebula_templates datasource +func commonTemplatesFilter(d *schema.ResourceData, meta interface{}) ([]*templateSc.Template, error) { config := meta.(*Configuration) controller := config.Controller @@ -172,9 +181,19 @@ func templateFilter(d *schema.ResourceData, meta interface{}) (*templateSc.Templ match = append(match, &templates.Templates[i]) } - // check filtering results + return match, nil +} + +func templateFilter(d *schema.ResourceData, meta interface{}) (*templateSc.Template, error) { + + match, err := commonTemplatesFilter(d, meta) + if err != nil { + return nil, err + } + + // the template datasource should match at most one element if len(match) == 0 { - return nil, fmt.Errorf("no template match the constraints") + return nil, fmt.Errorf("no templates match the constraints") } else if len(match) > 1 { return nil, fmt.Errorf("several templates match the constraints") } diff --git a/opennebula/data_opennebula_templates.go b/opennebula/data_opennebula_templates.go new file mode 100644 index 00000000..a86d398d --- /dev/null +++ b/opennebula/data_opennebula_templates.go @@ -0,0 +1,247 @@ +package opennebula + +import ( + "context" + "crypto/sha512" + "fmt" + "regexp" + "sort" + "strconv" + + templateSc "github.com/OpenNebula/one/src/oca/go/src/goca/schemas/template" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataOpennebulaTemplates() *schema.Resource { + return &schema.Resource{ + ReadContext: datasourceOpennebulaTemplatesRead, + + Schema: mergeSchemas( + commonDatasourceTemplateSchema(), + map[string]*schema.Schema{ + "name_regex": { + Type: schema.TypeString, + Optional: true, + Description: "Filter templates by name with a RE2 a regular expression", + }, + "sort_on": { + Type: schema.TypeString, + Optional: true, + Description: "Attribute used to sort the templates list, it has to be an ", + }, + "order": { + Type: schema.TypeString, + Optional: true, + Description: "Ordering of the sort: asc or desc", + }, + "templates": { + Type: schema.TypeList, + Optional: false, + Computed: true, + Description: "List of matching templates", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Optional: false, + Computed: true, + Description: "Name of the Template", + }, + "name": { + Type: schema.TypeString, + Optional: false, + Computed: true, + Description: "Name of the Template", + }, + "cpu": func() *schema.Schema { + s := cpuSchema() + s.Optional = false + s.Computed = true + return s + }(), + "vcpu": func() *schema.Schema { + s := vcpuSchema() + s.Optional = false + s.Computed = true + return s + }(), + "memory": func() *schema.Schema { + s := memorySchema() + s.Optional = false + s.Computed = true + return s + }(), + "disk": func() *schema.Schema { + s := diskSchema() + s.Computed = true + s.Optional = false + return s + }(), + "nic": func() *schema.Schema { + s := nicSchema() + s.Computed = true + s.Optional = false + return s + }(), + "vmgroup": func() *schema.Schema { + s := vmGroupSchema() + s.Computed = true + s.Optional = false + s.MaxItems = 0 + s.Description = "Virtual Machine Group to associate with during VM creation only." + return s + }(), + "register_date": { + Type: schema.TypeInt, + Optional: false, + Computed: true, + Description: "Creation date of the template.", + }, + "tags": func() *schema.Schema { + s := tagsSchema() + s.Computed = true + s.Optional = false + return s + }(), + }, + }, + }, + }, + ), + } +} + +func templatesFilter(d *schema.ResourceData, meta interface{}) ([]*templateSc.Template, error) { + newMatch := make([]*templateSc.Template, 0) + + matched, err := commonTemplatesFilter(d, meta) + if err != nil { + return nil, err + } + + nameRegStr := d.Get("name_regex").(string) + if len(nameRegStr) > 0 { + nameReg := regexp.MustCompile(nameRegStr) + for _, tpl := range matched { + if !nameReg.MatchString(tpl.Name) { + continue + } + newMatch = append(newMatch, tpl) + } + } + + if len(newMatch) == 0 { + return nil, fmt.Errorf("no templates match the constraints") + } + + return newMatch, nil +} + +func datasourceOpennebulaTemplatesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + + var diags diag.Diagnostics + + templates, err := templatesFilter(d, meta) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "templates filtering failed", + Detail: err.Error(), + }) + return diags + } + + templatesMaps := make([]map[string]interface{}, 0, len(templates)) + for _, template := range templates { + + cpu, _ := template.Template.GetCPU() + vcpu, _ := template.Template.GetVCPU() + memory, _ := template.Template.GetMemory() + + // builds disks list + disks := template.Template.GetDisks() + diskList := make([]interface{}, 0, len(disks)) + + for _, disk := range disks { + diskList = append(diskList, flattenDisk(disk)) + } + + // builds nics list + nics := template.Template.GetNICs() + nicList := make([]interface{}, 0, len(nics)) + + for _, nic := range nics { + nicList = append(nicList, flattenNIC(nic)) + } + + // builds VM Groups list + dynTemplate := template.Template.Template + vmgMap := make([]map[string]interface{}, 0, 1) + vmgIdStr, _ := dynTemplate.GetStrFromVec("VMGROUP", "VMGROUP_ID") + vmgid, _ := strconv.ParseInt(vmgIdStr, 10, 32) + vmgRole, _ := dynTemplate.GetStrFromVec("VMGROUP", "ROLE") + + vmgMap = append(vmgMap, map[string]interface{}{ + "vmgroup_id": vmgid, + "role": vmgRole, + }) + + // tags + tplPairs := pairsToMap(template.Template.Template) + + templateMap := map[string]interface{}{ + "name": template.Name, + "id": template.ID, + "cpu": cpu, + "vcpu": vcpu, + "memory": memory, + "disk": diskList, + "nic": nicList, + "vmgroup": vmgMap, + "register_date": template.RegTime, + } + + if len(tplPairs) > 0 { + templateMap["tags"] = tplPairs + } + + templatesMaps = append(templatesMaps, templateMap) + } + + sortOnAttr := d.Get("sort_on").(string) + ordering := d.Get("order").(string) + var orderingFn func(int, int) bool + switch ordering { + case "ASC": + orderingFn = func(i, j int) bool { + return templatesMaps[i][sortOnAttr].(int) > templatesMaps[j][sortOnAttr].(int) + } + case "DESC": + orderingFn = func(i, j int) bool { + return templatesMaps[i][sortOnAttr].(int) < templatesMaps[j][sortOnAttr].(int) + } + } + + // will crash if sortOnAttr is the name of an attributes with another type than integer + sort.Slice(templatesMaps, func(i, j int) bool { + return orderingFn(i, j) + }) + nameRegStr := d.Get("name_regex").(string) + + d.SetId(fmt.Sprintf("%x", sha512.Sum512([]byte(ordering+sortOnAttr+nameRegStr)))) + + err = d.Set("templates", templatesMaps) + if err != nil { + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "failed to set templates", + Detail: err.Error(), + }) + return diags + } + } + + return nil +} diff --git a/opennebula/provider.go b/opennebula/provider.go index 9ae73c41..37c93dde 100644 --- a/opennebula/provider.go +++ b/opennebula/provider.go @@ -71,6 +71,7 @@ func Provider() *schema.Provider { "opennebula_image": dataOpennebulaImage(), "opennebula_security_group": dataOpennebulaSecurityGroup(), "opennebula_template": dataOpennebulaTemplate(), + "opennebula_templates": dataOpennebulaTemplates(), "opennebula_user": dataOpennebulaUser(), "opennebula_virtual_data_center": dataOpennebulaVirtualDataCenter(), "opennebula_virtual_network": dataOpennebulaVirtualNetwork(), diff --git a/website/docs/d/templates.html.markdown b/website/docs/d/templates.html.markdown new file mode 100644 index 00000000..b7268122 --- /dev/null +++ b/website/docs/d/templates.html.markdown @@ -0,0 +1,54 @@ +--- +layout: "opennebula" +page_title: "OpenNebula: opennebula_templates" +sidebar_current: "docs-opennebula-datasource-templates" +description: |- + Get the template information for a given name. +--- + +# opennebula_templates + +Use this data source to retrieve templates information. + +## Example Usage + +```hcl +data "opennebula_templates" "example" { + name_regex = "test.*" + has_cpu = true + sort_on = "register_date" + sort_on = "ASC" +} +``` + + +## Argument Reference + +* `name_regex` - (Optional) Filter templates by name with a RE2 regular expression. +* `has_cpu` - (Optional) Indicate if a CPU value has been defined. +* `cpu` - (Optional) Amount of CPU shares assigned to the VM. +* `has_vcpu` - (Optional) Indicate if a VCPU value has been defined. +* `vcpu` - (Optional) Number of CPU cores presented to the VM. +* `has_memory` - (Optional) Indicate if a memory value has been defined. +* `memory` - (Optional) Amount of RAM assigned to the VM in MB. +* `tags` - (Optional) Template tags (Key = Value). +* `order` - (Optional) Ordering of the sort: asc or desc. + +## Attribute Reference + +The following attributes are exported: + +* `templates` - For each filtered template, this section collect a list of attributes. See [templates attributes](#templates-attributes) + +## Templates attributes + +* `id` - ID of the template. +* `name` - Name of the template. +* `cpu` - Amount of CPU shares assigned to the VM. +* `vcpu` - Number of CPU cores presented to the VM. +* `memory` - Amount of RAM assigned to the VM in MB. +* `disk` - Disk parameters +* `nic` - NIC parameters +* `vmgroup` - VM group parameters +* `register_date` - Creation date of the template +* `tags` - Tags of the template (Key = Value).