Compare commits
No commits in common. "testing" and "master" have entirely different histories.
21 changed files with 206 additions and 1739 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
cmd
|
|
||||||
57
MINIMAL.md
57
MINIMAL.md
|
|
@ -1,57 +0,0 @@
|
||||||
# Minimal Cloud Images
|
|
||||||
|
|
||||||
## Cloud-Init Image
|
|
||||||
|
|
||||||
How to prepare a cloud-init image.
|
|
||||||
|
|
||||||
### Ubuntu 20.04
|
|
||||||
|
|
||||||
Download the latest version of the base image to one of your proxmox nodes:
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -sSL https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img >/tmp/ubuntu-20.04-minimal-cloudimg-amd64.img
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a cloud-init config to bootstrap the template:
|
|
||||||
```
|
|
||||||
cat <<'EOF'> /var/lib/vz/snippets/9003_bootstrap
|
|
||||||
#cloud-config
|
|
||||||
package_update: true
|
|
||||||
package_upgrade: true
|
|
||||||
ntp:
|
|
||||||
enabled: true
|
|
||||||
servers: ["pool.ntp.org"]
|
|
||||||
ntp_client: systemd-timesyncd
|
|
||||||
ssh_deletekeys: true
|
|
||||||
ssh_genkeytypes: ["ed25519", "rsa"]
|
|
||||||
bootcmd:
|
|
||||||
- |
|
|
||||||
sed -i 's/ENABLED=1/ENABLED=0/' /etc/default/motd-news
|
|
||||||
systemctl disable motd-news.service motd-news.timer --now
|
|
||||||
runcmd:
|
|
||||||
- |
|
|
||||||
curl -sSL https://git.giftfish.de/ston1th/cleanup/raw/branch/master/ubuntu_2004_min.sh >/tmp/cleanup.sh
|
|
||||||
[ "$(sha256sum /tmp/cleanup.sh|cut -d" " -f1)" = "9fbd939eb8d1ef6157a61bb984a5045339ce361596b8a99a8847280cc785b726" ] && sh /tmp/cleanup.sh || echo "error: cleanup script hash does not match" >&2
|
|
||||||
power_state:
|
|
||||||
mode: poweroff
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Create the template VM:
|
|
||||||
```
|
|
||||||
qm stop 9003; qm destroy 9003
|
|
||||||
qm create 9003 --name ubuntu-2004-min --memory 2048 --net0 virtio,bridge=vmbr0
|
|
||||||
qm importdisk 9003 /tmp/ubuntu-20.04-minimal-cloudimg-amd64.img local
|
|
||||||
qm set 9003 --scsihw virtio-scsi-pci --scsi0 local:9003/vm-9003-disk-0.raw
|
|
||||||
qm set 9003 --ide0 local:cloudinit
|
|
||||||
qm set 9003 --boot c --bootdisk scsi0
|
|
||||||
qm set 9003 --serial0 socket --vga serial0
|
|
||||||
qm set 9003 --cicustom "user=local:snippets/9003_bootstrap"
|
|
||||||
qm set 9003 --ipconfig0 ip=dhcp,ip6=dhcp
|
|
||||||
|
|
||||||
qm start 9003
|
|
||||||
sleep 10;while :; do qm status 9003|grep -q stopped && break; done
|
|
||||||
qm set 9003 --cicustom ""
|
|
||||||
qm set 9003 --ipconfig0 ""
|
|
||||||
qm template 9003
|
|
||||||
```
|
|
||||||
230
README.md
230
README.md
|
|
@ -1,233 +1,5 @@
|
||||||
# pve-go
|
# pve-go
|
||||||
|
|
||||||
PVE-Go is a go library for the Proxmox VE API.
|
PVE-Go is a go library for the Porxmox VE API.
|
||||||
|
|
||||||
The design is based on the Hetzner Clound API implementation: https://github.com/hetznercloud/hcloud-go
|
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.
|
|
||||||
|
|
||||||
### Ubuntu 20.04
|
|
||||||
|
|
||||||
Download the latest version of the base image to one of your proxmox nodes:
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -sSL https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img >/tmp/focal-server-cloudimg-amd64.img
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a cloud-init config to bootstrap the template:
|
|
||||||
```
|
|
||||||
cat <<'EOF'> /var/lib/vz/snippets/9002_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/* /var/tmp/* /tmp/* /tmp/.*-unix; exit 0'"
|
|
||||||
power_state:
|
|
||||||
mode: poweroff
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Create the template VM:
|
|
||||||
```
|
|
||||||
qm stop 9002; qm destroy 9002
|
|
||||||
qm create 9002 --name ubuntu-2004 --memory 2048 --net0 virtio,bridge=vmbr0
|
|
||||||
qm importdisk 9002 /tmp/focal-server-cloudimg-amd64.img local
|
|
||||||
qm set 9002 --scsihw virtio-scsi-pci --scsi0 local:9002/vm-9002-disk-0.raw
|
|
||||||
qm set 9002 --ide0 local:cloudinit
|
|
||||||
qm set 9002 --boot c --bootdisk scsi0
|
|
||||||
qm set 9002 --serial0 socket --vga serial0
|
|
||||||
qm set 9002 --cicustom "user=local:snippets/9002_bootstrap"
|
|
||||||
qm set 9002 --ipconfig0 ip=dhcp,ip6=dhcp
|
|
||||||
|
|
||||||
qm start 9002
|
|
||||||
sleep 10;while :; do qm status 9002|grep -q stopped && break; done
|
|
||||||
qm set 9002 --cicustom ""
|
|
||||||
qm set 9002 --ipconfig0 ""
|
|
||||||
qm template 9002
|
|
||||||
```
|
|
||||||
|
|
||||||
#### More Image Cleanup
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir /etc/systemd/system/motd-news.service.d
|
|
||||||
cat <<EOF>/etc/systemd/system/motd-news.service.d/override.conf
|
|
||||||
[Service]
|
|
||||||
ExecStart=/bin/true
|
|
||||||
EOF
|
|
||||||
|
|
||||||
mkdir /etc/systemd/timesyncd.conf.d
|
|
||||||
cat <<EOF>/etc/systemd/timesyncd.conf.d/ntp.conf
|
|
||||||
[Time]
|
|
||||||
NTP=pool.ntp.org
|
|
||||||
EOF
|
|
||||||
|
|
||||||
apt -y purge alsa-topology-conf alsa-ucm-conf apport \
|
|
||||||
apport-symptoms at bash-completion bolt byobu eatmydata command-not-found \
|
|
||||||
eject fonts-ubuntu-console htop install-info landscape-common lxd-agent-loader \
|
|
||||||
motd-news-config nano ntfs-3g pastebinit plymouth plymouth-theme-ubuntu-text \
|
|
||||||
popularity-contest policykit-1 python3-apport os-prober snapd \
|
|
||||||
sound-theme-freedesktop ubuntu-advantage-tools ufw
|
|
||||||
|
|
||||||
# optional
|
|
||||||
# apt -y purge accountsservice multipath-tools packagekit udisks2 unattended-upgrades
|
|
||||||
|
|
||||||
apt -y autoremove
|
|
||||||
```
|
|
||||||
|
|
||||||
Even more cleanup:
|
|
||||||
|
|
||||||
```
|
|
||||||
apt -y purge lshw lsof ltrace man-db manpages mdadm mtr-tiny screen sosreport strace tcpdump tmux usbutils whiptail
|
|
||||||
apt -y autoremove
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ubuntu 18.04
|
|
||||||
|
|
||||||
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 stop 9000; qm destroy 9000
|
|
||||||
qm create 9000 --name ubuntu-1804 --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
|
|
||||||
```
|
|
||||||
|
|
||||||
#### More Image Cleanup
|
|
||||||
|
|
||||||
```
|
|
||||||
apt -y purge apport apport-symptoms bash-completion byobu htop landscape-common lxcfs lxd lxd-client motd-news-config nano ntfs-3g os-prober ufw
|
|
||||||
apt -y autoremove
|
|
||||||
```
|
|
||||||
|
|
||||||
Even more cleanup:
|
|
||||||
|
|
||||||
```
|
|
||||||
apt -y purge lshw lsof ltrace man-db manpages mdadm mtr-tiny screen sosreport strace tcpdump tmux usbutils whiptail
|
|
||||||
apt -y autoremove
|
|
||||||
```
|
|
||||||
|
|
||||||
## Minimal Cloud Image
|
|
||||||
|
|
||||||
### Ubuntu 20.04
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -sSL https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img >/tmp/focal-server-cloudimg-amd64.img
|
|
||||||
|
|
||||||
cat <<'EOF'> /var/lib/vz/snippets/9002_bootstrap
|
|
||||||
#cloud-config
|
|
||||||
package_update: true
|
|
||||||
package_upgrade: true
|
|
||||||
ssh_deletekeys: true
|
|
||||||
ssh_genkeytypes: ["ed25519", "rsa"]
|
|
||||||
runcmd:
|
|
||||||
- "curl -sSL https://git.giftfish.de/ston1th/cleanup/raw/branch/master/ubuntu_2004_full.sh >/tmp/cleanup.sh"
|
|
||||||
- |
|
|
||||||
bash -c '[ "$(sha256sum /tmp/cleanup.sh|cut -d" " -f1)" = "fe2827d03ebba6e058ed7b9e56116eef78a45606f2e5f2956b2b2f495f042724" ] && sh /tmp/cleanup.sh || echo "error: cleanup script hash does not match" >&2'
|
|
||||||
power_state:
|
|
||||||
mode: poweroff
|
|
||||||
EOF
|
|
||||||
|
|
||||||
qm stop 9002; qm destroy 9002
|
|
||||||
qm create 9002 --name ubuntu-2004 --memory 2048 --net0 virtio,bridge=vmbr0
|
|
||||||
qm importdisk 9002 /tmp/focal-server-cloudimg-amd64.img local
|
|
||||||
qm set 9002 --scsihw virtio-scsi-pci --scsi0 local:9002/vm-9002-disk-0.raw
|
|
||||||
qm set 9002 --ide0 local:cloudinit
|
|
||||||
qm set 9002 --boot c --bootdisk scsi0
|
|
||||||
qm set 9002 --serial0 socket --vga serial0
|
|
||||||
qm set 9002 --cicustom "user=local:snippets/9002_bootstrap"
|
|
||||||
qm set 9002 --ipconfig0 ip=dhcp,ip6=dhcp
|
|
||||||
|
|
||||||
qm start 9002
|
|
||||||
sleep 10;while :; do qm status 9002|grep -q stopped && break; done
|
|
||||||
qm set 9002 --cicustom ""
|
|
||||||
qm set 9002 --ipconfig0 ""
|
|
||||||
qm template 9002
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ubuntu 18.04
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -sSL https://cloud-images.ubuntu.com/minimal/releases/bionic/release/ubuntu-18.04-minimal-cloudimg-amd64.img >/tmp/ubuntu-18.04-minimal-cloudimg-amd64.img
|
|
||||||
|
|
||||||
cat <<'EOF'> /var/lib/vz/snippets/9001_bootstrap
|
|
||||||
#cloud-config
|
|
||||||
package_update: true
|
|
||||||
package_upgrade: true
|
|
||||||
ntp:
|
|
||||||
enabled: true
|
|
||||||
servers: ["pool.ntp.org"]
|
|
||||||
ssh_deletekeys: true
|
|
||||||
ssh_genkeytypes: ["ed25519", "rsa"]
|
|
||||||
runcmd:
|
|
||||||
- "curl -sSL https://git.giftfish.de/ston1th/cleanup/raw/branch/master/ubuntu_1804.sh >/tmp/cleanup.sh"
|
|
||||||
- |
|
|
||||||
bash -c '[ "$(sha256sum /tmp/cleanup.sh|cut -d" " -f1)" = "ecb2ae04cdd6b91fdc56a5db2015f401e474196b8eaaaf8e3aa5dc4b5e4c087d" ] && sh /tmp/cleanup.sh || echo "error: cleanup script hash does not match" >&2'
|
|
||||||
power_state:
|
|
||||||
mode: poweroff
|
|
||||||
EOF
|
|
||||||
|
|
||||||
qm stop 9001; qm destroy 9001
|
|
||||||
qm create 9001 --name ubuntu-1804-min --memory 2048 --net0 virtio,bridge=vmbr0
|
|
||||||
qm importdisk 9001 /tmp/ubuntu-18.04-minimal-cloudimg-amd64.img local
|
|
||||||
qm set 9001 --scsihw virtio-scsi-pci --scsi0 local:9001/vm-9001-disk-0.raw
|
|
||||||
qm set 9001 --ide0 local:cloudinit
|
|
||||||
qm set 9001 --boot c --bootdisk scsi0
|
|
||||||
qm set 9001 --serial0 socket --vga serial0
|
|
||||||
qm set 9001 --cicustom "user=local:snippets/9001_bootstrap"
|
|
||||||
qm set 9001 --ipconfig0 ip=dhcp,ip6=dhcp
|
|
||||||
|
|
||||||
qm start 9001
|
|
||||||
sleep 10;while :; do qm status 9001|grep -q stopped && break; done
|
|
||||||
qm set 9001 --cicustom ""
|
|
||||||
qm set 9001 --ipconfig0 ""
|
|
||||||
qm template 9001
|
|
||||||
```
|
|
||||||
|
|
|
||||||
1
TODO
1
TODO
|
|
@ -1 +0,0 @@
|
||||||
remove 18.04 fat cloud images
|
|
||||||
138
client.go
138
client.go
|
|
@ -9,11 +9,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -36,22 +35,21 @@ func ExponentialBackoff(b float64, d time.Duration) BackoffFunc {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
mu sync.Mutex
|
sync.Mutex
|
||||||
endpoints []string
|
endpoint string
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
session session
|
session session
|
||||||
//pollInterval time.Duration
|
//pollInterval time.Duration
|
||||||
//backoffFunc BackoffFunc
|
//backoffFunc BackoffFunc
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
debugWriter io.Writer
|
debugWriter io.Writer
|
||||||
insecure bool
|
insecure bool
|
||||||
|
|
||||||
Server ServerClient
|
Server ServerClient
|
||||||
Task TaskClient
|
Task TaskClient
|
||||||
Pool PoolClient
|
Pool PoolClient
|
||||||
Node NodeClient
|
Node NodeClient
|
||||||
Snippet SnippetClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
|
|
@ -65,15 +63,7 @@ type ClientOption func(*Client)
|
||||||
|
|
||||||
func WithEndpoint(endpoint string) ClientOption {
|
func WithEndpoint(endpoint string) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) {
|
||||||
client.endpoints = append(client.endpoints, strings.TrimRight(endpoint, "/"))
|
client.endpoint = strings.TrimRight(endpoint, "/")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithEndpoints(endpoints []string) ClientOption {
|
|
||||||
return func(client *Client) {
|
|
||||||
for _, e := range endpoints {
|
|
||||||
client.endpoints = append(client.endpoints, strings.TrimRight(e, "/"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,72 +106,6 @@ func WithCredentials(username, password string) ClientOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrMissingEndpointsEnv = errors.New("missing environment variable PVE_ENDPOINTS")
|
|
||||||
ErrMissingUserEnv = errors.New("missing environment variable PVE_USER")
|
|
||||||
ErrMissingPasswordEnv = errors.New("missing environment variable PVE_PASSWORD")
|
|
||||||
|
|
||||||
ErrMissingEndpoints = errors.New("missing Endpoints in config")
|
|
||||||
ErrMissingUser = errors.New("missing User in config")
|
|
||||||
ErrMissingPassword = errors.New("missing Password in config")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Endpoints []string `json:"endpoints"`
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Insecure bool `json:"insecure"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) {
|
|
||||||
if len(cfg.Endpoints) == 0 {
|
|
||||||
err = ErrMissingEndpoints
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cfg.User == "" {
|
|
||||||
err = ErrMissingUser
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cfg.Password == "" {
|
|
||||||
err = ErrMissingPassword
|
|
||||||
return
|
|
||||||
}
|
|
||||||
options = []ClientOption{
|
|
||||||
WithEndpoints(cfg.Endpoints),
|
|
||||||
WithCredentials(cfg.User, cfg.Password),
|
|
||||||
WithInsecureClient(cfg.Insecure),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientOptionsFromEnv() (options []ClientOption, err error) {
|
|
||||||
endpoints := os.Getenv("PVE_ENDPOINTS")
|
|
||||||
if endpoints == "" {
|
|
||||||
err = ErrMissingEndpointsEnv
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user := os.Getenv("PVE_USER")
|
|
||||||
if user == "" {
|
|
||||||
err = ErrMissingUserEnv
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pass := os.Getenv("PVE_PASSWORD")
|
|
||||||
if pass == "" {
|
|
||||||
err = ErrMissingPasswordEnv
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
insecure, _ := strconv.ParseBool(os.Getenv("PVE_INSECURE"))
|
|
||||||
|
|
||||||
options = []ClientOption{
|
|
||||||
WithEndpoints(strings.Split(endpoints, ",")),
|
|
||||||
WithCredentials(user, pass),
|
|
||||||
WithInsecureClient(insecure),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(options ...ClientOption) *Client {
|
func NewClient(options ...ClientOption) *Client {
|
||||||
client := &Client{}
|
client := &Client{}
|
||||||
/*
|
/*
|
||||||
|
|
@ -197,14 +121,8 @@ func NewClient(options ...ClientOption) *Client {
|
||||||
if client.httpClient == nil {
|
if client.httpClient == nil {
|
||||||
client.httpClient = &http.Client{Transport: &http.Transport{
|
client.httpClient = &http.Client{Transport: &http.Transport{
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
}).DialContext,
|
}).DialContext,
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: client.insecure,
|
InsecureSkipVerify: client.insecure,
|
||||||
},
|
},
|
||||||
|
|
@ -215,27 +133,20 @@ func NewClient(options ...ClientOption) *Client {
|
||||||
client.Task = TaskClient{client: client}
|
client.Task = TaskClient{client: client}
|
||||||
client.Pool = PoolClient{client: client}
|
client.Pool = PoolClient{client: client}
|
||||||
client.Node = NodeClient{client: client}
|
client.Node = NodeClient{client: client}
|
||||||
client.Snippet = SnippetClient{client: client}
|
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
func (c *Client) getEndpoint() string {
|
|
||||||
if len(c.endpoints) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.endpoints[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Auth() (err error) {
|
func (c *Client) Auth() (err error) {
|
||||||
if !time.Now().After(c.session.Time) {
|
if !time.Now().After(c.session.Time) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.mu.Lock()
|
c.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.Unlock()
|
||||||
if !time.Now().After(c.session.Time) {
|
if !time.Now().After(c.session.Time) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := c.getEndpoint() + "/access/ticket"
|
u := c.endpoint + "/access/ticket"
|
||||||
body := httpbody{"username": c.username, "password": c.password}
|
body := httpbody{"username": c.username, "password": c.password}
|
||||||
r, err := http.NewRequest("POST", u, body.Reader())
|
r, err := http.NewRequest("POST", u, body.Reader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -261,7 +172,7 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
url := c.getEndpoint() + path
|
url := c.endpoint + path
|
||||||
req, err := http.NewRequest(method, url, body)
|
req, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -277,26 +188,25 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
|
||||||
var HTTPErr = errors.New("http error")
|
var HTTPErr = errors.New("http error")
|
||||||
|
|
||||||
func (c *Client) Do(r *http.Request, v interface{}) (resp *http.Response, err error) {
|
func (c *Client) Do(r *http.Request, v interface{}) (resp *http.Response, err error) {
|
||||||
if c.debugWriter != nil {
|
|
||||||
dumpReq, err := httputil.DumpRequestOut(r, true)
|
|
||||||
if err != nil {
|
|
||||||
return &http.Response{}, err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(c.debugWriter, "--- Request:\n%s\n\n", dumpReq)
|
|
||||||
}
|
|
||||||
resp, err = c.httpClient.Do(r)
|
resp, err = c.httpClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
resp.Body = io.NopCloser(bytes.NewReader(body))
|
resp.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||||
|
|
||||||
if c.debugWriter != nil {
|
if c.debugWriter != nil {
|
||||||
|
dumpReq, err := httputil.DumpRequest(r, true)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(c.debugWriter, "--- Request:\n%s\n\n", dumpReq)
|
||||||
|
|
||||||
dumpResp, err := httputil.DumpResponse(resp, true)
|
dumpResp, err := httputil.DumpResponse(resp, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
|
|
|
||||||
34
docs/dump.sh
34
docs/dump.sh
|
|
@ -1,34 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#===================================================================================
|
|
||||||
#
|
|
||||||
# FILE: dump.sh
|
|
||||||
# USAGE: dump.sh [-i interface] [tcpdump-parameters]
|
|
||||||
# DESCRIPTION: tcpdump on any interface and add the prefix [Interace:xy] in front of the dump data.
|
|
||||||
# OPTIONS: same as tcpdump
|
|
||||||
# REQUIREMENTS: tcpdump, sed, ifconfig, kill, awk, grep, posix regex matching
|
|
||||||
# BUGS: ---
|
|
||||||
# FIXED: - In 1.0 The parameter -w would not work without -i parameter as multiple tcpdumps are started.
|
|
||||||
# - In 1.1 VLAN's would not be shown if a single interface was dumped.
|
|
||||||
# NOTES: ---
|
|
||||||
# - 1.2 git initial
|
|
||||||
# AUTHOR: Sebastian Haas
|
|
||||||
# COMPANY: pharma mall
|
|
||||||
# VERSION: 1.2
|
|
||||||
# CREATED: 16.09.2014
|
|
||||||
# REVISION: 22.09.2014
|
|
||||||
#
|
|
||||||
#===================================================================================
|
|
||||||
|
|
||||||
# When this exits, exit all background processes:
|
|
||||||
trap 'kill $(jobs -p) &> /dev/null && sleep 0.2 && echo ' EXIT
|
|
||||||
# Create one tcpdump output per interface and add an identifier to the beginning of each line:
|
|
||||||
if [[ $@ =~ -i[[:space:]]?[^[:space:]]+ ]]; then
|
|
||||||
tcpdump -l $@ | sed 's/^/[Interface:'"${BASH_REMATCH[0]:2}"'] /' &
|
|
||||||
else
|
|
||||||
for interface in $(ifconfig | grep '^[a-z0-9]' | awk '{print $1}')
|
|
||||||
do
|
|
||||||
tcpdump -l -i $interface -nn $@ | sed 's/^/[Interface:'"$interface"'] /' &
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
# wait .. until CTRL+C
|
|
||||||
wait
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
apt update && apt install -y curl tcpdump
|
|
||||||
echo "deb http://download.proxmox.com/debian/pve buster pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
|
|
||||||
curl -sSL http://download.proxmox.com/debian/proxmox-ve-release-6.x.gpg | apt-key add -
|
|
||||||
|
|
||||||
rm /etc/network/interfaces.d/50-cloud-init
|
|
||||||
echo "alias ll='ls -lah'" >>/root/.bashrc
|
|
||||||
cat <<EOF>/root/.vimrc
|
|
||||||
syntax on
|
|
||||||
set mouse=
|
|
||||||
set ttymouse=
|
|
||||||
EOF
|
|
||||||
# source /etc/network/interfaces.d/*
|
|
||||||
sed -i -e '24,29d' /etc/network/interfaces
|
|
||||||
sed -i '1isource /etc/network/interfaces.d/*' /etc/network/interfaces
|
|
||||||
iface=eth1
|
|
||||||
ip=$(ip a show $iface | grep "inet " | cut -d" " -f6 | cut -d"/" -f1)
|
|
||||||
hw=$(ip a show $iface | grep "link/" | cut -d" " -f6)
|
|
||||||
sed -i "s/127.0.1.1/$ip/" /etc/hosts
|
|
||||||
cat <<EOF>>/etc/network/interfaces
|
|
||||||
auto $iface
|
|
||||||
iface $iface inet static
|
|
||||||
hwaddress $hw
|
|
||||||
|
|
||||||
auto vmbr0
|
|
||||||
iface vmbr0 inet static
|
|
||||||
address $ip/24
|
|
||||||
bridge-ports $iface
|
|
||||||
bridge-stp off
|
|
||||||
bridge-fd 0
|
|
||||||
EOF
|
|
||||||
|
|
||||||
apt update && apt purge cloud-init -y && apt install ifupdown2 -y && systemctl restart networking
|
|
||||||
|
|
||||||
# install ifupdown2 ?
|
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt dist-upgrade -y -o DPkg::options::="--force-confold"; apt autoremove -y; DEBIAN_FRONTEND=noninteractive apt install --assume-yes -o DPkg::options::="--force-confdef" -y proxmox-ve vim isc-dhcp-server libpve-network-perl;
|
|
||||||
#systemctl disable hc-net-ifup@
|
|
||||||
reboot
|
|
||||||
apt remove -y os-prober linux-image-amd64 'linux-image-4.19*'; apt autoremove -y;
|
|
||||||
|
|
||||||
cat <<EOF>>/etc/network/interfaces
|
|
||||||
|
|
||||||
auto vmbr1
|
|
||||||
iface vmbr1 inet static
|
|
||||||
address 10.0.0.254/24
|
|
||||||
bridge-ports none
|
|
||||||
bridge-stp off
|
|
||||||
bridge-fd 0
|
|
||||||
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
||||||
post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o eth0 -j MASQUERADE
|
|
||||||
post-down iptables -t nat -F
|
|
||||||
EOF
|
|
||||||
systemctl restart networking
|
|
||||||
|
|
||||||
pvecm create pmx -link0 172.16.0.2
|
|
||||||
|
|
||||||
cat <<EOF>/etc/default/isc-dhcp-server
|
|
||||||
INTERFACESv4="vmbr1"
|
|
||||||
INTERFACESv6=""
|
|
||||||
EOF
|
|
||||||
cat <<EOF>/etc/dhcp/dhcpd.conf
|
|
||||||
default-lease-time 600;
|
|
||||||
max-lease-time 7200;
|
|
||||||
|
|
||||||
subnet 10.0.0.0 netmask 255.255.255.0 {
|
|
||||||
range 10.0.0.1 10.0.0.253;
|
|
||||||
option domain-name-servers 1.1.1.1, 8.8.8.8;
|
|
||||||
option routers 10.0.0.254;
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
systemctl restart isc-dhcp-server
|
|
||||||
|
|
||||||
# controllers
|
|
||||||
# id: evpn
|
|
||||||
# asn: 65000
|
|
||||||
# peers: 172.16.0.2,172.16.0.3,172.16.0.4
|
|
||||||
|
|
||||||
# zones
|
|
||||||
# id: evpn
|
|
||||||
# tag: 10000
|
|
||||||
# exit-nodes: pmx01, pmx02
|
|
||||||
# controller: evpn
|
|
||||||
# mtu 1400
|
|
||||||
|
|
||||||
# vnets
|
|
||||||
# name: vnet1
|
|
||||||
# zone: evpn
|
|
||||||
# tag: 11000
|
|
||||||
# gateway: 192.168.1.1
|
|
||||||
|
|
||||||
# name: vnet2
|
|
||||||
# zone: evpn
|
|
||||||
# tag: 12000
|
|
||||||
# gateway: 192.168.2.1
|
|
||||||
|
|
||||||
cd /var/lib/vz/template/iso
|
|
||||||
curl -sSLO https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-virt-3.14.1-x86_64.iso
|
|
||||||
|
|
||||||
ip link set up dev eth0
|
|
||||||
ip link set mtu 1450 dev eth0
|
|
||||||
ip a add 192.168.1.2/24 dev eth0
|
|
||||||
ip route add default via 192.168.1.1
|
|
||||||
|
|
||||||
ip a add 192.168.2.2/24 dev eth0
|
|
||||||
ip route add default via 192.168.2.1
|
|
||||||
|
|
||||||
rm /etc/apt/sources.list.d/pve-enterprise.list
|
|
||||||
sed -i 's/buster\/updates/bullseye-security/g;s/buster/bullseye/g' /etc/apt/sources.list
|
|
||||||
sed -i -e 's/buster/bullseye/g' /etc/apt/sources.list.d/pve-install-repo.list
|
|
||||||
|
|
||||||
apt update && apt dist-upgrade
|
|
||||||
|
|
||||||
|
|
||||||
cd /tmp
|
|
||||||
curl -sSLO https://mutulin1.odiso.net/frr_7.5.1-2+pve_amd64.deb
|
|
||||||
curl -sSLO https://mutulin1.odiso.net/frr-pythontools_7.5.1-2+pve_all.deb
|
|
||||||
dpkg -i frr_7.5.1-2+pve_amd64.deb
|
|
||||||
dpkg -i frr-pythontools_7.5.1-2+pve_all.deb
|
|
||||||
|
|
||||||
ip route add table vrf_evpn default dev eth0 via 164.90.160.
|
|
||||||
|
|
||||||
# /etc/frr/frr.conf
|
|
||||||
bgp listen range 172.16.0.0/24
|
|
||||||
|
|
||||||
pvecm add 172.16.0.2
|
|
||||||
|
|
||||||
# vxlan zone
|
|
||||||
# id: vxlan
|
|
||||||
# peer: 172.16.0.2,172.16.0.3,172.16.0.4
|
|
||||||
# mut: 1400
|
|
||||||
|
|
||||||
# vnets
|
|
||||||
# name: vnet1
|
|
||||||
# zone: vxlan
|
|
||||||
# tag: 100000
|
|
||||||
|
|
||||||
fallocate -l 10G /var/osd.img
|
|
||||||
losetup -l -P /dev/loop1 /var/osd.img
|
|
||||||
wipefs -a /dev/loop1
|
|
||||||
lsblk
|
|
||||||
sed -i "s/'mpath'/'mpath', 'loop'/g" /usr/lib/python3/dist-packages/ceph_volume/util/disk.py
|
|
||||||
|
|
||||||
/usr/share/perl5/PVE/Diskmanage.pm
|
|
||||||
get_disks
|
|
||||||
$dev !~ m/^loop\d+$/;
|
|
||||||
|
|
||||||
get_sysdir_info
|
|
||||||
my $data = {};
|
|
||||||
if ($sysdir =~ /loop\d+/) {
|
|
||||||
$data->{size} = 1024;
|
|
||||||
$data->{rotational} = 1;
|
|
||||||
$data->{vendor} = 'unknown';
|
|
||||||
$data->{model} = 'unknown';
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
systemctl restart pvedaemon
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
curl -sSL https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img >/tmp/bionic-server-cloudimg-amd64.img
|
|
||||||
|
|
||||||
qm destroy 9000
|
|
||||||
qm create 9000 --memory 512 --net0 virtio,bridge=vmbr1
|
|
||||||
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 set 9000 --kvm 0
|
|
||||||
qm set 9000 --balloon 0
|
|
||||||
qm set 9000 --cpu kvm64
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
# iptables
|
|
||||||
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 222 -j DNAT --to 10.0.0.11:22
|
|
||||||
iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o eth0 -j MASQUERADE
|
|
||||||
|
|
||||||
# ceph
|
|
||||||
https://www.netways.de/blog/2018/11/14/ceph-mimic-using-loop-devices-as-osd/
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
echo "deb http://download.proxmox.com/debian/pve buster pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list
|
|
||||||
curl -sSL http://download.proxmox.com/debian/proxmox-ve-release-6.x.gpg | apt-key add -
|
|
||||||
|
|
||||||
cat <<EOF>>/etc/network/interfaces
|
|
||||||
auto eth0
|
|
||||||
iface eth0 inet dhcp
|
|
||||||
dns-nameservers 1.1.1.1 8.8.8.8
|
|
||||||
|
|
||||||
EOF
|
|
||||||
rm /etc/network/interfaces.d/50-cloud-init
|
|
||||||
echo "alias ll='ls -lah'" >>/root/.bashrc
|
|
||||||
cat <<EOF>/root/.vimrc
|
|
||||||
syntax on
|
|
||||||
set mouse=
|
|
||||||
set ttymouse=
|
|
||||||
EOF
|
|
||||||
iface=enp7s0
|
|
||||||
ip=$(ip a show $iface | grep "inet " | cut -d" " -f6 | cut -d"/" -f1)
|
|
||||||
sed -i "s/127.0.1.1/$ip/" /etc/hosts
|
|
||||||
cat <<EOF>>/etc/network/interfaces
|
|
||||||
auto $iface
|
|
||||||
iface $iface inet static
|
|
||||||
mtu 1450
|
|
||||||
|
|
||||||
auto vmbr0
|
|
||||||
iface vmbr0 inet static
|
|
||||||
address $ip/32
|
|
||||||
bridge-ports $iface
|
|
||||||
bridge-stp off
|
|
||||||
bridge-fd 0
|
|
||||||
mtu 1450
|
|
||||||
post-up ip route add 172.16.0.1/32 dev vmbr0
|
|
||||||
post-up ip route add 172.16.0.0/24 via 172.16.0.1
|
|
||||||
EOF
|
|
||||||
|
|
||||||
apt update && apt purge cloud-init -y && apt install ifupdown2 -y && systemctl restart networking
|
|
||||||
|
|
||||||
# install ifupdown2 ?
|
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt dist-upgrade -y -o DPkg::options::="--force-confold"; apt autoremove -y; DEBIAN_FRONTEND=noninteractive apt install --assume-yes -o DPkg::options::="--force-confdef" -y proxmox-ve vim isc-dhcp-server libpve-network-perl;
|
|
||||||
systemctl disable hc-net-ifup@
|
|
||||||
reboot
|
|
||||||
apt remove -y os-prober linux-image-amd64 'linux-image-4.19*'; apt autoremove -y;
|
|
||||||
|
|
||||||
cat <<EOF>>/etc/network/interfaces
|
|
||||||
|
|
||||||
auto vmbr1
|
|
||||||
iface vmbr1 inet static
|
|
||||||
address 10.0.0.254/24
|
|
||||||
bridge-ports none
|
|
||||||
bridge-stp off
|
|
||||||
bridge-fd 0
|
|
||||||
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
||||||
post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o eth0 -j MASQUERADE
|
|
||||||
post-down iptables -t nat -F
|
|
||||||
EOF
|
|
||||||
systemctl restart networking
|
|
||||||
|
|
||||||
cat <<EOF>/etc/default/isc-dhcp-server
|
|
||||||
INTERFACESv4="vmbr1"
|
|
||||||
INTERFACESv6=""
|
|
||||||
EOF
|
|
||||||
cat <<EOF>/etc/dhcp/dhcpd.conf
|
|
||||||
default-lease-time 600;
|
|
||||||
max-lease-time 7200;
|
|
||||||
|
|
||||||
subnet 10.0.0.0 netmask 255.255.255.0 {
|
|
||||||
range 10.0.0.1 10.0.0.253;
|
|
||||||
option domain-name-servers 1.1.1.1, 8.8.8.8;
|
|
||||||
option routers 10.0.0.254;
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
systemctl restart isc-dhcp-server
|
|
||||||
|
|
||||||
# controllers
|
|
||||||
# id: router
|
|
||||||
# asn: 65534
|
|
||||||
# peers: 172.16.0.2,172.16.0.3,172.16.0.4
|
|
||||||
# gateway-nodes: pmx01, pmx02
|
|
||||||
|
|
||||||
# zones
|
|
||||||
# id: evpn
|
|
||||||
# tag: 10000
|
|
||||||
# controller: router
|
|
||||||
# mtu 1400
|
|
||||||
|
|
||||||
# vnets
|
|
||||||
# name: vnet2
|
|
||||||
# zone: evpn
|
|
||||||
# tag: 11000
|
|
||||||
# anycast: 10.0.0.1/24
|
|
||||||
|
|
||||||
# /etc/frr/frr.conf
|
|
||||||
bgp listen range 172.16.0.0/24
|
|
||||||
|
|
||||||
pvecm add 172.16.0.2
|
|
||||||
|
|
||||||
# vxlan zone
|
|
||||||
# id: vxlan
|
|
||||||
# peer: 172.16.0.2,172.16.0.3,172.16.0.4
|
|
||||||
# mut: 1400
|
|
||||||
|
|
||||||
# vnets
|
|
||||||
# name: vnet1
|
|
||||||
# zone: vxlan
|
|
||||||
# tag: 100000
|
|
||||||
|
|
||||||
fallocate -l 10G /var/osd.img
|
|
||||||
losetup -l -P /dev/loop1 /var/osd.img
|
|
||||||
wipefs -a /dev/loop1
|
|
||||||
lsblk
|
|
||||||
sed -i "s/'mpath'/'mpath', 'loop'/g" /usr/lib/python3/dist-packages/ceph_volume/util/disk.py
|
|
||||||
|
|
||||||
/usr/share/perl5/PVE/Diskmanage.pm
|
|
||||||
get_disks
|
|
||||||
$dev !~ m/^loop\d+$/;
|
|
||||||
|
|
||||||
get_sysdir_info
|
|
||||||
my $data = {};
|
|
||||||
if ($sysdir =~ /loop\d+/) {
|
|
||||||
$data->{size} = 1024;
|
|
||||||
$data->{rotational} = 1;
|
|
||||||
$data->{vendor} = 'unknown';
|
|
||||||
$data->{model} = 'unknown';
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
systemctl restart pvedaemon
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
curl -sSL https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img >/tmp/bionic-server-cloudimg-amd64.img
|
|
||||||
|
|
||||||
qm destroy 9000
|
|
||||||
qm create 9000 --memory 512 --net0 virtio,bridge=vmbr1
|
|
||||||
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 set 9000 --kvm 0
|
|
||||||
qm set 9000 --balloon 0
|
|
||||||
qm set 9000 --cpu kvm64
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
# iptables
|
|
||||||
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 222 -j DNAT --to 10.0.0.11:22
|
|
||||||
iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o eth0 -j MASQUERADE
|
|
||||||
|
|
||||||
# ceph
|
|
||||||
https://www.netways.de/blog/2018/11/14/ceph-mimic-using-loop-devices-as-osd/
|
|
||||||
23
helper.go
23
helper.go
|
|
@ -3,7 +3,6 @@
|
||||||
package pve
|
package pve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -44,25 +43,3 @@ func (b httpbody) Reader() io.Reader {
|
||||||
}
|
}
|
||||||
return strings.NewReader(data.Encode())
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
28
ipconfig.go
28
ipconfig.go
|
|
@ -8,25 +8,9 @@ import (
|
||||||
|
|
||||||
type IPConfigs []*IPConfig
|
type IPConfigs []*IPConfig
|
||||||
|
|
||||||
func (c *IPConfigs) Set(index int, ipc *IPConfig) {
|
func (c IPConfigs) Get(index int) *IPConfig {
|
||||||
l := len(*c)
|
if len(c)-1 >= index {
|
||||||
if index >= l {
|
return c[index]
|
||||||
n := make(IPConfigs, index+1)
|
|
||||||
copy(n, *c)
|
|
||||||
n[index] = ipc
|
|
||||||
*c = n
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if l == 0 {
|
|
||||||
*c = append(*c, ipc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
(*c)[index] = ipc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *IPConfigs) Get(index int) *IPConfig {
|
|
||||||
if len(*c)-1 >= index {
|
|
||||||
return (*c)[index]
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +20,7 @@ type IPConfig struct {
|
||||||
IPv4Gateway string
|
IPv4Gateway string
|
||||||
IPv6CIDR string
|
IPv6CIDR string
|
||||||
IPv6Gateway string
|
IPv6Gateway string
|
||||||
|
MTU string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *IPConfig) String() string {
|
func (c *IPConfig) String() string {
|
||||||
|
|
@ -52,6 +37,9 @@ func (c *IPConfig) String() string {
|
||||||
str = append(str, "gw6="+c.IPv6Gateway)
|
str = append(str, "gw6="+c.IPv6Gateway)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.MTU != "" {
|
||||||
|
str = append(str, "mtu="+c.MTU)
|
||||||
|
}
|
||||||
return strings.Join(str, ",")
|
return strings.Join(str, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,6 +63,8 @@ func parseIPConfig(s string) (c *IPConfig) {
|
||||||
c.IPv6CIDR = v[1]
|
c.IPv6CIDR = v[1]
|
||||||
case "gw6":
|
case "gw6":
|
||||||
c.IPv6Gateway = v[1]
|
c.IPv6Gateway = v[1]
|
||||||
|
case "mtu":
|
||||||
|
c.MTU = v[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
32
network.go
32
network.go
|
|
@ -9,25 +9,9 @@ import (
|
||||||
|
|
||||||
type NetworkDevices []*NetworkDevice
|
type NetworkDevices []*NetworkDevice
|
||||||
|
|
||||||
func (c *NetworkDevices) Set(index int, d *NetworkDevice) {
|
func (c NetworkDevices) Get(index int) *NetworkDevice {
|
||||||
l := len(*c)
|
if len(c)-1 >= index {
|
||||||
if index >= l {
|
return c[index]
|
||||||
n := make(NetworkDevices, index+1)
|
|
||||||
copy(n, *c)
|
|
||||||
n[index] = d
|
|
||||||
*c = n
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if l == 0 {
|
|
||||||
*c = append(*c, d)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
(*c)[index] = d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *NetworkDevices) Get(index int) *NetworkDevice {
|
|
||||||
if len(*c)-1 >= index {
|
|
||||||
return (*c)[index]
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -36,15 +20,10 @@ type NetworkDevice struct {
|
||||||
Type string
|
Type string
|
||||||
MACAddress string
|
MACAddress string
|
||||||
Bridge string
|
Bridge string
|
||||||
MTU string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetworkDevice) String() string {
|
func (n *NetworkDevice) String() string {
|
||||||
var mtu string
|
return fmt.Sprintf("%s=%s,bridge=%s", n.Type, n.MACAddress, n.Bridge)
|
||||||
if n.MTU != "" {
|
|
||||||
mtu = "," + n.MTU
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s=%s,bridge=%s%s", n.Type, n.MACAddress, n.Bridge, mtu)
|
|
||||||
}
|
}
|
||||||
func parseNetworkDevice(s string) (n *NetworkDevice) {
|
func parseNetworkDevice(s string) (n *NetworkDevice) {
|
||||||
cfg := strings.Split(s, ",")
|
cfg := strings.Split(s, ",")
|
||||||
|
|
@ -62,8 +41,7 @@ func parseNetworkDevice(s string) (n *NetworkDevice) {
|
||||||
for _, o := range cfg {
|
for _, o := range cfg {
|
||||||
if strings.HasPrefix(o, "bridge=") {
|
if strings.HasPrefix(o, "bridge=") {
|
||||||
n.Bridge = o[7:]
|
n.Bridge = o[7:]
|
||||||
} else if strings.HasPrefix(o, "mtu=") {
|
break
|
||||||
n.MTU = o[4:]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
208
node.go
208
node.go
|
|
@ -6,15 +6,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ErrServerNotFound = errors.New("server not found")
|
||||||
ErrNodesOffline = errors.New("one or more nodes are offline")
|
|
||||||
ErrNodesNotSearched = errors.New("one or more nodes could not be searched")
|
|
||||||
ErrUnschedulable = errors.New("no schedulable node found")
|
|
||||||
ErrTooManyServersFound = errors.New("too many servers found with the same name")
|
|
||||||
)
|
|
||||||
|
|
||||||
type NodeStatus string
|
type NodeStatus string
|
||||||
|
|
||||||
|
|
@ -25,163 +19,18 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Name string `json:"node"`
|
Name string `json:"node"`
|
||||||
Status NodeStatus `json:"status"`
|
Status NodeStatus `json:"status"`
|
||||||
CPU float64 `json:"cpu"`
|
|
||||||
MaxCPU float64 `json:"maxcpu"`
|
|
||||||
Mem uint64 `json:"mem"`
|
|
||||||
MaxMem uint64 `json:"maxmem"`
|
|
||||||
Disk uint64 `json:"disk"`
|
|
||||||
MaxDisk uint64 `json:"maxdisk"`
|
|
||||||
serverList ServerRefList `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) memFreePercent() float64 {
|
|
||||||
return float64(n.Mem) / float64(n.MaxMem) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) memUsedPercent() float64 {
|
|
||||||
return float64(n.MaxMem-n.Mem) / float64(n.MaxMem) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) weight() int {
|
|
||||||
return int(n.memUsedPercent()) + (len(n.serverList) * 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeList []*Node
|
type NodeList []*Node
|
||||||
|
|
||||||
func (nl NodeList) sortByWeight() {
|
type ServerList []*Server
|
||||||
sort.Sort(weightSorter(nl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nl *NodeList) remove(i int) {
|
|
||||||
(*nl)[i] = (*nl)[len(*nl)-1]
|
|
||||||
*nl = (*nl)[:len(*nl)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nl *NodeList) filter(filters ...NodeFilter) {
|
|
||||||
for _, f := range filters {
|
|
||||||
f(nl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nl *NodeList) getServerList(ctx context.Context, c *NodeClient) (err error) {
|
|
||||||
for _, n := range *nl {
|
|
||||||
n.serverList, err = c.ListServers(ctx, n)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type weightSorter NodeList
|
|
||||||
|
|
||||||
func (s weightSorter) Len() int { return len(s) }
|
|
||||||
func (s weightSorter) Less(i, j int) bool {
|
|
||||||
return s[i].weight() < s[j].weight()
|
|
||||||
}
|
|
||||||
func (s weightSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
|
|
||||||
type NodeFilter func(*NodeList)
|
|
||||||
|
|
||||||
func FilterOnlineNode() NodeFilter {
|
|
||||||
return func(nl *NodeList) {
|
|
||||||
for i, n := range *nl {
|
|
||||||
if n.Status != NodeStatusOnline {
|
|
||||||
nl.remove(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FilterNodeName(name string) NodeFilter {
|
|
||||||
return func(nl *NodeList) {
|
|
||||||
for i, n := range *nl {
|
|
||||||
if n.Name == name {
|
|
||||||
nl.remove(i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FilterMaxCores(cores uint64) NodeFilter {
|
|
||||||
return func(nl *NodeList) {
|
|
||||||
for i, n := range *nl {
|
|
||||||
if cores > uint64(n.MaxCPU) {
|
|
||||||
nl.remove(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
mib = 1024 * 1024
|
|
||||||
gib = mib * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
func FilterFreeMem(mem uint64) NodeFilter {
|
|
||||||
return func(nl *NodeList) {
|
|
||||||
for i, n := range *nl {
|
|
||||||
if (mem * gib) >= n.MaxMem-n.Mem {
|
|
||||||
nl.remove(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Useful? only local disk?
|
|
||||||
func FilterFreeDisk(disk uint64) NodeFilter {
|
|
||||||
return func(nl *NodeList) {
|
|
||||||
for i, n := range *nl {
|
|
||||||
if (disk * gib) >= n.MaxDisk-n.Disk {
|
|
||||||
nl.remove(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FilterVMAntiAffinity(url string) NodeFilter {
|
|
||||||
return func(nl *NodeList) {
|
|
||||||
_, _, id, err := ParseURL(url)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i, n := range *nl {
|
|
||||||
for _, s := range n.serverList {
|
|
||||||
if s.ID == id {
|
|
||||||
nl.remove(i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeClient struct {
|
type NodeClient struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NodeClient) Schedule(ctx context.Context, filters ...NodeFilter) (n *Node, err error) {
|
|
||||||
nl, err := c.List(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = nl.getServerList(ctx, c)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nl.filter(filters...)
|
|
||||||
nl.sortByWeight()
|
|
||||||
if len(nl) == 0 {
|
|
||||||
err = ErrUnschedulable
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n = nl[0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *NodeClient) List(ctx context.Context) (nl NodeList, err error) {
|
func (c *NodeClient) List(ctx context.Context) (nl NodeList, err error) {
|
||||||
req, err := c.client.NewRequest(ctx, "GET", "/nodes", nil)
|
req, err := c.client.NewRequest(ctx, "GET", "/nodes", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -191,7 +40,7 @@ func (c *NodeClient) List(ctx context.Context) (nl NodeList, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NodeClient) ListServers(ctx context.Context, n *Node) (sl ServerRefList, err error) {
|
func (c *NodeClient) ListServers(ctx context.Context, n *Node) (sl ServerList, err error) {
|
||||||
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu", n.Name), nil)
|
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu", n.Name), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -205,66 +54,43 @@ func (c *NodeClient) ListServers(ctx context.Context, n *Node) (sl ServerRefList
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NodeClient) FindServer(ctx context.Context, name string, id int) (ref *ServerRef, err error) {
|
func (c *NodeClient) FindServer(ctx context.Context, name, id string) (s *Server, err error) {
|
||||||
valid := ValidateID(id) == nil
|
|
||||||
nl, err := c.List(ctx)
|
nl, err := c.List(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
|
||||||
nodeOffline bool
|
|
||||||
nodeNotSearched bool
|
|
||||||
)
|
|
||||||
for _, node := range nl {
|
for _, node := range nl {
|
||||||
if node.Status != NodeStatusOnline {
|
if node.Status != NodeStatusOnline {
|
||||||
nodeOffline = true
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sl, err := c.ListServers(ctx, node)
|
sl, err := c.ListServers(ctx, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
nodeNotSearched = true
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, s := range sl {
|
for _, s := range sl {
|
||||||
if valid && s.ID == id {
|
if id != "" && s.ID == id {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
if name != "" && s.Name == name {
|
if name != "" && s.Name == name {
|
||||||
if ref != nil {
|
return s, nil
|
||||||
return nil, ErrTooManyServersFound
|
|
||||||
}
|
|
||||||
ref = s
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nodeOffline {
|
return nil, ErrServerNotFound
|
||||||
return nil, ErrNodesOffline
|
|
||||||
}
|
|
||||||
if nodeNotSearched {
|
|
||||||
return nil, ErrNodesNotSearched
|
|
||||||
}
|
|
||||||
if ref == nil {
|
|
||||||
err = ErrServerNotFound
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NodeClient) FindServerByName(ctx context.Context, name string) (s *ServerRef, err error) {
|
func (c *NodeClient) FindServerByName(ctx context.Context, name string) (s *Server, err error) {
|
||||||
return c.FindServer(ctx, name, InvalidID)
|
return c.FindServer(ctx, name, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NodeClient) FindServerByID(ctx context.Context, id int) (s *ServerRef, err error) {
|
func (c *NodeClient) FindServerByID(ctx context.Context, id string) (s *Server, err error) {
|
||||||
err = ValidateID(id)
|
return c.FindServer(ctx, "", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NodeClient) FindServerByURL(ctx context.Context, url string) (s *Server, err error) {
|
||||||
|
_, id, err := ParseURL(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.FindServer(ctx, "", id)
|
return c.FindServer(ctx, "", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NodeClient) FindServerByURL(ctx context.Context, url string) (s *ServerRef, err error) {
|
|
||||||
_, _, id, err := ParseURL(url)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.FindServerByID(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
--- PVE/Storage.pm.old 2021-04-18 12:35:48.201033594 +0200
|
|
||||||
+++ PVE/Storage.pm 2021-04-09 23:28:07.554075001 +0200
|
|
||||||
@@ -420,6 +420,15 @@
|
|
||||||
return $plugin->get_subdir($scfg, 'iso');
|
|
||||||
}
|
|
||||||
|
|
||||||
+sub get_snippet_dir {
|
|
||||||
+ my ($cfg, $storeid) = @_;
|
|
||||||
+
|
|
||||||
+ my $scfg = storage_config($cfg, $storeid);
|
|
||||||
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
|
||||||
+
|
|
||||||
+ return $plugin->get_subdir($scfg, 'snippets');
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
sub get_vztmpl_dir {
|
|
||||||
my ($cfg, $storeid) = @_;
|
|
||||||
|
|
||||||
--- PVE/API2/Storage/Status.pm.old 2021-04-18 12:36:10.512862744 +0200
|
|
||||||
+++ PVE/API2/Storage/Status.pm 2021-04-09 23:28:46.429777635 +0200
|
|
||||||
@@ -424,6 +424,8 @@
|
|
||||||
raise_param_exc({ filename => "missing '.tar.gz' or '.tar.xz' extension" });
|
|
||||||
}
|
|
||||||
$path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
|
|
||||||
+ } elsif ($content eq 'snippets') {
|
|
||||||
+ $path = PVE::Storage::get_snippet_dir($cfg, $param->{storage});
|
|
||||||
} else {
|
|
||||||
raise_param_exc({ content => "upload content type '$content' not allowed" });
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
--- Storage.pm.old 2021-11-28 12:58:53.026308987 +0100
|
|
||||||
+++ Storage.pm 2021-11-28 12:59:14.394144995 +0100
|
|
||||||
@@ -427,6 +427,15 @@
|
|
||||||
return $plugin->get_subdir($scfg, 'vztmpl');
|
|
||||||
}
|
|
||||||
|
|
||||||
+sub get_snippet_dir {
|
|
||||||
+ my ($cfg, $storeid) = @_;
|
|
||||||
+
|
|
||||||
+ my $scfg = storage_config($cfg, $storeid);
|
|
||||||
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
|
||||||
+
|
|
||||||
+ return $plugin->get_subdir($scfg, 'snippets');
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
sub get_backup_dir {
|
|
||||||
my ($cfg, $storeid) = @_;
|
|
||||||
|
|
||||||
|
|
||||||
--- API2/Storage/Config.pm.old 2021-11-28 14:09:07.861745249 +0100
|
|
||||||
+++ API2/Storage/Config.pm 2021-11-28 14:00:09.133897607 +0100
|
|
||||||
@@ -19,7 +19,7 @@
|
|
||||||
|
|
||||||
use base qw(PVE::RESTHandler);
|
|
||||||
|
|
||||||
-my @ctypes = qw(images vztmpl iso backup);
|
|
||||||
+my @ctypes = qw(images vztmpl iso backup snippets);
|
|
||||||
|
|
||||||
my $storage_type_enum = PVE::Storage::Plugin->lookup_types();
|
|
||||||
|
|
||||||
|
|
||||||
--- API2/Storage/Status.pm.old 2021-11-28 13:51:17.609996827 +0100
|
|
||||||
+++ API2/Storage/Status.pm 2021-11-28 13:51:02.190115817 +0100
|
|
||||||
@@ -381,7 +381,7 @@
|
|
||||||
content => {
|
|
||||||
description => "Content type.",
|
|
||||||
type => 'string', format => 'pve-storage-content',
|
|
||||||
- enum => ['iso', 'vztmpl'],
|
|
||||||
+ enum => ['iso', 'vztmpl', 'snippets'],
|
|
||||||
},
|
|
||||||
filename => {
|
|
||||||
description => "The name of the file to create. Caution: This will be normalized!",
|
|
||||||
@@ -433,6 +433,8 @@
|
|
||||||
raise_param_exc({ filename => "wrong file extension" });
|
|
||||||
}
|
|
||||||
$path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
|
|
||||||
+ } elsif ($content eq 'snippets') {
|
|
||||||
+ $path = PVE::Storage::get_snippet_dir($cfg, $param->{storage});
|
|
||||||
} else {
|
|
||||||
raise_param_exc({ content => "upload content type '$content' not allowed" });
|
|
||||||
}
|
|
||||||
@@ -534,7 +536,7 @@
|
|
||||||
content => {
|
|
||||||
description => "Content type.", # TODO: could be optional & detected in most cases
|
|
||||||
type => 'string', format => 'pve-storage-content',
|
|
||||||
- enum => ['iso', 'vztmpl'],
|
|
||||||
+ enum => ['iso', 'vztmpl', 'snippets'],
|
|
||||||
},
|
|
||||||
filename => {
|
|
||||||
description => "The name of the file to create. Caution: This will be normalized!",
|
|
||||||
111
pool.go
111
pool.go
|
|
@ -12,7 +12,6 @@ import (
|
||||||
var (
|
var (
|
||||||
ErrPoolExists = errors.New("pool already exists")
|
ErrPoolExists = errors.New("pool already exists")
|
||||||
ErrPoolNotExists = errors.New("pool does not exist")
|
ErrPoolNotExists = errors.New("pool does not exist")
|
||||||
ErrEmptyPoolName = errors.New("pool name is empty")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakePoolName(namespace, name string) string {
|
func MakePoolName(namespace, name string) string {
|
||||||
|
|
@ -73,113 +72,3 @@ func (c *PoolClient) Delete(ctx context.Context, name string) (err error) {
|
||||||
_, err = c.client.Do(req, nil)
|
_, err = c.client.Do(req, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type pools struct {
|
|
||||||
PoolID string `json:"poolid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PoolClient) ListPools(ctx context.Context) (list []string, err error) {
|
|
||||||
var ps []pools
|
|
||||||
req, err := c.client.NewRequest(ctx, "GET", "/pools", nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = c.client.Do(req, &ps)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, p := range ps {
|
|
||||||
list = append(list, p.PoolID)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type poolMember struct {
|
|
||||||
ID int `json:"vmid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Node string `json:"node"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type poolMembers []poolMember
|
|
||||||
|
|
||||||
func (pms poolMembers) ServerList() (sl ServerRefList) {
|
|
||||||
for _, pm := range pms {
|
|
||||||
sl = append(sl, &ServerRef{
|
|
||||||
ID: pm.ID,
|
|
||||||
Name: pm.Name,
|
|
||||||
Node: pm.Node,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type pool struct {
|
|
||||||
Members poolMembers `json:"members"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PoolClient) ListMembers(ctx context.Context, name string) (sl ServerRefList, err error) {
|
|
||||||
if name == "" {
|
|
||||||
err = ErrEmptyPoolName
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var p pool
|
|
||||||
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/pools/%s", name), nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = c.client.Do(req, &p)
|
|
||||||
sl = p.Members.ServerList()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PoolClient) FindServer(ctx context.Context, poolname, servername string, id int) (ref *ServerRef, err error) {
|
|
||||||
valid := ValidateID(id) == nil
|
|
||||||
var pools []string
|
|
||||||
if poolname != "" {
|
|
||||||
pools = append(pools, poolname)
|
|
||||||
} else {
|
|
||||||
pools, err = c.ListPools(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, pool := range pools {
|
|
||||||
sl, err := c.ListMembers(ctx, pool)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, s := range sl {
|
|
||||||
if valid && s.ID == id {
|
|
||||||
s.Pool = pool
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
if servername != "" && s.Name == servername {
|
|
||||||
if ref != nil {
|
|
||||||
return nil, ErrTooManyServersFound
|
|
||||||
}
|
|
||||||
s.Pool = pool
|
|
||||||
ref = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ref == nil {
|
|
||||||
err = ErrServerNotFound
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PoolClient) FindServerByName(ctx context.Context, poolname, servername string) (s *ServerRef, err error) {
|
|
||||||
return c.FindServer(ctx, poolname, servername, InvalidID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PoolClient) FindServerByID(ctx context.Context, poolname string, id int) (s *ServerRef, err error) {
|
|
||||||
return c.FindServer(ctx, poolname, "", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PoolClient) FindServerByURL(ctx context.Context, url string) (s *ServerRef, err error) {
|
|
||||||
pool, _, id, err := ParseURL(url)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.FindServerByID(ctx, pool, id)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
65
scheme.go
65
scheme.go
|
|
@ -6,33 +6,14 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const PVEScheme = "pve"
|
||||||
PVEScheme = "pve"
|
|
||||||
PVESchemeURL = PVEScheme + "://"
|
|
||||||
|
|
||||||
MinID = 100
|
var ErrInvalidPVEURL = errors.New("invalid pve url scheme")
|
||||||
InvalidID = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
func ParseURL(s string) (node, id string, err error) {
|
||||||
ErrInvalidPVEURL = errors.New("invalid pve url scheme")
|
|
||||||
ErrNoID = errors.New("missing id in url")
|
|
||||||
ErrInvalidID = errors.New("invalid id")
|
|
||||||
ErrParsingID = errors.New("error parsing id in url")
|
|
||||||
)
|
|
||||||
|
|
||||||
func ValidateID(id int) error {
|
|
||||||
if id < MinID {
|
|
||||||
return ErrInvalidID
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseURL(s string) (pool, node string, id int, err error) {
|
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -41,43 +22,9 @@ func ParseURL(s string) (pool, node string, id int, err error) {
|
||||||
err = ErrInvalidPVEURL
|
err = ErrInvalidPVEURL
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a := strings.Split(u.Path, "/")
|
return u.Host, strings.TrimPrefix(u.Path, "/"), nil
|
||||||
if len(a) != 3 {
|
|
||||||
err = ErrInvalidPVEURL
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if a[2] == "" {
|
|
||||||
err = ErrNoID
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id, err = strconv.Atoi(a[2])
|
|
||||||
if err != nil {
|
|
||||||
err = ErrParsingID
|
|
||||||
}
|
|
||||||
return u.Host, a[1], id, ValidateID(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerRefFromURL(s string) (ref *ServerRef, err error) {
|
func NewURL(node, id string) string {
|
||||||
pool, node, id, err := ParseURL(s)
|
return fmt.Sprintf("%s://%s/%s", PVEScheme, node, id)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ref = &ServerRef{
|
|
||||||
ID: id,
|
|
||||||
Node: node,
|
|
||||||
Pool: pool,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewURL(pool, node string, id int) string {
|
|
||||||
return fmt.Sprintf("%s%s/%s/%d", PVESchemeURL, pool, node, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func K8sURL(url string) (string, error) {
|
|
||||||
pool, _, id, err := ParseURL(url)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return NewURL(pool, "", id), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
367
server.go
367
server.go
|
|
@ -10,125 +10,51 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrServerNotFound = errors.New("server not found")
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ID int `json:"vmid"`
|
ID string `json:"vmid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Node string
|
Node string
|
||||||
Pool string
|
Status ServerStatus `json:"status"`
|
||||||
Status ServerStatus `json:"status"`
|
Resources Resources
|
||||||
Resources Resources
|
NetDevices NetworkDevices
|
||||||
NetDevices NetworkDevices
|
IPConfig IPConfigs
|
||||||
IPConfig IPConfigs
|
Nameserver Nameserver
|
||||||
Nameserver Nameserver
|
SearchDomain string
|
||||||
SearchDomain string
|
UserData *UserData
|
||||||
UserData *UserData
|
|
||||||
UserDataStorage string
|
|
||||||
cicustom bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UserDataSnippetName() string {
|
func (s *Server) body() (httpbody, error) {
|
||||||
return fmt.Sprintf("%d_userdata", s.ID)
|
body := httpbody{
|
||||||
}
|
"memory": strconv.FormatInt(s.Resources.Memory, 10),
|
||||||
|
"cores": strconv.FormatInt(s.Resources.Cores, 10),
|
||||||
func (s *Server) UserDataSnippet(storage string) string {
|
|
||||||
return fmt.Sprintf("user=%s", SnippetVolume(storage, s.UserDataSnippetName()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) body() (b httpbody) {
|
|
||||||
b = httpbody{
|
|
||||||
"memory": s.Resources.Memory.String(),
|
|
||||||
"cores": s.Resources.Cores.String(),
|
|
||||||
"name": s.Name,
|
"name": s.Name,
|
||||||
"nameserver": s.Nameserver.String(),
|
"nameserver": s.Nameserver.String(),
|
||||||
"searchdomain": s.SearchDomain,
|
"searchdomain": s.SearchDomain,
|
||||||
}
|
}
|
||||||
if s.UserData != nil || s.cicustom {
|
userdata, err := s.UserData.String()
|
||||||
b["cicustom"] = s.UserDataSnippet(s.UserDataStorage)
|
body["ciuserdata"] = userdata
|
||||||
}
|
|
||||||
for i, n := range s.NetDevices {
|
for i, n := range s.NetDevices {
|
||||||
if n == nil {
|
body["net"+strconv.Itoa(i)] = n.String()
|
||||||
b["net"+strconv.Itoa(i)] = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b["net"+strconv.Itoa(i)] = n.String()
|
|
||||||
}
|
}
|
||||||
for i, ip := range s.IPConfig {
|
for i, ip := range s.IPConfig {
|
||||||
if ip == nil {
|
body["ipconfig"+strconv.Itoa(i)] = ip.String()
|
||||||
b["ipconfig"+strconv.Itoa(i)] = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b["ipconfig"+strconv.Itoa(i)] = ip.String()
|
|
||||||
}
|
}
|
||||||
return
|
return body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) InstanceID() string {
|
func (s *Server) InstanceID() string {
|
||||||
return NewURL("", s.Node, s.ID)
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) K8sID() string {
|
func (s *Server) String() string {
|
||||||
return NewURL(s.Pool, "", s.ID)
|
return NewURL(s.Node, s.ID)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Ref() *ServerRef {
|
|
||||||
return &ServerRef{
|
|
||||||
ID: s.ID,
|
|
||||||
Node: s.Node,
|
|
||||||
Name: s.Name,
|
|
||||||
Pool: s.Pool,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerRefList []*ServerRef
|
|
||||||
|
|
||||||
type ServerRef struct {
|
|
||||||
ID int `json:"vmid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Template int `json:"template,omitempty"`
|
|
||||||
Node string `json:"node,omitempty"`
|
|
||||||
Pool string `json:"pool,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ref *ServerRef) InstanceID() string {
|
|
||||||
return NewURL("", ref.Node, ref.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ref *ServerRef) K8sID() string {
|
|
||||||
return NewURL(ref.Pool, "", ref.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ref *ServerRef) IsTemplate() bool {
|
|
||||||
return ref.Template == 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Resources struct {
|
type Resources struct {
|
||||||
Cores Cores `json:"cores"`
|
Memory int64
|
||||||
Memory Memory `json:"memory"`
|
Cores int64
|
||||||
Disk Disk `json:"disk"`
|
BootDisk string
|
||||||
}
|
BootDiskSize string
|
||||||
|
|
||||||
type Cores uint64
|
|
||||||
|
|
||||||
func (c Cores) String() string {
|
|
||||||
return strconv.FormatUint(uint64(c), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Memory uint64
|
|
||||||
|
|
||||||
func (m Memory) String() string {
|
|
||||||
return strconv.FormatUint(uint64(m)*1024, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Disk struct {
|
|
||||||
Storage string `json:"storage"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Size uint64 `json:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Disk) String() string {
|
|
||||||
return strconv.FormatUint(d.Size, 10) + "G"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Nameserver []string
|
type Nameserver []string
|
||||||
|
|
@ -143,11 +69,8 @@ func (n Nameserver) String() string {
|
||||||
|
|
||||||
type serverConfig map[string]interface{}
|
type serverConfig map[string]interface{}
|
||||||
|
|
||||||
func (sc serverConfig) Server(ref *ServerRef) (s *Server, err error) {
|
func (sc serverConfig) Server(node, id string) (s *Server, err error) {
|
||||||
if len(sc) == 0 {
|
s = &Server{ID: id, Node: node}
|
||||||
return
|
|
||||||
}
|
|
||||||
s = &Server{ID: ref.ID, Node: ref.Node}
|
|
||||||
|
|
||||||
for k := range sc {
|
for k := range sc {
|
||||||
switch k {
|
switch k {
|
||||||
|
|
@ -155,33 +78,25 @@ func (sc serverConfig) Server(ref *ServerRef) (s *Server, err error) {
|
||||||
if v, ok := sc[k].(string); ok {
|
if v, ok := sc[k].(string); ok {
|
||||||
s.Name = v
|
s.Name = v
|
||||||
}
|
}
|
||||||
case "cicustom":
|
case "ciuserdata":
|
||||||
if _, ok := sc[k].(string); ok {
|
if v, ok := sc[k].(string); ok {
|
||||||
s.cicustom = true
|
s.UserData, err = parseUserData(v)
|
||||||
}
|
}
|
||||||
case "memory":
|
case "memory":
|
||||||
if v, ok := sc[k].(float64); ok {
|
if v, ok := sc[k].(float64); ok {
|
||||||
s.Resources.Memory = Memory(uint64(v) / 1024)
|
s.Resources.Memory = int64(v)
|
||||||
}
|
}
|
||||||
case "cores":
|
case "cores":
|
||||||
if v, ok := sc[k].(float64); ok {
|
if v, ok := sc[k].(float64); ok {
|
||||||
s.Resources.Cores = Cores(uint64(v))
|
s.Resources.Cores = int64(v)
|
||||||
}
|
}
|
||||||
case "bootdisk":
|
case "bootdisk":
|
||||||
if v, ok := sc[k].(string); ok {
|
if v, ok := sc[k].(string); ok {
|
||||||
s.Resources.Disk.Name = v
|
s.Resources.BootDisk = v
|
||||||
if val, ok := sc[v].(string); ok {
|
if val, ok := sc[v].(string); ok {
|
||||||
vals := strings.Split(val, ",")
|
for _, o := range strings.Split(val, ",") {
|
||||||
if len(vals) > 0 {
|
|
||||||
storage := strings.Split(vals[0], ":")
|
|
||||||
if len(storage) > 0 {
|
|
||||||
s.Resources.Disk.Storage = storage[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, o := range vals {
|
|
||||||
if strings.HasPrefix(o, "size=") {
|
if strings.HasPrefix(o, "size=") {
|
||||||
size, _ := strconv.ParseUint(o[5:], 10, 64)
|
s.Resources.BootDiskSize = o[5:]
|
||||||
s.Resources.Disk.Size = size
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,23 +140,18 @@ type ServerClient struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) NextID(ctx context.Context) (id int, err error) {
|
func (c *ServerClient) NextID(ctx context.Context) (id string, err error) {
|
||||||
id = InvalidID
|
|
||||||
req, err := c.client.NewRequest(ctx, "GET", "/cluster/nextid", nil)
|
req, err := c.client.NewRequest(ctx, "GET", "/cluster/nextid", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var sid string
|
_, err = c.client.Do(req, &id)
|
||||||
_, err = c.client.Do(req, &sid)
|
return
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return strconv.Atoi(sid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) getConfig(ctx context.Context, ref *ServerRef) (cfg serverConfig, err error) {
|
func (c *ServerClient) getConfig(ctx context.Context, node, id string) (cfg serverConfig, err error) {
|
||||||
cfg = make(serverConfig)
|
cfg = make(serverConfig)
|
||||||
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%d/config?current=1", ref.Node, ref.ID), nil)
|
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%s/config", node, id), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -249,17 +159,21 @@ func (c *ServerClient) getConfig(ctx context.Context, ref *ServerRef) (cfg serve
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) getServer(ctx context.Context, ref *ServerRef) (s *Server, cfg serverConfig, err error) {
|
func (c *ServerClient) getServer(ctx context.Context, node, id string) (s *Server, cfg serverConfig, err error) {
|
||||||
cfg, err = c.getConfig(ctx, ref)
|
cfg, err = c.getConfig(ctx, node, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s, err = cfg.Server(ref)
|
s, err = cfg.Server(node, id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) GetByRef(ctx context.Context, ref *ServerRef) (s *Server, err error) {
|
func (c *ServerClient) GetByURL(ctx context.Context, url string) (s *Server, err error) {
|
||||||
s, cfg, err := c.getServer(ctx, ref)
|
node, id, err := ParseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, cfg, err := c.getServer(ctx, node, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -273,69 +187,25 @@ func (c *ServerClient) GetByRef(ctx context.Context, ref *ServerRef) (s *Server,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) GetByURL(ctx context.Context, url string) (s *Server, err error) {
|
|
||||||
ref, err := ServerRefFromURL(url)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ref.Node == "" && ref.Pool != "" {
|
|
||||||
ref, err = c.client.Pool.FindServerByID(ctx, ref.Pool, ref.ID)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if ref.Node == "" && ref.Pool == "" {
|
|
||||||
ref, err = c.client.Node.FindServerByID(ctx, ref.ID)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s, err = c.GetByRef(ctx, ref)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerTemplateOpts struct {
|
type ServerTemplateOpts struct {
|
||||||
Name string
|
Name string
|
||||||
TemplateID int
|
TemplateURL string
|
||||||
TemplateName string
|
|
||||||
// TemplateNode is optional
|
|
||||||
TemplateNode string
|
|
||||||
Pool string
|
Pool string
|
||||||
TargetStorage string
|
TargetStorage string
|
||||||
TargetNode string
|
TargetNode string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ServerTemplateOpts) Validate(ctx context.Context, c *Client) error {
|
func (o ServerTemplateOpts) Validate() error {
|
||||||
if o.Name == "" {
|
if o.Name == "" {
|
||||||
return errors.New("missing name")
|
return errors.New("missing name")
|
||||||
}
|
}
|
||||||
if ValidateID(o.TemplateID) != nil {
|
if o.TemplateURL == "" {
|
||||||
if o.TemplateName == "" {
|
return errors.New("missing template url")
|
||||||
return errors.New("missing or invalid template id")
|
|
||||||
}
|
|
||||||
ref, err := c.Node.FindServerByName(ctx, o.TemplateName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("template id not found: %w", err)
|
|
||||||
}
|
|
||||||
if !ref.IsTemplate() {
|
|
||||||
return errors.New("server is not a vm template")
|
|
||||||
}
|
|
||||||
o.TemplateID = ref.ID
|
|
||||||
o.TemplateNode = ref.Node
|
|
||||||
}
|
|
||||||
if o.TemplateNode == "" {
|
|
||||||
ref, err := c.Node.FindServerByID(ctx, o.TemplateID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("template node not found err: %w", err)
|
|
||||||
}
|
|
||||||
if ref.Node == "" {
|
|
||||||
return errors.New("missing template node")
|
|
||||||
}
|
|
||||||
o.TemplateNode = ref.Node
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ServerTemplateOpts) body() httpbody {
|
func (o ServerTemplateOpts) body() httpbody {
|
||||||
body := httpbody{"name": o.Name}
|
body := httpbody{"name": o.Name}
|
||||||
if o.TargetStorage != "" {
|
if o.TargetStorage != "" {
|
||||||
body["storage"] = o.TargetStorage
|
body["storage"] = o.TargetStorage
|
||||||
|
|
@ -347,8 +217,12 @@ func (o *ServerTemplateOpts) body() httpbody {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) CreateFromTemplate(ctx context.Context, opts *ServerTemplateOpts) (t *Task, url string, err error) {
|
func (c *ServerClient) CreateFromTemplate(ctx context.Context, opts ServerTemplateOpts) (t *Task, url string, err error) {
|
||||||
err = opts.Validate(ctx, c.client)
|
err = opts.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempnode, id, err := ParseURL(opts.TemplateURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -357,14 +231,14 @@ func (c *ServerClient) CreateFromTemplate(ctx context.Context, opts *ServerTempl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body := opts.body()
|
body := opts.body()
|
||||||
body["newid"] = strconv.Itoa(nextid)
|
body["newid"] = nextid
|
||||||
node := opts.TemplateNode
|
if opts.TargetNode != "" && tempnode != opts.TargetNode {
|
||||||
if opts.TargetNode != "" && opts.TemplateNode != opts.TargetNode {
|
|
||||||
node = opts.TargetNode
|
|
||||||
body["target"] = opts.TargetNode
|
body["target"] = opts.TargetNode
|
||||||
|
url = NewURL(opts.TargetNode, nextid)
|
||||||
|
} else {
|
||||||
|
url = NewURL(tempnode, nextid)
|
||||||
}
|
}
|
||||||
url = NewURL(opts.Pool, node, nextid)
|
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/nodes/%s/qemu/%s/clone", tempnode, id), body.Reader())
|
||||||
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/nodes/%s/qemu/%d/clone", opts.TemplateNode, opts.TemplateID), body.Reader())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -374,36 +248,36 @@ func (c *ServerClient) CreateFromTemplate(ctx context.Context, opts *ServerTempl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Start(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Start(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "start")
|
return c.setStatus(ctx, s, "start")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Reboot(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Reboot(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "reboot")
|
return c.setStatus(ctx, s, "reboot")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Shutdown(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Shutdown(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "shutdown")
|
return c.setStatus(ctx, s, "shutdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Reset(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Reset(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "reset")
|
return c.setStatus(ctx, s, "reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Suspend(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Suspend(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "suspent")
|
return c.setStatus(ctx, s, "suspent")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Resume(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Resume(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "resume")
|
return c.setStatus(ctx, s, "resume")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Stop(ctx context.Context, ref *ServerRef) (t *Task, err error) {
|
func (c *ServerClient) Stop(ctx context.Context, s *Server) (t *Task, err error) {
|
||||||
return c.setStatus(ctx, ref, "stop")
|
return c.setStatus(ctx, s, "stop")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) setStatus(ctx context.Context, ref *ServerRef, status string) (t *Task, err error) {
|
func (c *ServerClient) setStatus(ctx context.Context, s *Server, status string) (t *Task, err error) {
|
||||||
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/nodes/%s/qemu/%d/status/%s", ref.Node, ref.ID, status), nil)
|
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/nodes/%s/qemu/%s/status/%s", s.Node, s.ID, status), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -420,7 +294,7 @@ type statusObj struct {
|
||||||
func (c *ServerClient) GetStatus(ctx context.Context, s *Server, lock *string) (err error) {
|
func (c *ServerClient) GetStatus(ctx context.Context, s *Server, lock *string) (err error) {
|
||||||
var lockval string
|
var lockval string
|
||||||
if lock == nil {
|
if lock == nil {
|
||||||
cfg, err := c.getConfig(ctx, s.Ref())
|
cfg, err := c.getConfig(ctx, s.Node, s.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +305,7 @@ func (c *ServerClient) GetStatus(ctx context.Context, s *Server, lock *string) (
|
||||||
lockval = *lock
|
lockval = *lock
|
||||||
}
|
}
|
||||||
var status statusObj
|
var status statusObj
|
||||||
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%d/status/current", s.Node, s.ID), nil)
|
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%s/status/current", s.Node, s.ID), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Status = ServerStatusUnknown
|
s.Status = ServerStatusUnknown
|
||||||
return
|
return
|
||||||
|
|
@ -445,9 +319,15 @@ func (c *ServerClient) GetStatus(ctx context.Context, s *Server, lock *string) (
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func diffBody(cur, n *Server) (b httpbody) {
|
func diffBody(cur, n *Server) (b httpbody, err error) {
|
||||||
b = n.body()
|
b, err = n.body()
|
||||||
curbody := cur.body()
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curbody, err := cur.body()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
for k, v := range b {
|
for k, v := range b {
|
||||||
if curbody[k] == v {
|
if curbody[k] == v {
|
||||||
delete(b, k)
|
delete(b, k)
|
||||||
|
|
@ -457,7 +337,7 @@ func diffBody(cur, n *Server) (b httpbody) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) updateConfig(ctx context.Context, s *Server, body httpbody) (err error) {
|
func (c *ServerClient) updateConfig(ctx context.Context, s *Server, body httpbody) (err error) {
|
||||||
req, err := c.client.NewRequest(ctx, "PUT", fmt.Sprintf("/nodes/%s/qemu/%d/config", s.Node, s.ID), body.Reader())
|
req, err := c.client.NewRequest(ctx, "PUT", fmt.Sprintf("/nodes/%s/qemu/%s/config", s.Node, s.ID), body.Reader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -467,10 +347,10 @@ func (c *ServerClient) updateConfig(ctx context.Context, s *Server, body httpbod
|
||||||
|
|
||||||
func (c *ServerClient) resizeDisk(ctx context.Context, s *Server) (err error) {
|
func (c *ServerClient) resizeDisk(ctx context.Context, s *Server) (err error) {
|
||||||
body := httpbody{
|
body := httpbody{
|
||||||
"disk": s.Resources.Disk.Name,
|
"disk": s.Resources.BootDisk,
|
||||||
"size": s.Resources.Disk.String(),
|
"size": s.Resources.BootDiskSize,
|
||||||
}
|
}
|
||||||
req, err := c.client.NewRequest(ctx, "PUT", fmt.Sprintf("/nodes/%s/qemu/%d/resize", s.Node, s.ID), body.Reader())
|
req, err := c.client.NewRequest(ctx, "PUT", fmt.Sprintf("/nodes/%s/qemu/%s/resize", s.Node, s.ID), body.Reader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -478,47 +358,38 @@ func (c *ServerClient) resizeDisk(ctx context.Context, s *Server) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Update(ctx context.Context, s *Server) (t *Task, err error) {
|
func (c *ServerClient) Update(ctx context.Context, s *Server) (err error) {
|
||||||
cur, _, err := c.getServer(ctx, s.Ref())
|
cur, _, err := c.getServer(ctx, s.Node, s.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body := diffBody(cur, s)
|
body, err := diffBody(cur, s)
|
||||||
var e []string
|
if err != nil {
|
||||||
if s.UserData != nil {
|
return
|
||||||
b, userdata := s.UserData.Bytes()
|
|
||||||
if userdata == nil {
|
|
||||||
t, userdata = c.client.Snippet.Create(ctx, s.Node, s.UserDataStorage, s.UserDataSnippetName(), b)
|
|
||||||
}
|
|
||||||
if userdata != nil {
|
|
||||||
e = append(e, fmt.Sprintf("userdata: %s", userdata))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
cfg error
|
||||||
|
resize error
|
||||||
|
)
|
||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
cfg := c.updateConfig(ctx, s, body)
|
cfg = c.updateConfig(ctx, s, body)
|
||||||
if cfg != nil {
|
|
||||||
e = append(e, fmt.Sprintf("config: %s", cfg))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if cur.Resources.Disk.Size != s.Resources.Disk.Size {
|
if cur.Resources.BootDiskSize != s.Resources.BootDiskSize {
|
||||||
resize := c.resizeDisk(ctx, s)
|
resize = c.resizeDisk(ctx, s)
|
||||||
if resize != nil {
|
|
||||||
e = append(e, fmt.Sprintf("resize: %s", resize))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(e) > 0 {
|
if cfg != nil && resize != nil {
|
||||||
err = fmt.Errorf("%s", strings.Join(e, ""))
|
return fmt.Errorf("config: %s resize: %s", cfg, resize)
|
||||||
}
|
} else if cfg != nil {
|
||||||
if t == nil {
|
return fmt.Errorf("config: %s", cfg)
|
||||||
dummy := DummyTask
|
} else if resize != nil {
|
||||||
t = &dummy
|
return fmt.Errorf("resize: %s", resize)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerClient) Delete(ctx context.Context, s *Server, f OnTaskChange) (t *Task, err error) {
|
func (c *ServerClient) Delete(ctx context.Context, s *Server, f OnTaskChange) (t *Task, err error) {
|
||||||
if s.Status == ServerStatusRunning || s.Status == ServerStatusUnknown {
|
if s.Status == ServerStatusRunning || s.Status == ServerStatusUnknown {
|
||||||
task, err := c.Stop(ctx, s.Ref())
|
task, err := c.Stop(ctx, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -527,7 +398,7 @@ func (c *ServerClient) Delete(ctx context.Context, s *Server, f OnTaskChange) (t
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/nodes/%s/qemu/%d", s.Node, s.ID), nil)
|
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/nodes/%s/qemu/%s", s.Node, s.ID), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,6 @@ const (
|
||||||
ServerStatusSespending ServerStatus = "suspending"
|
ServerStatusSespending ServerStatus = "suspending"
|
||||||
ServerStatusSespended ServerStatus = "suspended"
|
ServerStatusSespended ServerStatus = "suspended"
|
||||||
ServerStatusResuming ServerStatus = "resuming"
|
ServerStatusResuming ServerStatus = "resuming"
|
||||||
|
|
||||||
ServerStatusError ServerStatus = "error"
|
|
||||||
ServerStatusScheduleError ServerStatus = "schedule-error"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServerStatusFromLock(status, lock string) ServerStatus {
|
func ServerStatusFromLock(status, lock string) ServerStatus {
|
||||||
|
|
@ -64,6 +61,7 @@ func ServerStatusFromTask(t *Task) ServerStatus {
|
||||||
if t.Exitstatus == TaskExitStatusOK {
|
if t.Exitstatus == TaskExitStatusOK {
|
||||||
return ServerStatusSespended
|
return ServerStatusSespended
|
||||||
}
|
}
|
||||||
|
return ServerStatusUnknown
|
||||||
case "qmresume":
|
case "qmresume":
|
||||||
if t.Status == TaskStatusRunning {
|
if t.Status == TaskStatusRunning {
|
||||||
return ServerStatusResuming
|
return ServerStatusResuming
|
||||||
|
|
@ -71,6 +69,7 @@ func ServerStatusFromTask(t *Task) ServerStatus {
|
||||||
if t.Exitstatus == TaskExitStatusOK {
|
if t.Exitstatus == TaskExitStatusOK {
|
||||||
return ServerStatusRunning
|
return ServerStatusRunning
|
||||||
}
|
}
|
||||||
|
return ServerStatusUnknown
|
||||||
case "qmclone", "qmcreate":
|
case "qmclone", "qmcreate":
|
||||||
if t.Status == TaskStatusRunning {
|
if t.Status == TaskStatusRunning {
|
||||||
return ServerStatusInitializing
|
return ServerStatusInitializing
|
||||||
|
|
@ -78,6 +77,7 @@ func ServerStatusFromTask(t *Task) ServerStatus {
|
||||||
if t.Exitstatus == TaskExitStatusOK {
|
if t.Exitstatus == TaskExitStatusOK {
|
||||||
return ServerStatusStopped
|
return ServerStatusStopped
|
||||||
}
|
}
|
||||||
|
return ServerStatusUnknown
|
||||||
case "qmreboot", "qmreset":
|
case "qmreboot", "qmreset":
|
||||||
if t.Status == TaskStatusRunning {
|
if t.Status == TaskStatusRunning {
|
||||||
return ServerStatusRebooting
|
return ServerStatusRebooting
|
||||||
|
|
@ -85,6 +85,7 @@ func ServerStatusFromTask(t *Task) ServerStatus {
|
||||||
if t.Exitstatus == TaskExitStatusOK {
|
if t.Exitstatus == TaskExitStatusOK {
|
||||||
return ServerStatusRunning
|
return ServerStatusRunning
|
||||||
}
|
}
|
||||||
|
return ServerStatusUnknown
|
||||||
case "qmstart":
|
case "qmstart":
|
||||||
if t.Status == TaskStatusRunning {
|
if t.Status == TaskStatusRunning {
|
||||||
return ServerStatusStarting
|
return ServerStatusStarting
|
||||||
|
|
@ -100,10 +101,12 @@ func ServerStatusFromTask(t *Task) ServerStatus {
|
||||||
if t.Exitstatus == TaskExitStatusOK {
|
if t.Exitstatus == TaskExitStatusOK {
|
||||||
return ServerStatusStopped
|
return ServerStatusStopped
|
||||||
}
|
}
|
||||||
|
return ServerStatusUnknown
|
||||||
case "qmdestroy":
|
case "qmdestroy":
|
||||||
if t.Status == TaskStatusRunning {
|
if t.Status == TaskStatusRunning {
|
||||||
return ServerStatusDeleting
|
return ServerStatusDeleting
|
||||||
}
|
}
|
||||||
|
return ServerStatusUnknown
|
||||||
}
|
}
|
||||||
return ServerStatusUnknown
|
return ServerStatusUnknown
|
||||||
}
|
}
|
||||||
|
|
|
||||||
85
snippet.go
85
snippet.go
|
|
@ -1,85 +0,0 @@
|
||||||
// Copyright (C) 2020 Marius Schellenberger
|
|
||||||
|
|
||||||
package pve
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoSnippetNode = errors.New("missing snippet target node")
|
|
||||||
ErrNoSnippetStorage = errors.New("missing snippet target storage")
|
|
||||||
ErrNoSnippetName = errors.New("missing snippet name")
|
|
||||||
)
|
|
||||||
|
|
||||||
type SnippetClient struct {
|
|
||||||
client *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SnippetClient) Create(ctx context.Context, node, storage, name string, data []byte) (t *Task, err error) {
|
|
||||||
if node == "" {
|
|
||||||
err = ErrNoSnippetNode
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if storage == "" {
|
|
||||||
err = ErrNoSnippetStorage
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if name == "" {
|
|
||||||
err = ErrNoSnippetName
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ub, b := newUnreadBuffer()
|
|
||||||
m := multipart.NewWriter(b)
|
|
||||||
err = m.WriteField("content", "snippets")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fw, err := m.CreateFormFile("filename", name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = fw.Write(data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.Close()
|
|
||||||
ub.Save()
|
|
||||||
ct := m.FormDataContentType()
|
|
||||||
var taskid string
|
|
||||||
var req *http.Request
|
|
||||||
var resp *http.Response
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
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 || resp.StatusCode != http.StatusOK {
|
|
||||||
ub.Reset(&b)
|
|
||||||
time.Sleep(time.Millisecond * 500)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
t = c.client.Task.MustGet(ctx, taskid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func SnippetVolume(storage, name string) string {
|
|
||||||
return fmt.Sprintf("%s:snippets/%s", storage, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SnippetClient) Delete(ctx context.Context, node, storage, name string) (t *Task, err error) {
|
|
||||||
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 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var taskid string
|
|
||||||
_, err = c.client.Do(req, &taskid)
|
|
||||||
t = c.client.Task.MustGet(ctx, taskid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
9
task.go
9
task.go
|
|
@ -16,12 +16,8 @@ const (
|
||||||
TaskExitStatusOK = "OK"
|
TaskExitStatusOK = "OK"
|
||||||
TaskStatusRunning = "running"
|
TaskStatusRunning = "running"
|
||||||
TaskStatusStopped = "stopped"
|
TaskStatusStopped = "stopped"
|
||||||
|
|
||||||
DummyTaskID = "dummy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var DummyTask = Task{ID: DummyTaskID}
|
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
ID string `json:"upid"`
|
ID string `json:"upid"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
@ -67,9 +63,6 @@ func (c *TaskClient) Get(ctx context.Context, taskid string) (t *Task, err error
|
||||||
type OnTaskChange func(t *Task)
|
type OnTaskChange func(t *Task)
|
||||||
|
|
||||||
func (c *TaskClient) Wait(ctx context.Context, t *Task, f OnTaskChange) error {
|
func (c *TaskClient) Wait(ctx context.Context, t *Task, f OnTaskChange) error {
|
||||||
if t.ID == DummyTaskID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if f != nil && t.Status != "" {
|
if f != nil && t.Status != "" {
|
||||||
f(t)
|
f(t)
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +90,7 @@ func (c *TaskClient) Wait(ctx context.Context, t *Task, f OnTaskChange) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(TaskStatusCheckInterval * time.Second)
|
time.Sleep(TaskStatusCheckInterval * time.Second)
|
||||||
waited += TaskStatusCheckInterval
|
waited = waited + TaskStatusCheckInterval
|
||||||
}
|
}
|
||||||
return errors.New("task wait timeout for: " + t.ID)
|
return errors.New("task wait timeout for: " + t.ID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
75
userdata.go
75
userdata.go
|
|
@ -3,49 +3,31 @@
|
||||||
package pve
|
package pve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cloudConfig = []byte("#cloud-config\n")
|
func parseUserData(s string) (u *UserData, err error) {
|
||||||
|
s, err = url.QueryUnescape(s)
|
||||||
const (
|
if err != nil {
|
||||||
minBytes = 87
|
return
|
||||||
pad = "########################################################################\n"
|
}
|
||||||
)
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
//TODO
|
return
|
||||||
//func parseUserData(s string) (u *UserData, err error) {
|
}
|
||||||
// s, err = url.QueryUnescape(s)
|
return UnmarshalUserData(b)
|
||||||
// if err != nil {
|
}
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// b, err := base64.StdEncoding.DecodeString(s)
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// return UnmarshalUserData(b)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func UnmarshalUserData(b []byte) (u *UserData, err error) {
|
func UnmarshalUserData(b []byte) (u *UserData, err error) {
|
||||||
u = new(UserData)
|
u = new(UserData)
|
||||||
err = yaml.Unmarshal(b, u)
|
err = yaml.Unmarshal(b, u)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func MarshalUserData(u *UserData) ([]byte, error) {
|
||||||
//TODO
|
return yaml.Marshal(u)
|
||||||
//func MarshalUserData(u *UserData) ([]byte, error) {
|
}
|
||||||
// if u == nil {
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
// b, err := yaml.Marshal(u)
|
|
||||||
// if err == nil {
|
|
||||||
// if len(b)+len(cloudConfig) < minBytes {
|
|
||||||
// b = append([]byte(pad[len(b):]), b...)
|
|
||||||
// }
|
|
||||||
// b = append(cloudConfig, b...)
|
|
||||||
// }
|
|
||||||
// return b, err
|
|
||||||
//}
|
|
||||||
|
|
||||||
type UserData struct {
|
type UserData struct {
|
||||||
Hostname string `yaml:"hostname"`
|
Hostname string `yaml:"hostname"`
|
||||||
|
|
@ -62,33 +44,12 @@ type UserData struct {
|
||||||
WriteFiles []File `yaml:"write_files,omitempty"`
|
WriteFiles []File `yaml:"write_files,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
// func (u *UserData) String() (s string, err error) {
|
|
||||||
// b, err := MarshalUserData(u)
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// s = url.QueryEscape(base64.StdEncoding.EncodeToString(b))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (u *UserData) String() (s string, err error) {
|
func (u *UserData) String() (s string, err error) {
|
||||||
b, err := u.Bytes()
|
b, err := MarshalUserData(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s = string(b)
|
s = base64.StdEncoding.EncodeToString(b)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserData) Bytes() (b []byte, err error) {
|
|
||||||
b, err = yaml.Marshal(u)
|
|
||||||
if err == nil {
|
|
||||||
if len(b)+len(cloudConfig) < minBytes {
|
|
||||||
b = append([]byte(pad[len(b):]), b...)
|
|
||||||
}
|
|
||||||
b = append(cloudConfig, b...)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue