2022-06-29 21:15:56 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2023-06-09 16:11:52 +08:00
|
|
|
"errors"
|
2022-06-29 21:15:56 +08:00
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
lxd "github.com/lxc/lxd/client"
|
|
|
|
|
"github.com/lxc/lxd/shared/api"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
timeout = 120
|
|
|
|
|
killTimeout = 5
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type BytesBuffer struct {
|
|
|
|
|
*bytes.Buffer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *BytesBuffer) Close() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type LXDClient struct {
|
|
|
|
|
Client lxd.InstanceServer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Connect to the LXD socket.
|
|
|
|
|
func (c *LXDClient) Connect() error {
|
|
|
|
|
client, err := lxd.ConnectLXDUnix("", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
client, err = lxd.ConnectLXDUnix("/var/snap/lxd/common/lxd/unix.socket", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Client = client
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a container using a specific remote and alias.
|
|
|
|
|
func (c *LXDClient) Create(name string, remote string, alias string) error {
|
|
|
|
|
fmt.Printf("creating %s with %s:%s\n", name, remote, alias)
|
|
|
|
|
|
|
|
|
|
if c.Client == nil {
|
|
|
|
|
err := c.Connect()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
server := ""
|
|
|
|
|
switch remote {
|
|
|
|
|
case "images":
|
|
|
|
|
server = "https://images.linuxcontainers.org"
|
|
|
|
|
case "ubuntu":
|
|
|
|
|
server = "https://cloud-images.ubuntu.com/releases"
|
|
|
|
|
case "ubuntu-daily":
|
|
|
|
|
server = "https://cloud-images.ubuntu.com/daily"
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("unknown remote: %s", remote)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req := api.ContainersPost{
|
|
|
|
|
Name: name,
|
|
|
|
|
Source: api.ContainerSource{
|
|
|
|
|
Type: "image",
|
|
|
|
|
Mode: "pull",
|
|
|
|
|
Protocol: "simplestreams",
|
|
|
|
|
Server: server,
|
|
|
|
|
Alias: alias,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get LXD to create the container (background operation)
|
|
|
|
|
op, err := c.Client.CreateContainer(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait for the operation to complete
|
|
|
|
|
err = op.Wait()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete the given container.
|
|
|
|
|
func (c *LXDClient) Delete(name string) error {
|
|
|
|
|
fmt.Println("deleting", name)
|
|
|
|
|
op, err := c.Client.DeleteContainer(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return op.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run a command returning result struct. Will kill commands that take longer
|
|
|
|
|
// than 120 seconds.
|
|
|
|
|
func (c *LXDClient) Exec(name string, command ...string) error {
|
|
|
|
|
fmt.Printf("$ %s\n", strings.Join(command, " "))
|
|
|
|
|
|
|
|
|
|
cmd := []string{"/usr/bin/timeout", "-k", strconv.Itoa(killTimeout), strconv.Itoa(timeout)}
|
|
|
|
|
cmd = append(cmd, command...)
|
|
|
|
|
req := api.ContainerExecPost{
|
|
|
|
|
Command: cmd,
|
|
|
|
|
WaitForWS: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output := &BytesBuffer{bytes.NewBuffer(nil)}
|
|
|
|
|
|
|
|
|
|
args := lxd.ContainerExecArgs{
|
|
|
|
|
Stdout: output,
|
|
|
|
|
Stderr: output,
|
|
|
|
|
DataDone: make(chan bool),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
op, err := c.Client.ExecContainer(name, req, &args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = op.Wait()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait for any remaining I/O to be flushed
|
|
|
|
|
<-args.DataDone
|
|
|
|
|
|
|
|
|
|
// get the return code
|
|
|
|
|
opAPI := op.Get()
|
|
|
|
|
rc := int(opAPI.Metadata["return"].(float64))
|
|
|
|
|
|
|
|
|
|
if rc != 0 {
|
2023-06-09 16:11:52 +08:00
|
|
|
return errors.New(output.String())
|
2022-06-29 21:15:56 +08:00
|
|
|
}
|
|
|
|
|
|
2022-11-15 04:41:22 +08:00
|
|
|
fmt.Println(output.String())
|
|
|
|
|
|
2022-06-29 21:15:56 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Push file to container.
|
|
|
|
|
func (c *LXDClient) Push(name string, src string, dst string) error {
|
|
|
|
|
fmt.Printf("cp %s %s%s\n", src, name, dst)
|
|
|
|
|
f, err := os.Open(src)
|
|
|
|
|
if err != nil {
|
2023-02-22 19:57:53 +08:00
|
|
|
return fmt.Errorf("error reading %s: %w", src, err)
|
2022-06-29 21:15:56 +08:00
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
return c.Client.CreateContainerFile(name, dst, lxd.ContainerFileArgs{
|
|
|
|
|
Content: f,
|
|
|
|
|
Mode: 0644,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start the given container.
|
|
|
|
|
func (c *LXDClient) Start(name string) error {
|
|
|
|
|
fmt.Println("starting", name)
|
|
|
|
|
reqState := api.ContainerStatePut{
|
|
|
|
|
Action: "start",
|
|
|
|
|
Timeout: -1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
op, err := c.Client.UpdateContainerState(name, reqState, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return op.Wait()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop the given container.
|
|
|
|
|
func (c *LXDClient) Stop(name string) error {
|
|
|
|
|
fmt.Println("stopping", name)
|
|
|
|
|
reqState := api.ContainerStatePut{
|
|
|
|
|
Action: "stop",
|
|
|
|
|
Force: true,
|
|
|
|
|
Timeout: 10,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
op, err := c.Client.UpdateContainerState(name, reqState, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return op.Wait()
|
|
|
|
|
}
|