From eb9546c9aff31ffaa4a6deb262cd9ddd38040783 Mon Sep 17 00:00:00 2001 From: ston1th Date: Sun, 1 Nov 2020 20:09:16 +0100 Subject: [PATCH] added clout init image docs --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ helper.go | 23 +++++++++++++++++++++++ server.go | 6 ------ snippet.go | 38 ++++++++++---------------------------- 4 files changed, 84 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 899bf47..7812340 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,54 @@ PVE-Go is a go library for the Proxmox VE API. The design is based on the Hetzner Clound API implementation: https://github.com/hetznercloud/hcloud-go + +## Cloud-Init Image + +How to prepare a cloud-init image. + +Download the latest version of the base image to one of your proxmox nodes: + +``` +curl -sSL https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img >/tmp/bionic-server-cloudimg-amd64.img +``` + +Create a cloud-init config to bootstrap the template: +``` +cat <<'EOF'> /var/lib/vz/snippets/9000_bootstrap +#cloud-config +package_update: true +package_upgrade: true +ntp: + enabled: true + servers: ["pool.ntp.org"] +ssh_deletekeys: true +ssh_genkeytypes: ["ed25519", "rsa"] +runcmd: +- "apt-get -y clean" +- "apt-get -y autoremove --purge" +- "bash -c 'rm /home/ubuntu/.ssh/authorized_keys; exit 0'" +- "bash -c 'find /var/log -type f | while read f; do echo -ne >$f; done; exit 0'" +- "bash -c 'rm -rf /var/lib/cloud/*; exit 0'" +power_state: + mode: poweroff +EOF +``` + +Create the template VM: +``` +qm destroy 9000 +qm create 9000 --memory 2048 --net0 virtio,bridge=vmbr0 +qm importdisk 9000 /tmp/bionic-server-cloudimg-amd64.img local +qm set 9000 --scsihw virtio-scsi-pci --scsi0 local:9000/vm-9000-disk-0.raw +qm set 9000 --ide0 local:cloudinit +qm set 9000 --boot c --bootdisk scsi0 +qm set 9000 --serial0 socket --vga serial0 +qm set 9000 --cicustom "user=local:snippets/9000_bootstrap" +qm set 9000 --ipconfig0 ip=dhcp,ip6=dhcp + +qm start 9000 +sleep 10;while :; do qm status 9000|grep -q stopped && break; done +qm set 9000 --cicustom "" +qm set 9000 --ipconfig0 "" +qm template 9000 +``` diff --git a/helper.go b/helper.go index 878c82d..01628ed 100644 --- a/helper.go +++ b/helper.go @@ -3,6 +3,7 @@ package pve import ( + "bytes" "encoding/json" "errors" "io" @@ -43,3 +44,25 @@ func (b httpbody) Reader() io.Reader { } return strings.NewReader(data.Encode()) } + +type unreadBuffer struct { + b *bytes.Buffer + save []byte +} + +func newUnreadBuffer() (ub *unreadBuffer, buf *bytes.Buffer) { + buf = new(bytes.Buffer) + ub = &unreadBuffer{b: buf} + return +} + +func (u *unreadBuffer) Save() { + u.save = u.b.Bytes() +} + +func (u *unreadBuffer) Reset(b **bytes.Buffer) { + if u.save != nil && b != nil { + u.b = bytes.NewBuffer(u.save) + *b = u.b + } +} diff --git a/server.go b/server.go index d925d9d..28f4e1d 100644 --- a/server.go +++ b/server.go @@ -47,7 +47,6 @@ func (s *Server) body() (b httpbody) { if s.UserData != nil || s.cicustom { b["cicustom"] = s.UserDataSnippet(s.UserDataStorage) } - // TODO userdata, err := s.UserData.String() for i, n := range s.NetDevices { if n == nil { b["net"+strconv.Itoa(i)] = "" @@ -155,11 +154,6 @@ func (sc serverConfig) Server(ref *ServerRef) (s *Server, err error) { if _, ok := sc[k].(string); ok { s.cicustom = true } - // TODO - // case "ciuserdata": - // if v, ok := sc[k].(string); ok { - // s.UserData, err = parseUserData(v) - // } case "memory": if v, ok := sc[k].(float64); ok { s.Resources.Memory = Memory(uint64(v) / 1024) diff --git a/snippet.go b/snippet.go index be30c47..abb9a01 100644 --- a/snippet.go +++ b/snippet.go @@ -3,7 +3,6 @@ package pve import ( - "bytes" "context" "errors" "fmt" @@ -18,16 +17,6 @@ var ( ErrNoSnippetName = errors.New("missing snippet name") ) -type CopyBuffer struct { - *bytes.Buffer -} - -func (c *CopyBuffer) Copy() *bytes.Buffer { - buf := make([]byte, c.Len()) - copy(buf, c.Bytes()) - return bytes.NewBuffer(buf) -} - type SnippetClient struct { client *Client } @@ -45,38 +34,32 @@ func (c *SnippetClient) Create(ctx context.Context, node, storage, name string, err = ErrNoSnippetName return } - var b bytes.Buffer - w := multipart.NewWriter(&b) - err = w.WriteField("content", "snippets") + ub, b := newUnreadBuffer() + m := multipart.NewWriter(b) + err = m.WriteField("content", "snippets") if err != nil { return } - fw, err := w.CreateFormFile("filename", name) + fw, err := m.CreateFormFile("filename", name) if err != nil { return } if _, err = fw.Write(data); err != nil { return } - w.Close() - cb := CopyBuffer{&b} - ct := w.FormDataContentType() + m.Close() + ub.Save() + ct := m.FormDataContentType() var taskid string var req *http.Request var resp *http.Response for i := 0; i < 3; i++ { - buf := cb.Copy() - req, err = c.client.NewRequest(ctx, "POST", fmt.Sprintf("/nodes/%s/storage/%s/upload", node, storage), buf) + req, err = c.client.NewRequest(ctx, "POST", fmt.Sprintf("/nodes/%s/storage/%s/upload", node, storage), b) req.Header.Set("Content-Type", ct) resp, err = c.client.Do(req, &taskid) - if err != nil { + if err != nil || resp.StatusCode != http.StatusOK { + ub.Reset(&b) time.Sleep(time.Millisecond * 500) - // TODO log error - continue - } - if resp.StatusCode != http.StatusOK { - time.Sleep(time.Millisecond * 500) - // TODO log error continue } break @@ -90,7 +73,6 @@ func SnippetVolume(storage, name string) string { } func (c *SnippetClient) Delete(ctx context.Context, node, storage, name string) (t *Task, err error) { - //volume := fmt.Sprintf("/%s:snippets/%s", storage, name) volume := SnippetVolume(storage, name) req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/nodes/%s/storage/%s/content/%s", node, storage, volume), nil) if err != nil {