summaryrefslogtreecommitdiff
path: root/libvirt/coreos_ignition_def.go
diff options
context:
space:
mode:
authorEamonn O'Toole <eamonn.otoole@hpe.com>2017-03-02 11:00:59 +0000
committerEamonn O'Toole <eamonn.otoole@hpe.com>2017-03-23 11:32:31 +0000
commitf130f9848e61924bded600602a91822cf76e0959 (patch)
treea75c0c56fe384d1789cc59b56408ae23e43d5b00 /libvirt/coreos_ignition_def.go
parenta07e21b74726d9ce443ac9332417c169b2ccd708 (diff)
downloadterraform-provider-libvirt-f130f9848e61924bded600602a91822cf76e0959.tar
terraform-provider-libvirt-f130f9848e61924bded600602a91822cf76e0959.tar.gz
Write Ignition file as a volume in a libvirt storage pool
This avoids the problem where the Ignition file is remote to the host on which the libvirt domain is being created. We've added a "libvirt_ignition" resource which manages the Ignition file in the libvirt volume - creates it, and destroys it. The "coreos_ignition" field in the libvirt_domain definition must point to the Id of a "libvirt_ignition" resource. The code is modelled on that used for CloudInit.
Diffstat (limited to 'libvirt/coreos_ignition_def.go')
-rw-r--r--libvirt/coreos_ignition_def.go202
1 files changed, 202 insertions, 0 deletions
diff --git a/libvirt/coreos_ignition_def.go b/libvirt/coreos_ignition_def.go
new file mode 100644
index 00000000..216075f2
--- /dev/null
+++ b/libvirt/coreos_ignition_def.go
@@ -0,0 +1,202 @@
+package libvirt
+
+import (
+ "encoding/xml"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+
+ libvirt "github.com/dmacvicar/libvirt-go"
+ "github.com/mitchellh/packer/common/uuid"
+)
+
+type defIgnition struct {
+ Name string
+ PoolName string
+ Content string
+}
+
+// Creates a new cloudinit with the defaults
+// the provider uses
+func newIgnitionDef() defIgnition {
+ ign := defIgnition{}
+
+ return ign
+}
+
+// Create a ISO file based on the contents of the CloudInit instance and
+// uploads it to the libVirt pool
+// Returns a string holding terraform's internal ID of this resource
+func (ign *defIgnition) CreateAndUpload(virConn *libvirt.VirConnection) (string, error) {
+ pool, err := virConn.LookupStoragePoolByName(ign.PoolName)
+ if err != nil {
+ return "", fmt.Errorf("can't find storage pool '%s'", ign.PoolName)
+ }
+ defer pool.Free()
+
+ PoolSync.AcquireLock(ign.PoolName)
+ defer PoolSync.ReleaseLock(ign.PoolName)
+
+ // Refresh the pool of the volume so that libvirt knows it is
+ // not longer in use.
+ WaitForSuccess("Error refreshing pool for volume", func() error {
+ return pool.Refresh(0)
+ })
+
+ volumeDef := newDefVolume()
+ volumeDef.Name = ign.Name
+
+ ignFile, err := ign.createFile()
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ // Remove the tmp ignition file
+ if err = os.Remove(ignFile); err != nil {
+ log.Printf("Error while removing tmp Ignition file: %s", err)
+ }
+ }()
+
+ img, err := newImage(ignFile)
+ if err != nil {
+ return "", err
+ }
+
+ size, err := img.Size()
+ if err != nil {
+ return "", err
+ }
+
+ volumeDef.Capacity.Unit = "B"
+ volumeDef.Capacity.Amount = size
+ volumeDef.Target.Format.Type = "raw"
+
+ volumeDefXml, err := xml.Marshal(volumeDef)
+ if err != nil {
+ return "", fmt.Errorf("Error serializing libvirt volume: %s", err)
+ }
+
+ // create the volume
+ volume, err := pool.StorageVolCreateXML(string(volumeDefXml), 0)
+ if err != nil {
+ return "", fmt.Errorf("Error creating libvirt volume for Ignition %s: %s", ign.Name, err)
+ }
+ defer volume.Free()
+
+ // upload ignition file
+ stream, err := libvirt.NewVirStream(virConn, 0)
+ if err != nil {
+ return "", err
+ }
+ defer stream.Close()
+
+ volume.Upload(stream, 0, uint64(volumeDef.Capacity.Amount), 0)
+ err = img.WriteToStream(stream)
+ if err != nil {
+ return "", err
+ }
+
+ key, err := volume.GetKey()
+ if err != nil {
+ return "", fmt.Errorf("Error retrieving volume key: %s", err)
+ }
+
+ return ign.buildTerraformKey(key), nil
+}
+
+// create a unique ID for terraform use
+// The ID is made by the volume ID (the internal one used by libvirt)
+// joined by the ";" with a UUID
+func (ign *defIgnition) buildTerraformKey(volumeKey string) string {
+ return fmt.Sprintf("%s;%s", volumeKey, uuid.TimeOrderedUUID())
+}
+
+func getIgnitionVolumeKeyFromTerraformID(id string) (string, error) {
+ s := strings.SplitN(id, ";", 2)
+ if len(s) != 2 {
+ return "", fmt.Errorf("%s is not a valid key", id)
+ }
+ return s[0], nil
+}
+
+
+// Dumps the Ignition object - either generated by Terraform or supplied as a file -
+// to a temporary ignition file
+func (ign *defIgnition) createFile() (string, error) {
+ log.Print("Creating Ignition temporary file")
+ tempFile, err := ioutil.TempFile("", ign.Name)
+ if err != nil {
+ return "", fmt.Errorf("Cannot create tmp file for Ignition: %s",
+ err)
+ }
+ defer tempFile.Close()
+
+ var file bool
+ file = true
+ if _, err := os.Stat(ign.Content); err != nil {
+ var js map[string]interface{}
+ if err_conf := json.Unmarshal([]byte(ign.Content), &js); err_conf != nil {
+ return "", fmt.Errorf("coreos_ignition 'content' is neither a file "+
+ "nor a valid json object %s", ign.Content)
+ }
+ file = false
+ }
+
+ if !file {
+ if _, err := tempFile.WriteString(ign.Content); err != nil {
+ return "", fmt.Errorf("Cannot write Ignition object to temporary "+
+ "ignition file")
+ }
+ } else if file {
+ ignFile, err := os.Open(ign.Content)
+ if err != nil {
+ return "", fmt.Errorf("Error opening supplied Ignition file %s", ign.Content)
+ }
+ defer ignFile.Close()
+ _, err = io.Copy(tempFile, ignFile)
+ if err != nil {
+ return "", fmt.Errorf("Error copying supplied Igition file to temporary file: %s", ign.Content)
+ }
+ }
+ return tempFile.Name(), nil
+}
+
+// Creates a new defIgnition object from provided id
+func newIgnitionDefFromRemoteVol(virConn *libvirt.VirConnection, id string) (defIgnition, error) {
+ ign := defIgnition{}
+
+ key, err := getIgnitionVolumeKeyFromTerraformID(id)
+ if err != nil {
+ return ign, err
+ }
+
+ volume, err := virConn.LookupStorageVolByKey(key)
+ if err != nil {
+ return ign, fmt.Errorf("Can't retrieve volume %s", key)
+ }
+ defer volume.Free()
+
+ ign.Name, err = volume.GetName()
+ if err != nil {
+ return ign, fmt.Errorf("Error retrieving volume name: %s", err)
+ }
+
+ volPool, err := volume.LookupPoolByVolume()
+ if err != nil {
+ return ign, fmt.Errorf("Error retrieving pool for volume: %s", err)
+ }
+ defer volPool.Free()
+
+ ign.PoolName, err = volPool.GetName()
+ if err != nil {
+ return ign, fmt.Errorf("Error retrieving pool name: %s", err)
+ }
+
+
+ return ign, nil
+}
+