Skip to content

Commit

Permalink
Ignition support: first phase
Browse files Browse the repository at this point in the history
A few changes here:

1.  CoreOS Ignition support:  A CoreOS Ignition file can be
    specified for a domain using the "coreos_ignition"
    parameter.  Alternatively the "coreos_ignition" parameter
    can be set equal to a Terraform ignition object.  If the
    latter, the ignition object is written to a file in /tmp
    whose name is a hash of the object itself.  This file-name
    is stored in libvirt domain metadata, and is removed when
    the domain is destroyed.  This feature requires the emission
    of qemu:commandline XML XML and also the setting of the XML
    name-space to "http://libvirt.org/schemas/domain/qemu/1.0"
2.  "graphics" block:  A "graphics" block can be specified
    in a domain definition.  We've added this because we've
    found that for some builds of qemu the default "spice"
    emulator doesn't work and results in failure to boot the
    VMs.
3.  "console" block: One or more "console" blocks can be
    specified in a domain definition.  Note the description
    in domain.html.markdown, and the information in
    https://libvirt.org/formatdomain.html#elementsConsole

Added a test for coreos_ignition in resource_libvirt_domain_test.go
  • Loading branch information
eamonnotoole committed Feb 14, 2017
1 parent ea96b66 commit 252e13d
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 10 deletions.
24 changes: 23 additions & 1 deletion docs/providers/libvirt/r/domain.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,29 @@ The following arguments are supported:

The following extra argument is provided for CoreOS images:

* `coreos_ignition` - (Optional) The name of a CoreOS Ignition file.
* `coreos_ignition` - (Optional) This can be set to the name of an existing ignition
file or alternatively can be set to the rendered value of a Terraform ignition provider object.

An example where a Terraform ignition provider object is used:
```
# Systemd unit resource containing the unit definition
resource "ignition_systemd_unit" "example" {
name = "example.service"
content = "[Service]\nType=oneshot\nExecStart=/usr/bin/echo Hello World\n\n[Install]\nWantedBy=multi-user.target"
}
# Ignition config include the previous defined systemd unit resource
resource "ignition_config" "example" {
systemd = [
"${ignition_systemd_unit.example.id}",
]
}
resource "libvirt_domain" "my_machine" {
coreos_ignition = "${ignition_config.example.rendered}"
...
}
```

Note that to make use of Ignition files with CoreOS the host must be running
QEMU v2.6 or greater.
Expand Down
6 changes: 4 additions & 2 deletions libvirt/domain_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ type defDomain struct {
}

type defMetadata struct {
XMLName xml.Name `xml:"http://github.com/dmacvicar/terraform-provider-libvirt/ user_data"`
Xml string `xml:",cdata"`
XMLName xml.Name `xml:"http://github.com/dmacvicar/terraform-provider-libvirt/ user_data"`
Xml string `xml:",cdata"`
IgnitionFile string `xml:",ignition_file,omitempty"`
}

type defOs struct {
Expand Down Expand Up @@ -143,3 +144,4 @@ func newDomainDef() defDomain {

return domainDef
}

2 changes: 2 additions & 0 deletions libvirt/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
ignition "github.com/hashicorp/terraform/builtin/providers/ignition"
)

var testAccProviders map[string]terraform.ResourceProvider
Expand All @@ -15,6 +16,7 @@ func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"libvirt": testAccProvider,
"ignition": ignition.Provider(),
}
}

Expand Down
67 changes: 61 additions & 6 deletions libvirt/resource_libvirt_domain.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package libvirt

import (
"encoding/json"
"encoding/xml"
"fmt"
"log"
Expand All @@ -9,6 +10,8 @@ import (
"strings"
"time"

"crypto/sha256"
"encoding/hex"
"github.com/davecgh/go-spew/spew"
libvirt "github.com/dmacvicar/libvirt-go"
"github.com/hashicorp/terraform/helper/schema"
Expand All @@ -20,6 +23,11 @@ func init() {
spew.Config.Indent = "\t"
}

func hash(s string) string {
sha := sha256.Sum256([]byte(s))
return hex.EncodeToString(sha[:])
}

func resourceLibvirtDomain() *schema.Resource {
return &schema.Resource{
Create: resourceLibvirtDomainCreate,
Expand Down Expand Up @@ -145,12 +153,38 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error
}

if ignition, ok := d.GetOk("coreos_ignition"); ok {
ignitionFile := ignition.(string)
if _, err := os.Stat(ignitionFile); os.IsNotExist(err) {
return fmt.Errorf("Could not find ignition file '%s'", ignitionFile)
var file bool
file = true
ignitionString := ignition.(string)
if _, err := os.Stat(ignitionString); err != nil {
var js map[string]interface{}
if err_conf := json.Unmarshal([]byte(ignitionString), &js); err_conf != nil {
return fmt.Errorf("coreos_ignition parameter is neither a file "+
"nor a valid json object %s", ignition)
}
log.Printf("[DEBUG] about to set file to false")
file = false
}
log.Printf("[DEBUG] file %s", file)
var fw_cfg []defCmd
ign_str := fmt.Sprintf("name=opt/com.coreos/config,file=%s", ignitionFile)
var ign_str string
if !file {
ignitionHash := hash(ignitionString)
tempFileName := fmt.Sprint("/tmp/", ignitionHash, ".ign")
tempFile, err := os.Create(tempFileName)
defer tempFile.Close()
if err != nil {
return fmt.Errorf("Cannot create temporary ignition file %s", tempFileName)
}
if _, err := tempFile.WriteString(ignitionString); err != nil {
return fmt.Errorf("Cannot write Ignition object to temporary "+
"ignition file %s", tempFileName)
}
domainDef.Metadata.TerraformLibvirt.IgnitionFile = tempFileName
ign_str = fmt.Sprintf("name=opt/com.coreos/config,file=%s", tempFileName)
} else if file {
ign_str = fmt.Sprintf("name=opt/com.coreos/config,file=%s", ignitionString)
}
fw_cfg = append(fw_cfg, defCmd{"-fw_cfg"})
fw_cfg = append(fw_cfg, defCmd{ign_str})
domainDef.CmdLine.Cmd = fw_cfg
Expand Down Expand Up @@ -197,12 +231,14 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error

if consoleCount, ok := d.GetOk("console.#"); ok {
var consoles []defConsole
for i :=0; i < consoleCount.(int); i++ {
for i := 0; i < consoleCount.(int); i++ {
console := defConsole{}
consolePrefix := fmt.Sprintf("console.%d", i)
console.Type = d.Get(consolePrefix + ".type").(string)
console.Source.Path = d.Get(consolePrefix + ".source_path").(string)
console.Target.Port = d.Get(consolePrefix + ".target_port").(string)
if source_path, ok := d.GetOk(consolePrefix + ".source_path"); ok {
console.Source.Path = source_path.(string)
}
if target_type, ok := d.GetOk(consolePrefix + ".target_type"); ok {
console.Target.Type = target_type.(string)
}
Expand Down Expand Up @@ -756,6 +792,25 @@ func resourceLibvirtDomainDelete(d *schema.ResourceData, meta interface{}) error
}
defer domain.Free()

xmlDesc, err := domain.GetXMLDesc(0)
if err != nil {
return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
}

domainDef := newDomainDef()
err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
if err != nil {
return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
}

if ignitionFile := domainDef.Metadata.TerraformLibvirt.IgnitionFile; ignitionFile != "" {
log.Printf("[DEBUG] deleting ignition file")
err = os.Remove(ignitionFile)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("Error removing Ignition file %s: %s", ignitionFile, err)
}
}

state, err := domain.GetState()
if err != nil {
return fmt.Errorf("Couldn't get info about domain: %s", err)
Expand Down
74 changes: 74 additions & 0 deletions libvirt/resource_libvirt_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
//"gopkg.in/alexzorin/libvirt-go.v2"
"encoding/xml"
libvirt "github.com/dmacvicar/libvirt-go"
)

Expand Down Expand Up @@ -208,6 +209,44 @@ func TestAccLibvirtDomain_NetworkInterface(t *testing.T) {
})
}

func TestAccLibvirtDomain_IgnitionObject(t *testing.T) {
var domain libvirt.VirDomain

var config = fmt.Sprintf(`
resource "ignition_systemd_unit" "acceptance-test-systemd" {
name = "example.service"
content = "[Service]\nType=oneshot\nExecStart=/usr/bin/echo Hello World\n\n[Install]\nWantedBy=multi-user.target"
}
resource "ignition_config" "acceptance-test-config" {
systemd = [
"${ignition_systemd_unit.acceptance-test-systemd.id}",
]
}
resource "libvirt_domain" "acceptance-test-domain" {
name = "terraform-test-domain"
coreos_ignition = "${ignition_config.acceptance-test-config.rendered}"
}
`)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckLibvirtDomainDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: config,
ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckLibvirtDomainExists("libvirt_domain.acceptance-test-domain", &domain),
testAccCheckIgnitionFileNameExists(&domain),
),
},
},
})
}

func testAccCheckLibvirtDomainDestroy(s *terraform.State) error {
virtConn := testAccProvider.Meta().(*Client).libvirt

Expand Down Expand Up @@ -263,3 +302,38 @@ func testAccCheckLibvirtDomainExists(n string, domain *libvirt.VirDomain) resour
return nil
}
}

func testAccCheckIgnitionFileNameExists(domain *libvirt.VirDomain) resource.TestCheckFunc {
return func(s *terraform.State) error {
var ignStr string
for _, rs := range s.RootModule().Resources {
if rs.Type != "libvirt_domain" {
continue
}
ignStr = rs.Primary.Attributes["coreos_ignition"]
}

xmlDesc, err := domain.GetXMLDesc(0)
if err != nil {
return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
}

domainDef := newDomainDef()
err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
if err != nil {
return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
}

ignitionFile := domainDef.Metadata.TerraformLibvirt.IgnitionFile
if ignitionFile == "" {
return fmt.Errorf("No ignition file meta-data")
}

hashStr := hash(ignStr)
hashFile := fmt.Sprint("/tmp/", hashStr, ".ign")
if ignitionFile != hashFile {
return fmt.Errorf("Igntion file metadata incorrect %s %s", ignitionFile, hashFile)
}
return nil
}
}
2 changes: 1 addition & 1 deletion vendor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ vendors:
- path: github.com/hashicorp/logutils
rev: 0dc08b1671f34c4250ce212759ebd880f743d883
- path: github.com/hashicorp/terraform
rev: v0.8.0
rev: v0.8.5
- path: github.com/hooklift/assert
rev: c7786599453421cddf9aa8a3a7b537f567d1ac1b
- path: github.com/hooklift/iso9660
Expand Down

0 comments on commit 252e13d

Please sign in to comment.