Skip to content

Commit

Permalink
Initial prototype of a XML transformation hook based on XSLT
Browse files Browse the repository at this point in the history
  • Loading branch information
dmacvicar committed Sep 23, 2018
1 parent df6aed4 commit 21f9bfa
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 1 deletion.
16 changes: 16 additions & 0 deletions examples/xslt/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
provider "libvirt" {
uri = "qemu:///system"
}

resource "libvirt_domain" "xslt-demo-domain" {
name = "xslt-demo-domain"
memory = "512"

network_interface {
network_name = "default"
}

xml {
xslt = "${file("nicmodel.xsl")}"
}
}
17 changes: 17 additions & 0 deletions examples/xslt/nicmodel.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>

<xsl:template match="/domain/devices/interface[@type='network']/model/@type">
<xsl:attribute name="type">
<xsl:value-of select="'e1000'"/>
</xsl:attribute>
</xsl:template>

</xsl:stylesheet>
20 changes: 19 additions & 1 deletion libvirt/resource_libvirt_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,20 @@ func resourceLibvirtDomain() *schema.Resource {
Default: false,
ForceNew: false,
},
"xml": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"xslt": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
},
}
}
Expand Down Expand Up @@ -456,8 +470,12 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error
if err != nil {
return fmt.Errorf("Error serializing libvirt domain: %s", err)
}
log.Printf("[DEBUG] Generated XML for libvirt domain:\n%s", data)

log.Printf("[DEBUG] Creating libvirt domain with XML:\n%s", data)
data, err = transformResourceXML(data, d)
if err != nil {
return fmt.Errorf("Error applying XSLT stylesheet: %s", err)
}

domain, err := virConn.DomainDefineXML(data)
if err != nil {
Expand Down
115 changes: 115 additions & 0 deletions libvirt/resource_libvirt_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1271,3 +1271,118 @@ func TestAccLibvirtDomain_Import(t *testing.T) {
},
})
}

func TestAccLibvirtDomain_XSLT_UnsupportedAttribute(t *testing.T) {
var domain libvirt.Domain
randomDomainName := acctest.RandString(10)
randomNetworkName := acctest.RandString(10)

var config = fmt.Sprintf(`
resource "libvirt_network" "%s" {
name = "%s"
addresses = ["10.17.3.0/24"]
}
resource "libvirt_domain" "%s" {
name = "%s"
network_interface = {
network_name = "default"
}
xml {
xslt = <<EOF
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/domain/devices/interface[@type='network']/model/@type">
<xsl:attribute name="type">
<xsl:value-of select="'e1000'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
EOF
}
}`, randomNetworkName, randomNetworkName, randomDomainName, randomDomainName)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLibvirtDomainDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testAccCheckLibvirtDomainExists("libvirt_domain."+randomDomainName, &domain),
testAccCheckLibvirtDomainDescription(&domain, func(domainDef libvirtxml.Domain) error {
if domainDef.Devices.Interfaces[0].Model.Type != "e1000" {
return fmt.Errorf("Expecting XSLT to tranform network model to e1000")
}
return nil
}),
),
},
},
})
}

func TestAccLibvirtDomain_XSLT_SupportedAttribute(t *testing.T) {
var domain libvirt.Domain
randomDomainName := acctest.RandString(10)
randomNetworkName := acctest.RandString(10)

var config = fmt.Sprintf(`
resource "libvirt_network" "%s" {
name = "%s"
addresses = ["10.17.3.0/24"]
}
resource "libvirt_domain" "%s" {
name = "%s"
network_interface = {
network_name = "default"
}
xml {
xslt = <<EOF
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/domain/devices/interface[@type='network']/source/@network">
<xsl:attribute name="network">
<xsl:value-of select="'%s'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
EOF
}
}`, randomNetworkName, randomNetworkName, randomDomainName, randomDomainName, randomNetworkName)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLibvirtDomainDestroy,
Steps: []resource.TestStep{
{
Config: config,
ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckLibvirtDomainExists("libvirt_domain."+randomDomainName, &domain),
resource.TestCheckResourceAttr(
"libvirt_domain."+randomDomainName, "network_interface.0.network_name", randomNetworkName),
),
},
},
})
}
59 changes: 59 additions & 0 deletions libvirt/utils_xslt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package libvirt

import (
"io/ioutil"
"log"
"os"
"os/exec"
"strings"

"github.com/hashicorp/terraform/helper/schema"
)

// this function applies a XSLT transform to the xml data
// and is to be reused in all resource types
// your resource need to have a xml.xslt element in the schema
func transformResourceXML(xml string, d *schema.ResourceData) (string, error) {
xslt, ok := d.GetOk("xml.0.xslt")
if !ok {
return xml, nil
}

xsltFile, err := ioutil.TempFile("", "terraform-provider-libvirt-xslt")
if err != nil {
log.Fatal(err)
}
defer os.Remove(xsltFile.Name()) // clean up

// we trim the xslt as it may contain space before the xml declaration
// because of HCL heredoc
if _, err := xsltFile.Write([]byte(strings.TrimSpace(xslt.(string)))); err != nil {
log.Fatal(err)
}

if err := xsltFile.Close(); err != nil {
log.Fatal(err)
}

xmlFile, err := ioutil.TempFile("", "terraform-provider-libvirt-xml")
if err != nil {
log.Fatal(err)
}
defer os.Remove(xmlFile.Name()) // clean up

if _, err := xmlFile.Write([]byte(xml)); err != nil {
log.Fatal(err)
}

if err := xmlFile.Close(); err != nil {
log.Fatal(err)
}

cmd := exec.Command("xsltproc", xsltFile.Name(), xmlFile.Name())
transformedXML, err := cmd.Output()
if err != nil {
return xml, err
}
log.Printf("[DEBUG] Transformed XML with user specified XSLT:\n%s", transformedXML)
return string(transformedXML), nil
}

0 comments on commit 21f9bfa

Please sign in to comment.