diff options
author | Dean Smith <dean@zelotus.com> | 2017-02-17 20:59:06 +0000 |
---|---|---|
committer | Dean Smith <dean@zelotus.com> | 2017-02-17 20:59:06 +0000 |
commit | 1d2eda779cecd57960586b3fb630c767fabc1adf (patch) | |
tree | 60173460142e2db3a847b5a8b8074d05c8ce62f8 | |
parent | e60609f3fe5766885a2edb36b2e52e1dd58ffa6d (diff) | |
parent | aeaee868666a6017b9bd0539c618711c4d15ea0d (diff) | |
download | terraform-provider-libvirt-1d2eda779cecd57960586b3fb630c767fabc1adf.tar terraform-provider-libvirt-1d2eda779cecd57960586b3fb630c767fabc1adf.tar.gz |
Merge remote-tracking branch 'origin' into bridge_fix
-rw-r--r-- | docs/providers/libvirt/r/domain.html.markdown | 75 | ||||
-rw-r--r-- | docs/providers/libvirt/r/network.markdown | 38 | ||||
-rw-r--r-- | libvirt/domain_def.go | 30 | ||||
-rw-r--r-- | libvirt/network_def.go | 19 | ||||
-rw-r--r-- | libvirt/provider_test.go | 2 | ||||
-rw-r--r-- | libvirt/resource_libvirt_domain.go | 118 | ||||
-rw-r--r-- | libvirt/resource_libvirt_domain_console.go | 34 | ||||
-rw-r--r-- | libvirt/resource_libvirt_domain_test.go | 74 | ||||
-rw-r--r-- | libvirt/resource_libvirt_network.go | 47 | ||||
-rw-r--r-- | vendor.yml | 2 |
10 files changed, 428 insertions, 11 deletions
diff --git a/docs/providers/libvirt/r/domain.html.markdown b/docs/providers/libvirt/r/domain.html.markdown index ae08872d..b9a5500e 100644 --- a/docs/providers/libvirt/r/domain.html.markdown +++ b/docs/providers/libvirt/r/domain.html.markdown @@ -41,6 +41,35 @@ The following arguments are supported: cloud-init won't cause the domain to be recreated, however the change will have effect on the next reboot. +The following extra argument is provided for CoreOS images: + +* `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. + Some extra arguments are also provided for using UEFI images: * `firmware` - (Optional) The UEFI rom images for exercising UEFI secure boot in a qemu @@ -145,6 +174,7 @@ resource "libvirt_domain" "my_machine" { } ``` + The `network_interface` specifies a network interface that can be connected either to a virtual network (the preferred method on hosts with dynamic / wireless networking configs) or directly to a LAN. @@ -197,6 +227,51 @@ resource "libvirt_domain" "my-domain" { must be installed and running inside of the domain in order to discover the IP addresses of all the network interfaces attached to a LAN. +The optional `graphics` block allows you to override the default graphics settings. The +block supports: + +* `type` - the type of graphics emulation (default is "spice") +* `autoport` - defaults to "yes" +* `listen_type` - "listen type", defaults to "none" + +On occasion we have found it necessary to set a `type` of "vnc" and a `listen_type` of "address" +with certain builds of QEMU. + +The `graphics` block will look as follows: +``` +resource "libvirt_domain" "my_machine" { + ... + graphics { + type = "vnc" + listen_type = "address" + } +} +``` + +The optional `console` block allows you to define a console for the domain. The block +looks as follows: +``` +resource "libvirt_domain" "my_machine" { + ... + console { + type = "pty" + target_port = "0" + target_type = <"serial" or "virtio"> + source_path = "/dev/pts/4" + } +} +``` + +Note the following: +* You can repeat the `console` block to create more than one console, in the same way +that you can repeat `disk` blocks (see above) +* The `target_type` is optional for the first console +* All subsequent `console` blocks must specify a `target_type` of `virtio` +* The `source_path` is optional for all consoles + +See [libvirt Domain XML Console element](https://libvirt.org/formatdomain.html#elementsConsole) +for more information. + ## Attributes Reference * `id` - a unique identifier for the resource diff --git a/docs/providers/libvirt/r/network.markdown b/docs/providers/libvirt/r/network.markdown index 6b784df2..c27bdbcc 100644 --- a/docs/providers/libvirt/r/network.markdown +++ b/docs/providers/libvirt/r/network.markdown @@ -31,6 +31,13 @@ resource "libvirt_network" "network1" { # which will be used to construct the virtual network. # (only necessary in "bridge" mode) # bridge = "br7" + + # (Optional) one or more DNS forwarder entries. One or both of + # "address" and "domain" must be specified. The format is: + # dns_forwarder { + # address = "my address" + # domain = "my domain" + # } } ``` @@ -63,5 +70,36 @@ The following arguments are supported: * `bridge` - (Optional) The bridge device defines the name of a bridge device which will be used to construct the virtual network (when not provided, it will be automatically obtained by libvirt in `none`, `nat` and `route` modes). +* `dns_forwarder` - (Optional) a DNS forwarder entry block. You can have + one or mode of these blocks in your network definition. You must specify one or + both of `address` and `domain`. You can use either of the forms below to + specify dns_forwarders: + +``` +resource "libvirt_network" "my_network" { + ... + dns_forwarder { + address = "my address" + } + dns_forwarder { + address = "my address 1" + domain = "my domain" + } +} +``` +``` +resource "libvirt_network" "my_network" { + ... + dns_forwarder = [ + { + address = "my address" + }, + { + address = "my address 1" + domain = "my domain + } + ] +} +``` diff --git a/libvirt/domain_def.go b/libvirt/domain_def.go index 953fcdb7..46dadaaf 100644 --- a/libvirt/domain_def.go +++ b/libvirt/domain_def.go @@ -8,6 +8,7 @@ type defDomain struct { XMLName xml.Name `xml:"domain"` Name string `xml:"name"` Type string `xml:"type,attr"` + Xmlns string `xml:"xmlns:qemu,attr"` Os defOs `xml:"os"` Memory defMemory `xml:"memory"` VCpu defVCpu `xml:"vcpu"` @@ -23,7 +24,8 @@ type defDomain struct { Devices struct { Disks []defDisk `xml:"disk"` NetworkInterfaces []defNetworkInterface `xml:"interface"` - Graphics struct { + Console []defConsole `xml:"console"` + Graphics struct { Type string `xml:"type,attr"` Autoport string `xml:"autoport,attr"` Listen struct { @@ -48,11 +50,16 @@ type defDomain struct { } `xml:"backend"` } `xml:"rng"` } `xml:"devices"` + CmdLine struct { + XMLName xml.Name `xml:"qemu:commandline"` + Cmd []defCmd `xml:"qemu:arg"` + } } 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 { @@ -77,6 +84,10 @@ type defVCpu struct { Amount int `xml:",chardata"` } +type defCmd struct { + Value string `xml:"value,attr"` +} + type defLoader struct { ReadOnly string `xml:"readonly,attr,omitempty"` Type string `xml:"type,attr,omitempty"` @@ -88,12 +99,24 @@ type defNvRam struct { File string `xml:",chardata"` } +type defConsole struct { + Type string `xml:"type,attr"` + Source struct { + Path string `xml:"path,attr,omitempty"` + } `xml:"source"` + Target struct { + Type string `xml:"type,attr,omitempty"` + Port string `xml:"port,attr"` + } `xml:"target"` +} + // Creates a domain definition with the defaults // the provider uses func newDomainDef() defDomain { // libvirt domain definition domainDef := defDomain{} domainDef.Type = "kvm" + domainDef.Xmlns = "" domainDef.Os = defOs{} domainDef.Os.Type = defOsType{} @@ -121,3 +144,4 @@ func newDomainDef() defDomain { return domainDef } + diff --git a/libvirt/network_def.go b/libvirt/network_def.go index 23217600..86f6dc6c 100644 --- a/libvirt/network_def.go +++ b/libvirt/network_def.go @@ -69,13 +69,18 @@ type defNetworkForward struct { } type defNetworkDns struct { - Host []*struct { - Ip string `xml:"ip,attr"` - HostName []string `xml:"hostname"` - } `xml:"host,omitempty"` - Forwarder []*struct { - Address string `xml:"addr,attr"` - } `xml:"forwarder,omitempty"` + Host []*defDnsHost `xml:"host,omitempty"` + Forwarder []*defDnsForwarder `xml:"forwarder,omitempty"` +} + +type defDnsHost struct { + Ip string `xml:"ip,attr"` + HostName []string `xml:"hostname"` +} + +type defDnsForwarder struct { + Domain string `xml:"domain,attr,omitempty"` + Address string `xml:"addr,attr,omitempty"` } // network definition in XML, compatible with what libvirt expects diff --git a/libvirt/provider_test.go b/libvirt/provider_test.go index 662b1185..c130f86b 100644 --- a/libvirt/provider_test.go +++ b/libvirt/provider_test.go @@ -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 @@ -15,6 +16,7 @@ func init() { testAccProvider = Provider().(*schema.Provider) testAccProviders = map[string]terraform.ResourceProvider{ "libvirt": testAccProvider, + "ignition": ignition.Provider(), } } diff --git a/libvirt/resource_libvirt_domain.go b/libvirt/resource_libvirt_domain.go index bfbf235d..00bbaf19 100644 --- a/libvirt/resource_libvirt_domain.go +++ b/libvirt/resource_libvirt_domain.go @@ -1,6 +1,7 @@ package libvirt import ( + "encoding/json" "encoding/xml" "fmt" "log" @@ -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" @@ -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, @@ -73,6 +81,13 @@ func resourceLibvirtDomain() *schema.Resource { Optional: true, ForceNew: false, }, + "coreos_ignition": &schema.Schema{ + Type: schema.TypeString, + Required: false, + Optional: true, + ForceNew: false, + Default: "", + }, "disk": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -90,6 +105,19 @@ func resourceLibvirtDomain() *schema.Resource { Schema: networkInterfaceCommonSchema(), }, }, + "graphics": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Required: false, + }, + "console": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Required: false, + Elem: &schema.Resource{ + Schema: consoleSchema(), + }, + }, }, } } @@ -115,6 +143,7 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error } domainDef := newDomainDef() + if name, ok := d.GetOk("name"); ok { domainDef.Name = name.(string) } @@ -123,6 +152,58 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error domainDef.Metadata.TerraformLibvirt.Xml = metadata.(string) } + if ignition, ok := d.GetOk("coreos_ignition"); ok { + 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 + 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 + domainDef.Xmlns = "http://libvirt.org/schemas/domain/qemu/1.0" + } + + if graphics, ok := d.GetOk("graphics"); ok { + graphics_map := graphics.(map[string]interface{}) + if graphics_type, ok := graphics_map["type"]; ok { + domainDef.Devices.Graphics.Type = graphics_type.(string) + } + if autoport, ok := graphics_map["autoport"]; ok { + domainDef.Devices.Graphics.Type = autoport.(string) + } + if listen_type, ok := graphics_map["listen_type"]; ok { + domainDef.Devices.Graphics.Listen.Type = listen_type.(string) + } + } + if firmware, ok := d.GetOk("firmware"); ok { firmwareFile := firmware.(string) if _, err := os.Stat(firmwareFile); os.IsNotExist(err) { @@ -148,6 +229,24 @@ func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error domainDef.Memory.Amount = d.Get("memory").(int) domainDef.VCpu.Amount = d.Get("vcpu").(int) + if consoleCount, ok := d.GetOk("console.#"); ok { + var consoles []defConsole + for i := 0; i < consoleCount.(int); i++ { + console := defConsole{} + consolePrefix := fmt.Sprintf("console.%d", i) + console.Type = d.Get(consolePrefix + ".type").(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) + } + consoles = append(consoles, console) + } + domainDef.Devices.Console = consoles + } + disksCount := d.Get("disk.#").(int) var disks []defDisk for i := 0; i < disksCount; i++ { @@ -693,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) diff --git a/libvirt/resource_libvirt_domain_console.go b/libvirt/resource_libvirt_domain_console.go new file mode 100644 index 00000000..eadb4483 --- /dev/null +++ b/libvirt/resource_libvirt_domain_console.go @@ -0,0 +1,34 @@ +package libvirt + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +func consoleSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Optional: false, + Required: true, + ForceNew: true, + }, + "source_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Required: false, + ForceNew: true, + }, + "target_port": &schema.Schema{ + Type: schema.TypeString, + Optional: false, + Required: true, + ForceNew: true, + }, + "target_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Required: false, + ForceNew: true, + }, + } +} diff --git a/libvirt/resource_libvirt_domain_test.go b/libvirt/resource_libvirt_domain_test.go index 73feefe5..6749283c 100644 --- a/libvirt/resource_libvirt_domain_test.go +++ b/libvirt/resource_libvirt_domain_test.go @@ -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" ) @@ -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 @@ -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 + } +} diff --git a/libvirt/resource_libvirt_network.go b/libvirt/resource_libvirt_network.go index 6d4e54b8..8085673f 100644 --- a/libvirt/resource_libvirt_network.go +++ b/libvirt/resource_libvirt_network.go @@ -78,6 +78,32 @@ func resourceLibvirtNetwork() *schema.Resource { Default: true, ForceNew: false, }, + "dns_forwarder": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Required: false, + ForceNew: true, + Elem: &schema.Resource{ + Schema: dnsForwarderSchema(), + }, + }, + }, + } +} + +func dnsForwarderSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Required: false, + ForceNew: true, + }, + "domain": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Required: false, + ForceNew: true, }, } } @@ -205,6 +231,27 @@ func resourceLibvirtNetworkCreate(d *schema.ResourceData, meta interface{}) erro } networkDef.Ips = ipsPtrsLst } + + if dns_forward_count, ok := d.GetOk("dns_forwarder.#"); ok { + var dns defNetworkDns + for i := 0; i < dns_forward_count.(int); i++ { + forward := defDnsForwarder{} + forwardPrefix := fmt.Sprintf("dns_forwarder.%d", i) + if address, ok := d.GetOk(forwardPrefix + ".address"); ok { + ip := net.ParseIP(address.(string)) + if ip == nil { + return fmt.Errorf("Could not parse address '%s'", address) + } + forward.Address = ip.String() + } + if domain, ok := d.GetOk(forwardPrefix + ".domain"); ok { + forward.Domain = domain.(string) + } + dns.Forwarder = append(dns.Forwarder, &forward) + } + networkDef.Dns = &dns + } + } else if networkDef.Forward.Mode == netModeBridge { if bridgeName == "" { return fmt.Errorf("'bridge' must be provided when using the bridged network mode") @@ -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 |