Skip to content

Commit

Permalink
Merge pull request #431 from dmacvicar/xslt_transform
Browse files Browse the repository at this point in the history
Initial prototype of a XML transformation hook based on XSLT
  • Loading branch information
dmacvicar authored Nov 15, 2018
2 parents f0f9b7c + 02a1da1 commit 67abfbc
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 4 deletions.
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
221 changes: 221 additions & 0 deletions libvirt/resource_libvirt_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,227 @@ 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
}),
),
},
},
})
}

// If using XSLT to transform a supported attribute by the terraform
// provider schema, the provider will try to change it back to the
// known state.
// Therefore we explicitly advise against using it with existing
// schema attributes
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),
),
},
},
})
}

// changed whitespace in the xslt should create an empty plan
// as the supress diff function should take care of seeing they are equivalent
func TestAccLibvirtDomain_XSLT_Whitespace(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)

var configAfter = 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
}),
),
},
{
Config: configAfter,
ExpectNonEmptyPlan: false,
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 testAccCheckLibvirtDomainDestroy(s *terraform.State) error {
virtConn := testAccProvider.Meta().(*Client).libvirt
for _, rs := range s.RootModule().Resources {
Expand Down
21 changes: 21 additions & 0 deletions libvirt/resource_libvirt_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,21 @@ func resourceLibvirtNetwork() *schema.Resource {
},
},
},
"xml": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"xslt": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
},
}
}
Expand Down Expand Up @@ -440,6 +455,12 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro
if err != nil {
return fmt.Errorf("Error serializing libvirt network: %s", err)
}
log.Printf("[DEBUG] Generated XML for libvirt network:\n%s", data)

data, err = transformResourceXML(data, d)
if err != nil {
return fmt.Errorf("Error applying XSLT stylesheet: %s", err)
}

log.Printf("[DEBUG] Creating libvirt network at %s: %s", connectURI, data)
network, err := virConn.NetworkDefineXML(data)
Expand Down
26 changes: 23 additions & 3 deletions libvirt/resource_libvirt_volume.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package libvirt

import (
"encoding/xml"
"fmt"
"log"

Expand Down Expand Up @@ -59,6 +58,21 @@ func resourceLibvirtVolume() *schema.Resource {
Optional: true,
ForceNew: true,
},
"xml": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"xslt": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
},
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
Expand Down Expand Up @@ -206,13 +220,19 @@ func resourceLibvirtVolumeCreate(d *schema.ResourceData, meta interface{}) error
}

volumeDef.Capacity.Value = uint64(d.Get("size").(int))
volumeDefXML, err := xml.Marshal(volumeDef)
data, err := xmlMarshallIndented(volumeDef)
if err != nil {
return fmt.Errorf("Error serializing libvirt volume: %s", err)
}
log.Printf("[DEBUG] Generated XML for libvirt volume:\n%s", data)

data, err = transformResourceXML(data, d)
if err != nil {
return fmt.Errorf("Error applying XSLT stylesheet: %s", err)
}

// create the volume
volume, err := pool.StorageVolCreateXML(string(volumeDefXML), 0)
volume, err := pool.StorageVolCreateXML(data, 0)
if err != nil {
return fmt.Errorf("Error creating libvirt volume: %s", err)
}
Expand Down
Loading

0 comments on commit 67abfbc

Please sign in to comment.