Skip to content

Commit

Permalink
initial cloud init resource
Browse files Browse the repository at this point in the history
  • Loading branch information
lsjostro committed Apr 19, 2023
1 parent df48e1a commit 8e875af
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/hashicorp/terraform-registry-address v0.2.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/kdomanski/iso9660 v0.3.3
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/kdomanski/iso9660 v0.3.3 h1:cNwM9L2L1Hzc5hZWGy6fPJ92UyWDccaY69DmEPlfDNY=
github.com/kdomanski/iso9660 v0.3.3/go.mod h1:K+UlIGxKgtrdAWyoigPnFbeQLVs/Xudz4iztWFThBwo=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down
9 changes: 5 additions & 4 deletions proxmox/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,11 @@ func Provider() *schema.Provider {
},

ResourcesMap: map[string]*schema.Resource{
"proxmox_vm_qemu": resourceVmQemu(),
"proxmox_lxc": resourceLxc(),
"proxmox_lxc_disk": resourceLxcDisk(),
"proxmox_pool": resourcePool(),
"proxmox_vm_qemu": resourceVmQemu(),
"proxmox_lxc": resourceLxc(),
"proxmox_lxc_disk": resourceLxcDisk(),
"proxmox_pool": resourcePool(),
"proxmox_cloud_init": resourceCloudInitDisk(),
// TODO - proxmox_storage_iso
// TODO - proxmox_bridge
// TODO - proxmox_vm_qemu_template
Expand Down
191 changes: 191 additions & 0 deletions proxmox/resource_cloud_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package proxmox

import (
"bytes"
"context"
"crypto/sha256"
"fmt"
"io"
"strings"

"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/kdomanski/iso9660"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
isoContentType = "iso"
)

func resourceCloudInitDisk() *schema.Resource {
return &schema.Resource{
CreateContext: resourceCloudInitDiskCreate,
ReadContext: resourceCloudInitDiskRead,
DeleteContext: resourceCloudInitDiskDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"pve_node": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"storage": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"user_data": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"meta_data": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"network_config": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"content_sha256": {
Type: schema.TypeString,
Computed: true,
ForceNew: true,
},
"size": {
Type: schema.TypeString,
Computed: true,
ForceNew: true,
},
},
}
}

func createCloudInitISO(metaData, userData, networkConfig string) (io.Reader, string, error) {
isoWriter, err := iso9660.NewWriter()
if err != nil {
return nil, "", err
}
defer isoWriter.Cleanup()

err = isoWriter.AddFile(strings.NewReader(metaData), "meta-data")
if err != nil {
return nil, "", err
}

err = isoWriter.AddFile(strings.NewReader(userData), "user-data")
if err != nil {
return nil, "", err
}

err = isoWriter.AddFile(strings.NewReader(networkConfig), "network-config")
if err != nil {
return nil, "", err
}

var b bytes.Buffer
err = isoWriter.WriteTo(&b, "cidata")
if err != nil {
return nil, "", err
}

// Calculate the sha256 of the content
hasher := sha256.New()
var content bytes.Buffer
content.WriteString(metaData)
content.WriteString(userData)
content.WriteString(networkConfig)
if _, err := io.Copy(hasher, &content); err != nil {
return nil, "", err
}
contentSum := fmt.Sprintf("%x", hasher.Sum(nil))

return bytes.NewReader(b.Bytes()), contentSum, nil
}

func resourceCloudInitDiskCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
pconf := m.(*providerConfiguration)
client := pconf.Client

r, sum, err := createCloudInitISO(d.Get("meta_data").(string), d.Get("user_data").(string), d.Get("network_config").(string))
if err != nil {
return diag.FromErr(err)
}

fileName := fmt.Sprintf("%s.iso", d.Get("name").(string))
err = client.Upload(d.Get("pve_node").(string), d.Get("storage").(string), isoContentType, fileName, r)
if err != nil {
return diag.FromErr(err)
}

// The volume ID is the storage name and the file name
volId := fmt.Sprintf("%s:%s/%s", d.Get("storage").(string), isoContentType, fileName)
d.SetId(volId)
d.Set("content_sha256", sum)

return resourceCloudInitDiskRead(ctx, d, m)
}

func resourceCloudInitDiskRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
pconf := m.(*providerConfiguration)
client := pconf.Client

var isoFound bool
pveNode := d.Get("pve_node").(string)
vmRef := &proxmox.VmRef{}
vmRef.SetNode(pveNode)
vmRef.SetVmType("qemu")
storageContent, err := client.GetStorageContent(vmRef, d.Get("storage").(string))
if err != nil {
return diag.FromErr(err)
}
contents := storageContent["data"].([]interface{})
for c := range contents {
storageContentMap := contents[c].(map[string]interface{})
if storageContentMap["volid"].(string) == d.Id() {
size := storageContentMap["size"].(float64)
d.Set("size", ByteCountIEC(int64(size)))
isoFound = true
break
}
}

if !isoFound {
// ISO not found so we (re)create it
d.SetId("")
return nil
}

// ISO can't be downloaded so we need to compare the content sha256
_, sum, err := createCloudInitISO(d.Get("meta_data").(string), d.Get("user_data").(string), d.Get("network_config").(string))
if err != nil {
return diag.FromErr(err)
}
d.Set("content_sha256", sum)
// If the sha256 doesn't match the state we need to recreate the resource
if d.HasChange("content_sha256") {
d.SetId("")
}
return nil
}

func resourceCloudInitDiskDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
pconf := m.(*providerConfiguration)
client := pconf.Client

storage := strings.SplitN(d.Id(), ":", 2)[0]
isoURL := fmt.Sprintf("/nodes/%s/storage/%s/content/%s", d.Get("pve_node").(string), storage, d.Id())
err := client.Delete(isoURL)
if err != nil {
return diag.FromErr(err)
}
return nil
}
14 changes: 14 additions & 0 deletions proxmox/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,3 +495,17 @@ func permissions_check(s1 []string, s2 []string) []string {
}
return diff
}

func ByteCountIEC(b int64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%dB", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%0.f%c",
float64(b)/float64(div), "KMGTPE"[exp])
}

0 comments on commit 8e875af

Please sign in to comment.