From f130f9848e61924bded600602a91822cf76e0959 Mon Sep 17 00:00:00 2001 From: Eamonn O'Toole Date: Thu, 2 Mar 2017 11:00:59 +0000 Subject: 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. --- libvirt/coreos_ignition_def.go | 202 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 libvirt/coreos_ignition_def.go (limited to 'libvirt/coreos_ignition_def.go') 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 +} + -- cgit v1.2.3