mirror of https://github.com/containers/podman.git
commit
db35674873
|
@ -1092,3 +1092,11 @@ func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete str
|
|||
}
|
||||
return completeKeyValues(toComplete, kv)
|
||||
}
|
||||
|
||||
// AutocompleteMachineSSH - Autocomplete machine ssh command.
|
||||
func AutocompleteMachineSSH(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// +build amd64,linux amd64,darwin arm64,darwin
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/machine"
|
||||
"github.com/containers/podman/v3/pkg/machine/qemu"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
createCmd = &cobra.Command{
|
||||
Use: "create [options] [NAME]",
|
||||
Short: "Create a vm",
|
||||
Long: "Create a virtual machine for Podman to run on. Virtual machines are used to run Podman.",
|
||||
RunE: create,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Example: `podman machine create myvm`,
|
||||
ValidArgsFunction: completion.AutocompleteNone,
|
||||
}
|
||||
)
|
||||
|
||||
type CreateCLIOptions struct {
|
||||
CPUS uint64
|
||||
Memory uint64
|
||||
Devices []string
|
||||
ImagePath string
|
||||
IgnitionPath string
|
||||
Name string
|
||||
}
|
||||
|
||||
var (
|
||||
createOpts = CreateCLIOptions{}
|
||||
defaultMachineName string = "podman-machine-default"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: createCmd,
|
||||
Parent: machineCmd,
|
||||
})
|
||||
flags := createCmd.Flags()
|
||||
|
||||
cpusFlagName := "cpus"
|
||||
flags.Uint64Var(
|
||||
&createOpts.CPUS,
|
||||
cpusFlagName, 1,
|
||||
"Number of CPUs. The default is 0.000 which means no limit",
|
||||
)
|
||||
_ = createCmd.RegisterFlagCompletionFunc(cpusFlagName, completion.AutocompleteNone)
|
||||
|
||||
memoryFlagName := "memory"
|
||||
flags.Uint64VarP(
|
||||
&createOpts.Memory,
|
||||
memoryFlagName, "m", 2048,
|
||||
"Memory (in MB)",
|
||||
)
|
||||
_ = createCmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone)
|
||||
|
||||
ImagePathFlagName := "image-path"
|
||||
flags.StringVar(&createOpts.ImagePath, ImagePathFlagName, "", "Path to qcow image")
|
||||
_ = createCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault)
|
||||
|
||||
IgnitionPathFlagName := "ignition-path"
|
||||
flags.StringVar(&createOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file")
|
||||
_ = createCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault)
|
||||
}
|
||||
|
||||
// TODO should we allow for a users to append to the qemu cmdline?
|
||||
func create(cmd *cobra.Command, args []string) error {
|
||||
createOpts.Name = defaultMachineName
|
||||
if len(args) > 0 {
|
||||
createOpts.Name = args[0]
|
||||
}
|
||||
vmOpts := machine.CreateOptions{
|
||||
CPUS: createOpts.CPUS,
|
||||
Memory: createOpts.Memory,
|
||||
IgnitionPath: createOpts.IgnitionPath,
|
||||
ImagePath: createOpts.ImagePath,
|
||||
Name: createOpts.Name,
|
||||
}
|
||||
var (
|
||||
vm machine.VM
|
||||
vmType string
|
||||
err error
|
||||
)
|
||||
switch vmType {
|
||||
default: // qemu is the default
|
||||
vm, err = qemu.NewMachine(vmOpts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return vm.Create(vmOpts)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// +build amd64,linux amd64,darwin arm64,darwin
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/cmd/podman/validate"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
noOp = func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
// Command: podman _machine_
|
||||
machineCmd = &cobra.Command{
|
||||
Use: "machine",
|
||||
Short: "Manage a virtual machine",
|
||||
Long: "Manage a virtual machine. Virtual machines are used to run Podman.",
|
||||
PersistentPreRunE: noOp,
|
||||
PersistentPostRunE: noOp,
|
||||
RunE: validate.SubCommandExists,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: machineCmd,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// +build !amd64 arm64,linux amd64,windows
|
||||
|
||||
package machine
|
||||
|
||||
func init() {}
|
|
@ -0,0 +1,88 @@
|
|||
// +build amd64,linux amd64,darwin arm64,darwin
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/machine"
|
||||
"github.com/containers/podman/v3/pkg/machine/qemu"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
removeCmd = &cobra.Command{
|
||||
Use: "remove [options] NAME",
|
||||
Short: "Remove an existing machine",
|
||||
Long: "Remove an existing machine ",
|
||||
RunE: remove,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: `podman machine remove myvm`,
|
||||
ValidArgsFunction: completion.AutocompleteNone,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
destoryOptions machine.RemoveOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: removeCmd,
|
||||
Parent: machineCmd,
|
||||
})
|
||||
|
||||
flags := removeCmd.Flags()
|
||||
formatFlagName := "force"
|
||||
flags.BoolVar(&destoryOptions.Force, formatFlagName, false, "Do not prompt before removeing")
|
||||
|
||||
keysFlagName := "save-keys"
|
||||
flags.BoolVar(&destoryOptions.SaveKeys, keysFlagName, false, "Do not delete SSH keys")
|
||||
|
||||
ignitionFlagName := "save-ignition"
|
||||
flags.BoolVar(&destoryOptions.SaveIgnition, ignitionFlagName, false, "Do not delete ignition file")
|
||||
|
||||
imageFlagName := "save-image"
|
||||
flags.BoolVar(&destoryOptions.SaveImage, imageFlagName, false, "Do not delete the image file")
|
||||
}
|
||||
|
||||
func remove(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
vm machine.VM
|
||||
vmType string
|
||||
)
|
||||
switch vmType {
|
||||
default:
|
||||
vm, err = qemu.LoadVMByName(args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmationMessage, doIt, err := vm.Remove(args[0], machine.RemoveOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !destoryOptions.Force {
|
||||
// Warn user
|
||||
fmt.Println(confirmationMessage)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Are you sure you want to continue? [y/N] ")
|
||||
answer, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.ToLower(answer)[0] != 'y' {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return doIt()
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// +build amd64,linux amd64,darwin arm64,darwin
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/machine"
|
||||
"github.com/containers/podman/v3/pkg/machine/qemu"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
sshCmd = &cobra.Command{
|
||||
Use: "ssh [options] NAME [COMMAND [ARG ...]]",
|
||||
Short: "SSH into a virtual machine",
|
||||
Long: "SSH into a virtual machine ",
|
||||
RunE: ssh,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Example: `podman machine ssh myvm
|
||||
podman machine ssh -e myvm echo hello`,
|
||||
|
||||
ValidArgsFunction: common.AutocompleteMachineSSH,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
sshOpts machine.SSHOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: sshCmd,
|
||||
Parent: machineCmd,
|
||||
})
|
||||
|
||||
flags := sshCmd.Flags()
|
||||
executeFlagName := "execute"
|
||||
flags.BoolVarP(&sshOpts.Execute, executeFlagName, "e", false, "Execute command from args")
|
||||
}
|
||||
|
||||
func ssh(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
vm machine.VM
|
||||
vmType string
|
||||
)
|
||||
sshOpts.Args = args[1:]
|
||||
|
||||
// Error if no execute but args given
|
||||
if !sshOpts.Execute && len(sshOpts.Args) > 0 {
|
||||
return errors.New("too many args: to execute commands via ssh, use -e flag")
|
||||
}
|
||||
// Error if execute but no args given
|
||||
if sshOpts.Execute && len(sshOpts.Args) < 1 {
|
||||
return errors.New("must proivde at least one command to execute")
|
||||
}
|
||||
|
||||
switch vmType {
|
||||
default:
|
||||
vm, err = qemu.LoadVMByName(args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "vm %s not found", args[0])
|
||||
}
|
||||
return vm.SSH(args[0], sshOpts)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// +build amd64,linux amd64,darwin arm64,darwin
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/machine"
|
||||
"github.com/containers/podman/v3/pkg/machine/qemu"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
startCmd = &cobra.Command{
|
||||
Use: "start NAME",
|
||||
Short: "Start an existing machine",
|
||||
Long: "Start an existing machine ",
|
||||
RunE: start,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: `podman machine start myvm`,
|
||||
ValidArgsFunction: completion.AutocompleteNone,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: startCmd,
|
||||
Parent: machineCmd,
|
||||
})
|
||||
}
|
||||
|
||||
func start(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
vm machine.VM
|
||||
vmType string
|
||||
)
|
||||
switch vmType {
|
||||
default:
|
||||
vm, err = qemu.LoadVMByName(args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return vm.Start(args[0], machine.StartOptions{})
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// +build amd64,linux amd64,darwin arm64,darwin
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/machine"
|
||||
"github.com/containers/podman/v3/pkg/machine/qemu"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
stopCmd = &cobra.Command{
|
||||
Use: "stop NAME",
|
||||
Short: "Stop an existing machine",
|
||||
Long: "Stop an existing machine ",
|
||||
RunE: stop,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: `podman machine stop myvm`,
|
||||
ValidArgsFunction: completion.AutocompleteNone,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||
Command: stopCmd,
|
||||
Parent: machineCmd,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Name shouldnt be required, need to create a default vm
|
||||
func stop(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
vm machine.VM
|
||||
vmType string
|
||||
)
|
||||
switch vmType {
|
||||
default:
|
||||
vm, err = qemu.LoadVMByName(args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return vm.Stop(args[0], machine.StopOptions{})
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
_ "github.com/containers/podman/v3/cmd/podman/generate"
|
||||
_ "github.com/containers/podman/v3/cmd/podman/healthcheck"
|
||||
_ "github.com/containers/podman/v3/cmd/podman/images"
|
||||
_ "github.com/containers/podman/v3/cmd/podman/machine"
|
||||
_ "github.com/containers/podman/v3/cmd/podman/manifest"
|
||||
_ "github.com/containers/podman/v3/cmd/podman/networks"
|
||||
_ "github.com/containers/podman/v3/cmd/podman/play"
|
||||
|
|
|
@ -55,6 +55,8 @@ Commands
|
|||
|
||||
:doc:`logs <markdown/podman-logs.1>` Fetch the logs of a container
|
||||
|
||||
:doc:`machine <markdown/podman-machine.1>` Manage podman's virtual machine
|
||||
|
||||
:doc:`manifest <manifest>` Create and manipulate manifest lists and image indexes
|
||||
|
||||
:doc:`mount <markdown/podman-mount.1>` Mount a working container's root filesystem
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
Machine
|
||||
======
|
||||
|
||||
|
||||
:doc:`create <markdown/podman-machine-create.1>` Create a new virtual machine
|
||||
:doc:`remove <markdown/podman-machine-remove.1>` Remove a virtual machine
|
||||
:doc:`ssh <markdown/podman-machine-ssh.1>` SSH into a virtual machine
|
||||
:doc:`start <markdown/podman-machine-start.1>` Start a virtual machine
|
||||
:doc:`stop <markdown/podman-machine-stop.1>` Stop a virtual machine
|
|
@ -0,0 +1,53 @@
|
|||
% podman-machine-create(1)
|
||||
|
||||
## NAME
|
||||
podman\-machine\-create - Create a new virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine create** [*options*] [*name*]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Creates a new virtual machine for Podman.
|
||||
|
||||
Podman on MacOS requires a virtual machine. This is because containers are Linux -
|
||||
containers do not run on any other OS because containers' core functionality are
|
||||
tied to the Linux kernel.
|
||||
|
||||
**podman machine create** creates a new Linux virtual machine where containers are run.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--cpus**=*number*
|
||||
|
||||
Number of CPUs.
|
||||
|
||||
#### **--ignition-path**
|
||||
|
||||
Fully qualified path of the ignition file
|
||||
|
||||
#### **--image-path**
|
||||
|
||||
Fully qualified path of the uncompressed image file
|
||||
|
||||
#### **--memory**, **-m**=*number*
|
||||
|
||||
Memory (in MB).
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
$ podman machine create myvm
|
||||
$ podman machine create --device=/dev/xvdc:rw myvm
|
||||
$ podman machine create --memory=1024 myvm
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman-machine (1)
|
||||
|
||||
## HISTORY
|
||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
|
@ -0,0 +1,65 @@
|
|||
% podman-machine-remove(1)
|
||||
|
||||
## NAME
|
||||
podman\-machine\-remove - Remove a virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine remove** [*options*] *name*
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Remove a virtual machine and its related files. What is actually deleted
|
||||
depends on the virtual machine type. For all virtual machines, the generated
|
||||
SSH keys and the podman system connection are deleted. The ignition files
|
||||
generated for that VM are also removeed as is its image file on the filesystem.
|
||||
|
||||
Users get a display of what will be deleted and are required to confirm unless the option `--force`
|
||||
is used.
|
||||
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
#### **--force**
|
||||
|
||||
Delete without confirmation
|
||||
|
||||
#### **--save-ignition**
|
||||
|
||||
Do not delete the generated ignition file
|
||||
|
||||
#### **--save-image**
|
||||
|
||||
Do not delete the VM image
|
||||
|
||||
#### **--save-keys**
|
||||
|
||||
Do not delete the SSH keys for the VM. The system connection is always
|
||||
deleted.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Remove a VM named "test1"
|
||||
|
||||
```
|
||||
$ podman machine remove test1
|
||||
|
||||
The following files will be deleted:
|
||||
|
||||
/home/user/.ssh/test1
|
||||
/home/user/.ssh/test1.pub
|
||||
/home/user/.config/containers/podman/machine/qemu/test1.ign
|
||||
/home/user/.local/share/containers/podman/machine/qemu/test1_fedora-coreos-33.20210315.1.0-qemu.x86_64.qcow2
|
||||
/home/user/.config/containers/podman/machine/qemu/test1.json
|
||||
|
||||
Are you sure you want to continue? [y/N] y
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman-machine (1)
|
||||
|
||||
## HISTORY
|
||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
|
@ -0,0 +1,43 @@
|
|||
% podman-machine-ssh(1)
|
||||
|
||||
## NAME
|
||||
podman\-machine\-ssh - SSH into a virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine ssh** [*options*] *name* [*command* [*arg* ...]]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
SSH into a Podman-managed virtual machine.
|
||||
|
||||
Podman on MacOS requires a virtual machine. This is because containers are Linux -
|
||||
containers do not run on any other OS because containers' core functionality are
|
||||
tied to the Linux kernel.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--execute**, **-e**
|
||||
|
||||
Execute the given command on the VM
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
To get an interactive session with a VM called `myvm`:
|
||||
```
|
||||
$ podman machine ssh myvm
|
||||
```
|
||||
|
||||
To run a command on a VM called `myvm`:
|
||||
```
|
||||
$ podman machine ssh -e myvm -- rpm -q podman
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman-machine (1)
|
||||
|
||||
## HISTORY
|
||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
|
@ -0,0 +1,35 @@
|
|||
% podman-machine-start(1)
|
||||
|
||||
## NAME
|
||||
podman\-machine\-start - Start a virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine start** *name*
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Starts a virtual machine for Podman.
|
||||
|
||||
Podman on MacOS requires a virtual machine. This is because containers are Linux -
|
||||
containers do not run on any other OS because containers' core functionality are
|
||||
tied to the Linux kernel.
|
||||
|
||||
**podman machine start** starts a Linux virtual machine where containers are run.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
$ podman machine start myvm
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman-machine (1)
|
||||
|
||||
## HISTORY
|
||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
|
@ -0,0 +1,35 @@
|
|||
% podman-machine-stop(1)
|
||||
|
||||
## NAME
|
||||
podman\-machine\-stop - Stop a virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine stop** *name*
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Stops a virtual machine.
|
||||
|
||||
Podman on MacOS requires a virtual machine. This is because containers are Linux -
|
||||
containers do not run on any other OS because containers' core functionality are
|
||||
tied to the Linux kernel.
|
||||
|
||||
**podman machine stop** stops a Linux virtual machine where containers are run.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
$ podman machine stop myvm
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman-machine (1)
|
||||
|
||||
## HISTORY
|
||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
|
@ -0,0 +1,26 @@
|
|||
% podman-machine(1)
|
||||
|
||||
## NAME
|
||||
podman\-machine - Manage Podman's virtual machine
|
||||
|
||||
## SYNOPSIS
|
||||
**podman machine** *subcommand*
|
||||
|
||||
## DESCRIPTION
|
||||
`podman machine` is a set of subcommands that manage Podman's virtual machine on MacOS.
|
||||
|
||||
## SUBCOMMANDS
|
||||
|
||||
| Command | Man Page | Description |
|
||||
| ------- | ------------------------------------------------------- | ----------------------------- |
|
||||
| create | [podman-machine-create(1)](podman-machine-create.1.md) | Create a new virtual machine |
|
||||
| remove | [podman-machine-destroy(1)](podman-machine-remove.1.md)| Remove a virtual machine |
|
||||
| ssh | [podman-machine-ssh.1.md(1)](podman-machine-ssh.1.md) | SSH into a virtual machine |
|
||||
| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine |
|
||||
| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine |
|
||||
|
||||
## SEE ALSO
|
||||
podman(1)
|
||||
|
||||
## HISTORY
|
||||
March 2021, Originally compiled by Ashley Cui <acui@redhat.com>
|
|
@ -237,6 +237,7 @@ the exit codes follow the `chroot` standard, see below:
|
|||
| [podman-login(1)](podman-login.1.md) | Login to a container registry. |
|
||||
| [podman-logout(1)](podman-logout.1.md) | Logout of a container registry. |
|
||||
| [podman-logs(1)](podman-logs.1.md) | Display the logs of one or more containers. |
|
||||
| [podman-machine(1)](podman-machine.1.md) | Manage Podman's virtual machine |
|
||||
| [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. |
|
||||
| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
|
||||
| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. |
|
||||
|
|
5
go.mod
5
go.mod
|
@ -18,9 +18,11 @@ require (
|
|||
github.com/containers/psgo v1.5.2
|
||||
github.com/containers/storage v1.28.1
|
||||
github.com/coreos/go-systemd/v22 v22.3.0
|
||||
github.com/coreos/stream-metadata-go v0.0.0-20210225230131-70edb9eb47b3
|
||||
github.com/cri-o/ocicni v0.2.1-0.20210301205850-541cf7c703cf
|
||||
github.com/cyphar/filepath-securejoin v0.2.2
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
github.com/docker/docker v20.10.0-beta1.0.20201113105859-b6bfff2a628f+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
|
@ -38,7 +40,6 @@ require (
|
|||
github.com/json-iterator/go v1.1.10
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.10 // indirect
|
||||
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf
|
||||
github.com/mrunalp/fileutils v0.5.0
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
|
@ -52,7 +53,6 @@ require (
|
|||
github.com/opencontainers/selinux v1.8.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rootless-containers/rootlesskit v0.14.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
|
@ -60,6 +60,7 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
||||
github.com/vbauerster/mpb/v6 v6.0.2
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
|
|
18
go.sum
18
go.sum
|
@ -68,6 +68,7 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
|||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
||||
|
@ -209,6 +210,7 @@ github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6E
|
|||
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
|
@ -219,7 +221,10 @@ github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+
|
|||
github.com/coreos/go-systemd/v22 v22.3.0 h1:C8u/Ljj8G8O6rqWJh2J8cDyeEFBMWvXlvJ/ccMyzcqw=
|
||||
github.com/coreos/go-systemd/v22 v22.3.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/stream-metadata-go v0.0.0-20210225230131-70edb9eb47b3 h1:0JspqV66RwYqYfvi8lCUoL5zUZMh9uN4hx/J5+NRXIE=
|
||||
github.com/coreos/stream-metadata-go v0.0.0-20210225230131-70edb9eb47b3/go.mod h1:RTjQyHgO/G37oJ3qnqYK6Z4TPZ5EsaabOtfMjVXmgko=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
|
@ -241,6 +246,10 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l
|
|||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1 h1:j6vGflaQ2T7yOWqVgPdiRF73j/U2Zmpbbzab8nyDCRQ=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1/go.mod h1:QS1XzqZLcDniNYrN7EZefq3wIyb/M2WmJbql4ZKoc1Q=
|
||||
github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f h1:N2HvbwONtcvzegFxOAgGt15JsajIk5QzY3j5X3VzFDI=
|
||||
github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f/go.mod h1:IetBE52JfFxK46p2n2Rqm+p5Gx1gpu2hRHsrbnPOWZQ=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
|
@ -282,6 +291,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
|
@ -772,6 +782,8 @@ github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02
|
|||
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
|
||||
github.com/vbauerster/mpb/v5 v5.4.0 h1:n8JPunifvQvh6P1D1HAl2Ur9YcmKT1tpoUuiea5mlmg=
|
||||
github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI=
|
||||
github.com/vbauerster/mpb/v6 v6.0.2 h1:DWFnBOcgHi9GUNduC1MbQ936Z7B77wvOnZexP9Hjzcw=
|
||||
github.com/vbauerster/mpb/v6 v6.0.2/go.mod h1:JDNVbdx4oAMMxZNXodDH2DeDY5xBJC8bDGHNFZwRqQM=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
|
||||
|
@ -793,6 +805,7 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
|||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
|
@ -893,6 +906,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
|
@ -989,6 +1003,8 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
|
||||
|
@ -1046,6 +1062,8 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
|
|||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
)
|
||||
|
||||
type CreateOptions struct {
|
||||
Name string
|
||||
CPUS uint64
|
||||
Memory uint64
|
||||
IgnitionPath string
|
||||
ImagePath string
|
||||
Username string
|
||||
URI url.URL
|
||||
IsDefault bool
|
||||
//KernelPath string
|
||||
//Devices []VMDevices
|
||||
}
|
||||
|
||||
type RemoteConnectionType string
|
||||
|
||||
var (
|
||||
SSHRemoteConnection RemoteConnectionType = "ssh"
|
||||
DefaultIgnitionUserName = "core"
|
||||
)
|
||||
|
||||
type Download struct {
|
||||
Arch string
|
||||
Artifact string
|
||||
CompressionType string
|
||||
Format string
|
||||
ImageName string `json:"image_name"`
|
||||
LocalPath string
|
||||
LocalUncompressedFile string
|
||||
Sha256sum string
|
||||
URL *url.URL
|
||||
VMName string
|
||||
}
|
||||
|
||||
type SSHOptions struct {
|
||||
Execute bool
|
||||
Args []string
|
||||
}
|
||||
type StartOptions struct{}
|
||||
|
||||
type StopOptions struct{}
|
||||
|
||||
type RemoveOptions struct {
|
||||
Force bool
|
||||
SaveKeys bool
|
||||
SaveImage bool
|
||||
SaveIgnition bool
|
||||
}
|
||||
|
||||
type VM interface {
|
||||
Create(opts CreateOptions) error
|
||||
Remove(name string, opts RemoveOptions) (string, func() error, error)
|
||||
SSH(name string, opts SSHOptions) error
|
||||
Start(name string, opts StartOptions) error
|
||||
Stop(name string, opts StopOptions) error
|
||||
}
|
||||
|
||||
type DistributionDownload interface {
|
||||
DownloadImage() error
|
||||
Get() *Download
|
||||
}
|
||||
|
||||
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
|
||||
userInfo := url.User(userName)
|
||||
uri := url.URL{
|
||||
Scheme: "ssh",
|
||||
Opaque: "",
|
||||
User: userInfo,
|
||||
Host: host,
|
||||
Path: path,
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
}
|
||||
if len(port) > 0 {
|
||||
uri.Host = net.JoinHostPort(uri.Hostname(), port)
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
// GetDataDir returns the filepath where vm images should
|
||||
// live for podman-machine
|
||||
func GetDataDir(vmType string) (string, error) {
|
||||
data, err := homedir.GetDataHome()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataDir := filepath.Join(data, "containers", "podman", "machine", vmType)
|
||||
if _, err := os.Stat(dataDir); !os.IsNotExist(err) {
|
||||
return dataDir, nil
|
||||
}
|
||||
mkdirErr := os.MkdirAll(dataDir, 0755)
|
||||
return dataDir, mkdirErr
|
||||
}
|
||||
|
||||
// GetConfigDir returns the filepath to where configuration
|
||||
// files for podman-machine should live
|
||||
func GetConfDir(vmType string) (string, error) {
|
||||
conf, err := homedir.GetConfigHome()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
confDir := filepath.Join(conf, "containers", "podman", "machine", vmType)
|
||||
if _, err := os.Stat(confDir); !os.IsNotExist(err) {
|
||||
return confDir, nil
|
||||
}
|
||||
mkdirErr := os.MkdirAll(confDir, 0755)
|
||||
return confDir, mkdirErr
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) error {
|
||||
if len(identity) < 1 {
|
||||
return errors.New("identity must be defined")
|
||||
}
|
||||
cfg, err := config.ReadCustomConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := cfg.Engine.ServiceDestinations[name]; ok {
|
||||
return errors.New("cannot overwrite connection")
|
||||
}
|
||||
if isDefault {
|
||||
cfg.Engine.ActiveService = name
|
||||
}
|
||||
dst := config.Destination{
|
||||
URI: uri.String(),
|
||||
}
|
||||
dst.Identity = identity
|
||||
if cfg.Engine.ServiceDestinations == nil {
|
||||
cfg.Engine.ServiceDestinations = map[string]config.Destination{
|
||||
name: dst,
|
||||
}
|
||||
cfg.Engine.ActiveService = name
|
||||
} else {
|
||||
cfg.Engine.ServiceDestinations[name] = dst
|
||||
}
|
||||
return cfg.Write()
|
||||
}
|
||||
|
||||
func RemoveConnection(name string) error {
|
||||
cfg, err := config.ReadCustomConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := cfg.Engine.ServiceDestinations[name]; ok {
|
||||
delete(cfg.Engine.ServiceDestinations, name)
|
||||
} else {
|
||||
return errors.Errorf("unable to find connection named %q", name)
|
||||
}
|
||||
return cfg.Write()
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
url2 "net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// These should eventually be moved into machine/qemu as
|
||||
// they are specific to running qemu
|
||||
var (
|
||||
artifact string = "qemu"
|
||||
Format string = "qcow2.xz"
|
||||
)
|
||||
|
||||
type FcosDownload struct {
|
||||
Download
|
||||
}
|
||||
|
||||
func NewFcosDownloader(vmType, vmName string) (DistributionDownload, error) {
|
||||
info, err := getFCOSDownload()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
urlSplit := strings.Split(info.Location, "/")
|
||||
imageName := urlSplit[len(urlSplit)-1]
|
||||
url, err := url2.Parse(info.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataDir, err := GetDataDir(vmType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fcd := FcosDownload{
|
||||
Download: Download{
|
||||
Arch: getFcosArch(),
|
||||
Artifact: artifact,
|
||||
Format: Format,
|
||||
ImageName: imageName,
|
||||
LocalPath: filepath.Join(dataDir, imageName),
|
||||
Sha256sum: info.Sha256Sum,
|
||||
URL: url,
|
||||
VMName: vmName,
|
||||
},
|
||||
}
|
||||
fcd.Download.LocalUncompressedFile = fcd.getLocalUncompressedName()
|
||||
return fcd, nil
|
||||
}
|
||||
|
||||
func (f FcosDownload) getLocalUncompressedName() string {
|
||||
uncompressedFilename := filepath.Join(filepath.Dir(f.LocalPath), f.VMName+"_"+f.ImageName)
|
||||
return strings.TrimSuffix(uncompressedFilename, ".xz")
|
||||
}
|
||||
|
||||
func (f FcosDownload) DownloadImage() error {
|
||||
// check if the latest image is already present
|
||||
ok, err := UpdateAvailable(&f.Download)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
if err := DownloadVMImage(f.URL, f.LocalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
uncompressedFileWriter, err := os.OpenFile(f.getLocalUncompressedName(), os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sourceFile, err := ioutil.ReadFile(f.LocalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
compressionType := archive.DetectCompression(sourceFile)
|
||||
f.CompressionType = compressionType.Extension()
|
||||
|
||||
switch f.CompressionType {
|
||||
case "tar.xz":
|
||||
return decompressXZ(f.LocalPath, uncompressedFileWriter)
|
||||
default:
|
||||
// File seems to be uncompressed, make a copy
|
||||
if err := copyFile(f.LocalPath, uncompressedFileWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src string, dest *os.File) error {
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := source.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(dest, source)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f FcosDownload) Get() *Download {
|
||||
return &f.Download
|
||||
}
|
||||
|
||||
type fcosDownloadInfo struct {
|
||||
CompressionType string
|
||||
Location string
|
||||
Release string
|
||||
Sha256Sum string
|
||||
}
|
||||
|
||||
func UpdateAvailable(d *Download) (bool, error) {
|
||||
// check the sha of the local image if it exists
|
||||
// get the sha of the remote image
|
||||
// == dont bother to pull
|
||||
files, err := ioutil.ReadDir(filepath.Dir(d.LocalPath))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if filepath.Base(d.LocalPath) == file.Name() {
|
||||
b, err := ioutil.ReadFile(d.LocalPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
s := sha256.Sum256(b)
|
||||
sum := digest.NewDigestFromBytes(digest.SHA256, s[:])
|
||||
if sum.Encoded() == d.Sha256sum {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func getFcosArch() string {
|
||||
var arch string
|
||||
// TODO fill in more architectures
|
||||
switch runtime.GOARCH {
|
||||
case "arm64":
|
||||
arch = "aarch64"
|
||||
default:
|
||||
arch = "x86_64"
|
||||
}
|
||||
return arch
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/stream-metadata-go/fedoracoreos"
|
||||
"github.com/coreos/stream-metadata-go/stream"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// This should get Exported and stay put as it will apply to all fcos downloads
|
||||
// getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version
|
||||
func getFCOSDownload() (*fcosDownloadInfo, error) {
|
||||
var (
|
||||
fcosstable stream.Stream
|
||||
)
|
||||
streamurl := fedoracoreos.GetStreamURL(fedoracoreos.StreamNext)
|
||||
resp, err := http.Get(streamurl.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := json.Unmarshal(body, &fcosstable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arch, ok := fcosstable.Architectures[getFcosArch()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream")
|
||||
}
|
||||
artifacts := arch.Artifacts
|
||||
if artifacts == nil {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no artifact in stream")
|
||||
}
|
||||
qemu, ok := artifacts[artifact]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no qemu artifact in stream")
|
||||
}
|
||||
formats := qemu.Formats
|
||||
if formats == nil {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no formats in stream")
|
||||
}
|
||||
qcow, ok := formats[Format]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
|
||||
}
|
||||
disk := qcow.Disk
|
||||
if disk == nil {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no disk in stream")
|
||||
}
|
||||
return &fcosDownloadInfo{
|
||||
Location: disk.Location,
|
||||
Release: qemu.Release,
|
||||
Sha256Sum: disk.Sha256,
|
||||
CompressionType: "xz",
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const aarchBaseURL = "https://fedorapeople.org/groups/fcos-images/builds/latest/aarch64/"
|
||||
|
||||
// Total hack until automation is possible.
|
||||
// We need a proper json file at least to automate
|
||||
func getFCOSDownload() (*fcosDownloadInfo, error) {
|
||||
|
||||
meta := Build{}
|
||||
fmt.Println(aarchBaseURL + "meta.json")
|
||||
resp, err := http.Get(aarchBaseURL + "meta.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
if err := json.Unmarshal(body, &meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fcosDownloadInfo{
|
||||
Location: "https://fedorapeople.org/groups/fcos-images/builds/latest/aarch64/fedora-coreos-33.20210310.dev.0-qemu.aarch64.qcow2",
|
||||
Release: "",
|
||||
Sha256Sum: meta.BuildArtifacts.Qemu.Sha256,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
All of this can be nuked when fcos upstream generates a proper meta data file for aarch.
|
||||
*/
|
||||
type AliyunImage struct {
|
||||
ImageID string `json:"id"`
|
||||
Region string `json:"name"`
|
||||
}
|
||||
|
||||
type Amis struct {
|
||||
Hvm string `json:"hvm"`
|
||||
Region string `json:"name"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
}
|
||||
|
||||
type Artifact struct {
|
||||
Path string `json:"path"`
|
||||
Sha256 string `json:"sha256"`
|
||||
SizeInBytes float64 `json:"size,omitempty"`
|
||||
UncompressedSha256 string `json:"uncompressed-sha256,omitempty"`
|
||||
UncompressedSize int `json:"uncompressed-size,omitempty"`
|
||||
}
|
||||
|
||||
type Build struct {
|
||||
AlibabaAliyunUploads []AliyunImage `json:"aliyun,omitempty"`
|
||||
Amis []Amis `json:"amis,omitempty"`
|
||||
Architecture string `json:"coreos-assembler.basearch,omitempty"`
|
||||
Azure *Cloudartifact `json:"azure,omitempty"`
|
||||
BuildArtifacts *BuildArtifacts `json:"images,omitempty"`
|
||||
BuildID string `json:"buildid"`
|
||||
BuildRef string `json:"ref,omitempty"`
|
||||
BuildSummary string `json:"summary"`
|
||||
BuildTimeStamp string `json:"coreos-assembler.build-timestamp,omitempty"`
|
||||
BuildURL string `json:"build-url,omitempty"`
|
||||
ConfigGitRev string `json:"coreos-assembler.config-gitrev,omitempty"`
|
||||
ContainerConfigGit *Git `json:"coreos-assembler.container-config-git,omitempty"`
|
||||
CoreOsSource string `json:"coreos-assembler.code-source,omitempty"`
|
||||
CosaContainerImageGit *Git `json:"coreos-assembler.container-image-git,omitempty"`
|
||||
CosaDelayedMetaMerge bool `json:"coreos-assembler.delayed-meta-merge,omitempty"`
|
||||
CosaImageChecksum string `json:"coreos-assembler.image-config-checksum,omitempty"`
|
||||
CosaImageVersion int `json:"coreos-assembler.image-genver,omitempty"`
|
||||
Extensions *Extensions `json:"extensions,omitempty"`
|
||||
FedoraCoreOsParentCommit string `json:"fedora-coreos.parent-commit,omitempty"`
|
||||
FedoraCoreOsParentVersion string `json:"fedora-coreos.parent-version,omitempty"`
|
||||
Gcp *Gcp `json:"gcp,omitempty"`
|
||||
GitDirty string `json:"coreos-assembler.config-dirty,omitempty"`
|
||||
ImageInputChecksum string `json:"coreos-assembler.image-input-checksum,omitempty"`
|
||||
InputHasOfTheRpmOstree string `json:"rpm-ostree-inputhash"`
|
||||
MetaStamp float64 `json:"coreos-assembler.meta-stamp,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Oscontainer *Image `json:"oscontainer,omitempty"`
|
||||
OstreeCommit string `json:"ostree-commit"`
|
||||
OstreeContentBytesWritten int `json:"ostree-content-bytes-written,omitempty"`
|
||||
OstreeContentChecksum string `json:"ostree-content-checksum"`
|
||||
OstreeNCacheHits int `json:"ostree-n-cache-hits,omitempty"`
|
||||
OstreeNContentTotal int `json:"ostree-n-content-total,omitempty"`
|
||||
OstreeNContentWritten int `json:"ostree-n-content-written,omitempty"`
|
||||
OstreeNMetadataTotal int `json:"ostree-n-metadata-total,omitempty"`
|
||||
OstreeNMetadataWritten int `json:"ostree-n-metadata-written,omitempty"`
|
||||
OstreeTimestamp string `json:"ostree-timestamp"`
|
||||
OstreeVersion string `json:"ostree-version"`
|
||||
OverridesActive bool `json:"coreos-assembler.overrides-active,omitempty"`
|
||||
PkgdiffAgainstParent PackageSetDifferences `json:"parent-pkgdiff,omitempty"`
|
||||
PkgdiffBetweenBuilds PackageSetDifferences `json:"pkgdiff,omitempty"`
|
||||
ReleasePayload *Image `json:"release-payload,omitempty"`
|
||||
}
|
||||
|
||||
type BuildArtifacts struct {
|
||||
Aliyun *Artifact `json:"aliyun,omitempty"`
|
||||
Aws *Artifact `json:"aws,omitempty"`
|
||||
Azure *Artifact `json:"azure,omitempty"`
|
||||
AzureStack *Artifact `json:"azurestack,omitempty"`
|
||||
Dasd *Artifact `json:"dasd,omitempty"`
|
||||
DigitalOcean *Artifact `json:"digitalocean,omitempty"`
|
||||
Exoscale *Artifact `json:"exoscale,omitempty"`
|
||||
Gcp *Artifact `json:"gcp,omitempty"`
|
||||
IbmCloud *Artifact `json:"ibmcloud,omitempty"`
|
||||
Initramfs *Artifact `json:"initramfs,omitempty"`
|
||||
Iso *Artifact `json:"iso,omitempty"`
|
||||
Kernel *Artifact `json:"kernel,omitempty"`
|
||||
LiveInitramfs *Artifact `json:"live-initramfs,omitempty"`
|
||||
LiveIso *Artifact `json:"live-iso,omitempty"`
|
||||
LiveKernel *Artifact `json:"live-kernel,omitempty"`
|
||||
LiveRootfs *Artifact `json:"live-rootfs,omitempty"`
|
||||
Metal *Artifact `json:"metal,omitempty"`
|
||||
Metal4KNative *Artifact `json:"metal4k,omitempty"`
|
||||
OpenStack *Artifact `json:"openstack,omitempty"`
|
||||
Ostree Artifact `json:"ostree"`
|
||||
Qemu *Artifact `json:"qemu,omitempty"`
|
||||
Vmware *Artifact `json:"vmware,omitempty"`
|
||||
Vultr *Artifact `json:"vultr,omitempty"`
|
||||
}
|
||||
|
||||
type Cloudartifact struct {
|
||||
Image string `json:"image"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Extensions struct {
|
||||
Manifest map[string]interface{} `json:"manifest"`
|
||||
Path string `json:"path"`
|
||||
RpmOstreeState string `json:"rpm-ostree-state"`
|
||||
Sha256 string `json:"sha256"`
|
||||
}
|
||||
|
||||
type Gcp struct {
|
||||
ImageFamily string `json:"family,omitempty"`
|
||||
ImageName string `json:"image"`
|
||||
ImageProject string `json:"project,omitempty"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Git struct {
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Commit string `json:"commit"`
|
||||
Dirty string `json:"dirty,omitempty"`
|
||||
Origin string `json:"origin"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Digest string `json:"digest"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
type Items interface{}
|
||||
|
||||
type PackageSetDifferences []Items
|
|
@ -0,0 +1,151 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
/*
|
||||
If this file gets too nuts, we can perhaps use existing go code
|
||||
to create ignition files. At this point, the file is so simple
|
||||
that I chose to use structs and not import any code as I was
|
||||
concerned (unsubstantiated) about too much bloat coming in.
|
||||
|
||||
https://github.com/openshift/machine-config-operator/blob/master/pkg/server/server.go
|
||||
*/
|
||||
|
||||
// Convenience function to convert int to ptr
|
||||
func intToPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Convenience function to convert string to ptr
|
||||
func strToPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// Convenience function to convert bool to ptr
|
||||
func boolToPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func getNodeUsr(usrName string) NodeUser {
|
||||
return NodeUser{Name: &usrName}
|
||||
}
|
||||
|
||||
func getNodeGrp(grpName string) NodeGroup {
|
||||
return NodeGroup{Name: &grpName}
|
||||
}
|
||||
|
||||
// NewIgnitionFile
|
||||
func NewIgnitionFile(name, key, writePath string) error {
|
||||
if len(name) < 1 {
|
||||
name = DefaultIgnitionUserName
|
||||
}
|
||||
ignVersion := Ignition{
|
||||
Version: "3.2.0",
|
||||
}
|
||||
|
||||
ignPassword := Passwd{
|
||||
Users: []PasswdUser{{
|
||||
Name: name,
|
||||
SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(key)},
|
||||
}},
|
||||
}
|
||||
|
||||
ignStorage := Storage{
|
||||
Directories: getDirs(name),
|
||||
Files: getFiles(name),
|
||||
Links: getLinks(name),
|
||||
}
|
||||
ignSystemd := Systemd{
|
||||
Units: []Unit{
|
||||
{
|
||||
Enabled: boolToPtr(true),
|
||||
Name: "podman.socket",
|
||||
}}}
|
||||
|
||||
ignConfig := Config{
|
||||
Ignition: ignVersion,
|
||||
Passwd: ignPassword,
|
||||
Storage: ignStorage,
|
||||
Systemd: ignSystemd,
|
||||
}
|
||||
b, err := json.Marshal(ignConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(writePath, b, 0644)
|
||||
}
|
||||
|
||||
func getDirs(usrName string) []Directory {
|
||||
// Ignition has a bug/feature? where if you make a series of dirs
|
||||
// in one swoop, then the leading dirs are creates as root.
|
||||
newDirs := []string{
|
||||
"/home/" + usrName + "/.config",
|
||||
"/home/" + usrName + "/.config/systemd",
|
||||
"/home/" + usrName + "/.config/systemd/user",
|
||||
"/home/" + usrName + "/.config/systemd/user/default.target.wants",
|
||||
}
|
||||
var (
|
||||
dirs = make([]Directory, len(newDirs))
|
||||
)
|
||||
for i, d := range newDirs {
|
||||
newDir := Directory{
|
||||
Node: Node{
|
||||
Group: getNodeGrp(usrName),
|
||||
Path: d,
|
||||
User: getNodeUsr(usrName),
|
||||
},
|
||||
DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(493)},
|
||||
}
|
||||
dirs[i] = newDir
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
func getFiles(usrName string) []File {
|
||||
var (
|
||||
files []File
|
||||
)
|
||||
// Add a fake systemd service to get the user socket rolling
|
||||
files = append(files, File{
|
||||
Node: Node{
|
||||
Group: getNodeGrp(usrName),
|
||||
Path: "/home/" + usrName + "/.config/systemd/user/linger-example.service",
|
||||
User: getNodeUsr(usrName),
|
||||
},
|
||||
FileEmbedded1: FileEmbedded1{
|
||||
Append: nil,
|
||||
Contents: Resource{
|
||||
Source: strToPtr("data:,%5BUnit%5D%0ADescription%3DA%20systemd%20user%20unit%20demo%0AAfter%3Dnetwork-online.target%0AWants%3Dnetwork-online.target%20podman.socket%0A%5BService%5D%0AExecStart%3D%2Fusr%2Fbin%2Fsleep%20infinity%0A"),
|
||||
},
|
||||
Mode: intToPtr(484),
|
||||
},
|
||||
})
|
||||
|
||||
// Add a file into linger
|
||||
files = append(files, File{
|
||||
Node: Node{
|
||||
Group: getNodeGrp(usrName),
|
||||
Path: "/var/lib/systemd/linger/core",
|
||||
User: getNodeUsr(usrName),
|
||||
},
|
||||
FileEmbedded1: FileEmbedded1{Mode: intToPtr(420)},
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
func getLinks(usrName string) []Link {
|
||||
return []Link{{
|
||||
Node: Node{
|
||||
Group: getNodeGrp(usrName),
|
||||
Path: "/home/" + usrName + "/.config/systemd/user/default.target.wants/linger-example.service",
|
||||
User: getNodeUsr(usrName),
|
||||
},
|
||||
LinkEmbedded1: LinkEmbedded1{
|
||||
Hard: boolToPtr(false),
|
||||
Target: "/home/" + usrName + "/.config/systemd/user/linger-example.service",
|
||||
},
|
||||
}}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
package machine
|
||||
|
||||
/*
|
||||
This file was taken from https://github.com/coreos/ignition/blob/master/config/v3_2/types/schema.go in an effort to
|
||||
use more of the core-os structs but not fully commit to bringing their api in.
|
||||
|
||||
// generated by "schematyper --package=types config/v3_2/schema/ignition.json -o config/v3_2/types/ignition_schema.go --root-type=Config" -- DO NOT EDIT
|
||||
*/
|
||||
|
||||
type Clevis struct {
|
||||
Custom *Custom `json:"custom,omitempty"`
|
||||
Tang []Tang `json:"tang,omitempty"`
|
||||
Threshold *int `json:"threshold,omitempty"`
|
||||
Tpm2 *bool `json:"tpm2,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Ignition Ignition `json:"ignition"`
|
||||
Passwd Passwd `json:"passwd,omitempty"`
|
||||
Storage Storage `json:"storage,omitempty"`
|
||||
Systemd Systemd `json:"systemd,omitempty"`
|
||||
}
|
||||
|
||||
type Custom struct {
|
||||
Config string `json:"config"`
|
||||
NeedsNetwork *bool `json:"needsNetwork,omitempty"`
|
||||
Pin string `json:"pin"`
|
||||
}
|
||||
|
||||
type Device string
|
||||
|
||||
type Directory struct {
|
||||
Node
|
||||
DirectoryEmbedded1
|
||||
}
|
||||
|
||||
type DirectoryEmbedded1 struct {
|
||||
Mode *int `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
type Disk struct {
|
||||
Device string `json:"device"`
|
||||
Partitions []Partition `json:"partitions,omitempty"`
|
||||
WipeTable *bool `json:"wipeTable,omitempty"`
|
||||
}
|
||||
|
||||
type Dropin struct {
|
||||
Contents *string `json:"contents,omitempty"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Node
|
||||
FileEmbedded1
|
||||
}
|
||||
|
||||
type FileEmbedded1 struct {
|
||||
Append []Resource `json:"append,omitempty"`
|
||||
Contents Resource `json:"contents,omitempty"`
|
||||
Mode *int `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
type Filesystem struct {
|
||||
Device string `json:"device"`
|
||||
Format *string `json:"format,omitempty"`
|
||||
Label *string `json:"label,omitempty"`
|
||||
MountOptions []MountOption `json:"mountOptions,omitempty"`
|
||||
Options []FilesystemOption `json:"options,omitempty"`
|
||||
Path *string `json:"path,omitempty"`
|
||||
UUID *string `json:"uuid,omitempty"`
|
||||
WipeFilesystem *bool `json:"wipeFilesystem,omitempty"`
|
||||
}
|
||||
|
||||
type FilesystemOption string
|
||||
|
||||
type Group string
|
||||
|
||||
type HTTPHeader struct {
|
||||
Name string `json:"name"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPHeaders []HTTPHeader
|
||||
|
||||
type Ignition struct {
|
||||
Config IgnitionConfig `json:"config,omitempty"`
|
||||
Proxy Proxy `json:"proxy,omitempty"`
|
||||
Security Security `json:"security,omitempty"`
|
||||
Timeouts Timeouts `json:"timeouts,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type IgnitionConfig struct {
|
||||
Merge []Resource `json:"merge,omitempty"`
|
||||
Replace Resource `json:"replace,omitempty"`
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Node
|
||||
LinkEmbedded1
|
||||
}
|
||||
|
||||
type LinkEmbedded1 struct {
|
||||
Hard *bool `json:"hard,omitempty"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
type Luks struct {
|
||||
Clevis *Clevis `json:"clevis,omitempty"`
|
||||
Device *string `json:"device,omitempty"`
|
||||
KeyFile Resource `json:"keyFile,omitempty"`
|
||||
Label *string `json:"label,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Options []LuksOption `json:"options,omitempty"`
|
||||
UUID *string `json:"uuid,omitempty"`
|
||||
WipeVolume *bool `json:"wipeVolume,omitempty"`
|
||||
}
|
||||
|
||||
type LuksOption string
|
||||
|
||||
type MountOption string
|
||||
|
||||
type NoProxyItem string
|
||||
|
||||
type Node struct {
|
||||
Group NodeGroup `json:"group,omitempty"`
|
||||
Overwrite *bool `json:"overwrite,omitempty"`
|
||||
Path string `json:"path"`
|
||||
User NodeUser `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
type NodeGroup struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type NodeUser struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type Partition struct {
|
||||
GUID *string `json:"guid,omitempty"`
|
||||
Label *string `json:"label,omitempty"`
|
||||
Number int `json:"number,omitempty"`
|
||||
Resize *bool `json:"resize,omitempty"`
|
||||
ShouldExist *bool `json:"shouldExist,omitempty"`
|
||||
SizeMiB *int `json:"sizeMiB,omitempty"`
|
||||
StartMiB *int `json:"startMiB,omitempty"`
|
||||
TypeGUID *string `json:"typeGuid,omitempty"`
|
||||
WipePartitionEntry *bool `json:"wipePartitionEntry,omitempty"`
|
||||
}
|
||||
|
||||
type Passwd struct {
|
||||
Groups []PasswdGroup `json:"groups,omitempty"`
|
||||
Users []PasswdUser `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
type PasswdGroup struct {
|
||||
Gid *int `json:"gid,omitempty"`
|
||||
Name string `json:"name"`
|
||||
PasswordHash *string `json:"passwordHash,omitempty"`
|
||||
ShouldExist *bool `json:"shouldExist,omitempty"`
|
||||
System *bool `json:"system,omitempty"`
|
||||
}
|
||||
|
||||
type PasswdUser struct {
|
||||
Gecos *string `json:"gecos,omitempty"`
|
||||
Groups []Group `json:"groups,omitempty"`
|
||||
HomeDir *string `json:"homeDir,omitempty"`
|
||||
Name string `json:"name"`
|
||||
NoCreateHome *bool `json:"noCreateHome,omitempty"`
|
||||
NoLogInit *bool `json:"noLogInit,omitempty"`
|
||||
NoUserGroup *bool `json:"noUserGroup,omitempty"`
|
||||
PasswordHash *string `json:"passwordHash,omitempty"`
|
||||
PrimaryGroup *string `json:"primaryGroup,omitempty"`
|
||||
SSHAuthorizedKeys []SSHAuthorizedKey `json:"sshAuthorizedKeys,omitempty"`
|
||||
Shell *string `json:"shell,omitempty"`
|
||||
ShouldExist *bool `json:"shouldExist,omitempty"`
|
||||
System *bool `json:"system,omitempty"`
|
||||
UID *int `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
HTTPProxy *string `json:"httpProxy,omitempty"`
|
||||
HTTPSProxy *string `json:"httpsProxy,omitempty"`
|
||||
NoProxy []NoProxyItem `json:"noProxy,omitempty"`
|
||||
}
|
||||
|
||||
type Raid struct {
|
||||
Devices []Device `json:"devices"`
|
||||
Level string `json:"level"`
|
||||
Name string `json:"name"`
|
||||
Options []RaidOption `json:"options,omitempty"`
|
||||
Spares *int `json:"spares,omitempty"`
|
||||
}
|
||||
|
||||
type RaidOption string
|
||||
|
||||
type Resource struct {
|
||||
Compression *string `json:"compression,omitempty"`
|
||||
HTTPHeaders HTTPHeaders `json:"httpHeaders,omitempty"`
|
||||
Source *string `json:"source,omitempty"`
|
||||
Verification Verification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
type SSHAuthorizedKey string
|
||||
|
||||
type Security struct {
|
||||
TLS TLS `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
Directories []Directory `json:"directories,omitempty"`
|
||||
Disks []Disk `json:"disks,omitempty"`
|
||||
Files []File `json:"files,omitempty"`
|
||||
Filesystems []Filesystem `json:"filesystems,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
Luks []Luks `json:"luks,omitempty"`
|
||||
Raid []Raid `json:"raid,omitempty"`
|
||||
}
|
||||
|
||||
type Systemd struct {
|
||||
Units []Unit `json:"units,omitempty"`
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
CertificateAuthorities []Resource `json:"certificateAuthorities,omitempty"`
|
||||
}
|
||||
|
||||
type Tang struct {
|
||||
Thumbprint *string `json:"thumbprint,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type Timeouts struct {
|
||||
HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
|
||||
HTTPTotal *int `json:"httpTotal,omitempty"`
|
||||
}
|
||||
|
||||
type Unit struct {
|
||||
Contents *string `json:"contents,omitempty"`
|
||||
Dropins []Dropin `json:"dropins,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Mask *bool `json:"mask,omitempty"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Verification struct {
|
||||
Hash *string `json:"hash,omitempty"`
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateSSHKeys makes a priv and pub ssh key for interacting
|
||||
// the a VM.
|
||||
func CreateSSHKeys(writeLocation string) (string, error) {
|
||||
if err := generatekeys(writeLocation); err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := ioutil.ReadFile(writeLocation + ".pub")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSuffix(string(b), "\n"), nil
|
||||
}
|
||||
|
||||
// generatekeys creates an ed25519 set of keys
|
||||
func generatekeys(writeLocation string) error {
|
||||
return exec.Command("ssh-keygen", "-N", "", "-t", "ed25519", "-f", writeLocation).Run()
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package libvirt
|
||||
|
||||
type MachineVM struct {
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package libvirt
|
||||
|
||||
import "github.com/containers/podman/v3/pkg/machine"
|
||||
|
||||
func (v *MachineVM) Create(name string, opts machine.CreateOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *MachineVM) Start(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *MachineVM) Stop(name string) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package machine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbauerster/mpb/v6"
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
)
|
||||
|
||||
// DownloadVMImage downloads a VM image from url to given path
|
||||
// with download status
|
||||
func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
|
||||
out, err := os.Create(localImagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := out.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
resp, err := http.Get(downloadURL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("error downloading VM image: %s", resp.Status)
|
||||
}
|
||||
size := resp.ContentLength
|
||||
urlSplit := strings.Split(downloadURL.String(), "/")
|
||||
prefix := "Downloading VM image: " + urlSplit[len(urlSplit)-1]
|
||||
onComplete := prefix + ": done"
|
||||
|
||||
p := mpb.New(
|
||||
mpb.WithWidth(60),
|
||||
mpb.WithRefreshRate(180*time.Millisecond),
|
||||
)
|
||||
|
||||
bar := p.AddBar(size,
|
||||
mpb.BarFillerClearOnComplete(),
|
||||
mpb.PrependDecorators(
|
||||
decor.OnComplete(decor.Name(prefix), onComplete),
|
||||
),
|
||||
mpb.AppendDecorators(
|
||||
decor.OnComplete(decor.CountersKibiByte("%.1f / %.1f"), ""),
|
||||
),
|
||||
)
|
||||
|
||||
proxyReader := bar.ProxyReader(resp.Body)
|
||||
defer func() {
|
||||
if err := proxyReader.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(out, proxyReader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Will error out if file without .xz already exists
|
||||
// Maybe extracting then renameing is a good idea here..
|
||||
// depends on xz: not pre-installed on mac, so it becomes a brew dependecy
|
||||
func decompressXZ(src string, output io.Writer) error {
|
||||
fmt.Println("Extracting compressed file")
|
||||
cmd := exec.Command("xzcat", "-k", src)
|
||||
//cmd := exec.Command("xz", "-d", "-k", "-v", src)
|
||||
stdOut, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
go func() {
|
||||
if _, err := io.Copy(output, stdOut); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
return cmd.Run()
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package qemu
|
||||
|
||||
import "time"
|
||||
|
||||
type MachineVM struct {
|
||||
// CPUs to be assigned to the VM
|
||||
CPUs uint64
|
||||
// The command line representation of the qemu command
|
||||
CmdLine []string
|
||||
// IdentityPath is the fq path to the ssh priv key
|
||||
IdentityPath string
|
||||
// IgnitionFilePath is the fq path to the .ign file
|
||||
IgnitionFilePath string
|
||||
// ImagePath is the fq path to
|
||||
ImagePath string
|
||||
// Memory in megabytes assigned to the vm
|
||||
Memory uint64
|
||||
// Name of the vm
|
||||
Name string
|
||||
// SSH port for user networking
|
||||
Port int
|
||||
// QMPMonitor is the qemu monitor object for sending commands
|
||||
QMPMonitor Monitor
|
||||
// RemoteUsername of the vm user
|
||||
RemoteUsername string
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
// Address portion of the qmp monitor (/tmp/tmp.sock)
|
||||
Address string
|
||||
// Network portion of the qmp monitor (unix)
|
||||
Network string
|
||||
// Timeout in seconds for qmp monitor transactions
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultQMPTimeout is the timeout duration for the
|
||||
// qmp monitor interactions
|
||||
defaultQMPTimeout time.Duration = 2 * time.Second
|
||||
// defaultRemoteUser describes the ssh username default
|
||||
defaultRemoteUser = "core"
|
||||
)
|
|
@ -0,0 +1,317 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v3/utils"
|
||||
|
||||
"github.com/containers/podman/v3/pkg/machine"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/digitalocean/go-qemu/qmp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// vmtype refers to qemu (vs libvirt, krun, etc)
|
||||
vmtype = "qemu"
|
||||
// qemuCommon are the common command line arguments between the arches
|
||||
//qemuCommon = []string{"-cpu", "host", "-qmp", "unix://tmp/qmp.sock,server,nowait"}
|
||||
//qemuCommon = []string{"-cpu", "host", "-qmp", "tcp:localhost:4444,server,nowait"}
|
||||
)
|
||||
|
||||
// NewMachine creates an instance of a virtual machine based on the qemu
|
||||
// virtualization.
|
||||
func NewMachine(opts machine.CreateOptions) (machine.VM, error) {
|
||||
vmConfigDir, err := machine.GetConfDir(vmtype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm := new(MachineVM)
|
||||
if len(opts.Name) > 0 {
|
||||
vm.Name = opts.Name
|
||||
}
|
||||
vm.IgnitionFilePath = opts.IgnitionPath
|
||||
// If no ignitionfilepath was provided, use defaults
|
||||
if len(vm.IgnitionFilePath) < 1 {
|
||||
ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign")
|
||||
vm.IgnitionFilePath = ignitionFile
|
||||
}
|
||||
|
||||
// An image was specified
|
||||
if len(opts.ImagePath) > 0 {
|
||||
vm.ImagePath = opts.ImagePath
|
||||
}
|
||||
|
||||
// Assign remote user name. if not provided, use default
|
||||
vm.RemoteUsername = opts.Username
|
||||
if len(vm.RemoteUsername) < 1 {
|
||||
vm.RemoteUsername = defaultRemoteUser
|
||||
}
|
||||
|
||||
// Add a random port for ssh
|
||||
port, err := utils.GetRandomPort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm.Port = port
|
||||
|
||||
vm.CPUs = opts.CPUS
|
||||
vm.Memory = opts.Memory
|
||||
|
||||
// Look up the executable
|
||||
execPath, err := exec.LookPath(QemuCommand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := append([]string{execPath})
|
||||
// Add memory
|
||||
cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
|
||||
// Add cpus
|
||||
// TODO
|
||||
// Add ignition file
|
||||
cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath}...)
|
||||
// Add qmp socket
|
||||
monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm.QMPMonitor = monitor
|
||||
cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server,nowait"}...)
|
||||
|
||||
// Add network
|
||||
cmd = append(cmd, "-nic", "user,model=virtio,hostfwd=tcp::"+strconv.Itoa(vm.Port)+"-:22")
|
||||
vm.CmdLine = cmd
|
||||
fmt.Println("///")
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
// LoadByName reads a json file that describes a known qemu vm
|
||||
// and returns a vm instance
|
||||
func LoadVMByName(name string) (machine.VM, error) {
|
||||
// TODO need to define an error relating to ErrMachineNotFound
|
||||
vm := new(MachineVM)
|
||||
vmConfigDir, err := machine.GetConfDir(vmtype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(b, vm)
|
||||
logrus.Debug(vm.CmdLine)
|
||||
return vm, err
|
||||
}
|
||||
|
||||
// Create writes the json configuration file to the filesystem for
|
||||
// other verbs (start, stop)
|
||||
func (v *MachineVM) Create(opts machine.CreateOptions) error {
|
||||
sshDir := filepath.Join(homedir.Get(), ".ssh")
|
||||
// GetConfDir creates the directory so no need to check for
|
||||
// its existence
|
||||
vmConfigDir, err := machine.GetConfDir(vmtype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
|
||||
v.IdentityPath = filepath.Join(sshDir, v.Name)
|
||||
|
||||
dd, err := machine.NewFcosDownloader(vmtype, v.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.ImagePath = dd.Get().LocalUncompressedFile
|
||||
if err := dd.DownloadImage(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add arch specific options including image location
|
||||
v.CmdLine = append(v.CmdLine, v.addArchOptions()...)
|
||||
|
||||
// Add location of bootable image
|
||||
v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath)
|
||||
// This kind of stinks but no other way around this r/n
|
||||
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
|
||||
if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the JSON file
|
||||
b, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := machine.CreateSSHKeys(v.IdentityPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Run arch specific things that need to be done
|
||||
if err := v.prepare(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the ignition file
|
||||
return machine.NewIgnitionFile(opts.Username, key, v.IgnitionFilePath)
|
||||
}
|
||||
|
||||
// Start executes the qemu command line and forks it
|
||||
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
attr := new(os.ProcAttr)
|
||||
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
attr.Files = files
|
||||
logrus.Debug(v.CmdLine)
|
||||
_, err = os.StartProcess(v.CmdLine[0], v.CmdLine, attr)
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop uses the qmp monitor to call a system_powerdown
|
||||
func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
||||
// check if the qmp socket is there. if not, qemu instance is gone
|
||||
if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
|
||||
// Right now it is NOT an error to stop a stopped machine
|
||||
logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
|
||||
return nil
|
||||
}
|
||||
qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Simple JSON formation for the QAPI
|
||||
stopCommand := struct {
|
||||
Execute string `json:"execute"`
|
||||
}{
|
||||
Execute: "system_powerdown",
|
||||
}
|
||||
input, err := json.Marshal(stopCommand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := qmpMonitor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := qmpMonitor.Disconnect(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
_, err = qmpMonitor.Run(input)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewQMPMonitor creates the monitor subsection of our vm
|
||||
func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) {
|
||||
rtDir, err := getSocketDir()
|
||||
if err != nil {
|
||||
return Monitor{}, err
|
||||
}
|
||||
rtDir = filepath.Join(rtDir, "podman")
|
||||
if _, err := os.Stat(filepath.Join(rtDir)); os.IsNotExist(err) {
|
||||
// TODO 0644 is fine on linux but macos is weird
|
||||
if err := os.MkdirAll(rtDir, 0755); err != nil {
|
||||
return Monitor{}, err
|
||||
}
|
||||
}
|
||||
if timeout == 0 {
|
||||
timeout = defaultQMPTimeout
|
||||
}
|
||||
monitor := Monitor{
|
||||
Network: network,
|
||||
Address: filepath.Join(rtDir, "qmp_"+name+".sock"),
|
||||
Timeout: timeout,
|
||||
}
|
||||
return monitor, nil
|
||||
}
|
||||
|
||||
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
||||
var (
|
||||
files []string
|
||||
)
|
||||
|
||||
// cannot remove a running vm
|
||||
if v.isRunning() {
|
||||
return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
|
||||
}
|
||||
|
||||
// Collect all the files that need to be destroyed
|
||||
if !opts.SaveKeys {
|
||||
files = append(files, v.IdentityPath, v.IdentityPath+".pub")
|
||||
}
|
||||
if !opts.SaveIgnition {
|
||||
files = append(files, v.IgnitionFilePath)
|
||||
}
|
||||
if !opts.SaveImage {
|
||||
files = append(files, v.ImagePath)
|
||||
}
|
||||
files = append(files, v.archRemovalFiles()...)
|
||||
|
||||
if err := machine.RemoveConnection(v.Name); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
vmConfigDir, err := machine.GetConfDir(vmtype)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
|
||||
confirmationMessage := "\nThe following files will be deleted:\n\n"
|
||||
for _, msg := range files {
|
||||
confirmationMessage += msg + "\n"
|
||||
}
|
||||
confirmationMessage += "\n"
|
||||
return confirmationMessage, func() error {
|
||||
for _, f := range files {
|
||||
if err := os.Remove(f); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *MachineVM) isRunning() bool {
|
||||
// Check if qmp socket path exists
|
||||
if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
// Check if we can dial it
|
||||
if _, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SSH opens an interactive SSH session to the vm specified.
|
||||
// Added ssh function to VM interface: pkg/machine/config/go : line 58
|
||||
func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
|
||||
if !v.isRunning() {
|
||||
return errors.Errorf("vm %q is not running.", v.Name)
|
||||
}
|
||||
|
||||
sshDestination := v.RemoteUsername + "@localhost"
|
||||
port := strconv.Itoa(v.Port)
|
||||
|
||||
args := []string{"-i", v.IdentityPath, "-p", port, sshDestination}
|
||||
if opts.Execute {
|
||||
args = append(args, opts.Args...)
|
||||
} else {
|
||||
fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
|
||||
}
|
||||
|
||||
cmd := exec.Command("ssh", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
return cmd.Run()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func getSocketDir() (string, error) {
|
||||
tmpDir, ok := os.LookupEnv("TMPDIR")
|
||||
if !ok {
|
||||
return "", errors.New("unable to resolve TMPDIR")
|
||||
}
|
||||
return tmpDir, nil
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package qemu
|
||||
|
||||
var (
|
||||
QemuCommand = "qemu-system-x86_64"
|
||||
)
|
||||
|
||||
func (v *MachineVM) addArchOptions() []string {
|
||||
opts := []string{"-cpu", "host"}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (v *MachineVM) prepare() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *MachineVM) archRemovalFiles() []string {
|
||||
return []string{}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
QemuCommand = "qemu-system-aarch64"
|
||||
)
|
||||
|
||||
func (v *MachineVM) addArchOptions() []string {
|
||||
ovmfDir := getOvmfDir(v.ImagePath, v.Name)
|
||||
opts := []string{
|
||||
"-accel", "hvf",
|
||||
"-cpu", "cortex-a57",
|
||||
"-M", "virt,highmem=off",
|
||||
"-drive", "file=/usr/local/share/qemu/edk2-aarch64-code.fd,if=pflash,format=raw,readonly=on",
|
||||
"-drive", "file=" + ovmfDir + ",if=pflash,format=raw"}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (v *MachineVM) prepare() error {
|
||||
ovmfDir := getOvmfDir(v.ImagePath, v.Name)
|
||||
cmd := []string{"dd", "if=/dev/zero", "conv=sync", "bs=1m", "count=64", "of=" + ovmfDir}
|
||||
return exec.Command(cmd[0], cmd[1:]...).Run()
|
||||
}
|
||||
|
||||
func (v *MachineVM) archRemovalFiles() []string {
|
||||
ovmDir := getOvmfDir(v.ImagePath, v.Name)
|
||||
return []string{ovmDir}
|
||||
}
|
||||
|
||||
func getOvmfDir(imagePath, vmName string) string {
|
||||
return filepath.Join(filepath.Dir(imagePath), vmName+"_ovmf_vars.fd")
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package qemu
|
||||
|
||||
import "github.com/containers/podman/v3/pkg/util"
|
||||
|
||||
func getSocketDir() (string, error) {
|
||||
return util.GetRuntimeDir()
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package qemu
|
||||
|
||||
var (
|
||||
QemuCommand = "qemu-kvm"
|
||||
)
|
||||
|
||||
func (v *MachineVM) addArchOptions() []string {
|
||||
opts := []string{"-cpu", "host"}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (v *MachineVM) prepare() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *MachineVM) archRemovalFiles() []string {
|
||||
return []string{}
|
||||
}
|
|
@ -6,6 +6,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v3/utils"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/image"
|
||||
"github.com/containers/podman/v3/pkg/specgen"
|
||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
||||
|
@ -218,7 +220,7 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
|
|||
// Only get a random candidate for single entries or the start
|
||||
// of a range. Otherwise we just increment the candidate.
|
||||
if !tmp.isInRange || tmp.startOfRange {
|
||||
candidate, err = getRandomPort()
|
||||
candidate, err = utils.GetRandomPort()
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort)
|
||||
}
|
||||
|
@ -344,7 +346,7 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *imag
|
|||
for hostPort == 0 && tries > 0 {
|
||||
// We can't select a specific protocol, which is
|
||||
// unfortunate for the UDP case.
|
||||
candidate, err := getRandomPort()
|
||||
candidate, err := utils.GetRandomPort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -419,21 +421,3 @@ func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
|
|||
|
||||
return finalProto, nil
|
||||
}
|
||||
|
||||
// Find a random, open port on the host
|
||||
func getRandomPort() (int, error) {
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to get free TCP port")
|
||||
}
|
||||
defer l.Close()
|
||||
_, randomPort, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to determine free port")
|
||||
}
|
||||
rp, err := strconv.Atoi(randomPort)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to convert random port to int")
|
||||
}
|
||||
return rp, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Find a random, open port on the host
|
||||
func GetRandomPort() (int, error) {
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to get free TCP port")
|
||||
}
|
||||
defer l.Close()
|
||||
_, randomPort, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to determine free port")
|
||||
}
|
||||
rp, err := strconv.Atoi(randomPort)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to convert random port to int")
|
||||
}
|
||||
return rp, nil
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,27 @@
|
|||
// Package fedoracoreos contains APIs defining well-known
|
||||
// streams for Fedora CoreOS and a method to retrieve
|
||||
// the URL for a stream endpoint.
|
||||
package fedoracoreos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/coreos/stream-metadata-go/fedoracoreos/internals"
|
||||
)
|
||||
|
||||
const (
|
||||
// StreamStable is the default stream
|
||||
StreamStable = "stable"
|
||||
// StreamTesting is what is intended to land in stable
|
||||
StreamTesting = "testing"
|
||||
// StreamNext usually tracks the next Fedora major version
|
||||
StreamNext = "next"
|
||||
)
|
||||
|
||||
// GetStreamURL returns the URL for the given stream
|
||||
func GetStreamURL(stream string) url.URL {
|
||||
u := internals.GetBaseURL()
|
||||
u.Path = fmt.Sprintf("streams/%s.json", stream)
|
||||
return u
|
||||
}
|
33
vendor/github.com/coreos/stream-metadata-go/fedoracoreos/internals/fcosinternals.go
generated
vendored
Normal file
33
vendor/github.com/coreos/stream-metadata-go/fedoracoreos/internals/fcosinternals.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Package internals contains functions for accessing
|
||||
// the underlying "releases" and coreos-assembler builds
|
||||
// backing streams. General user code should avoid
|
||||
// this package and use streams.
|
||||
package internals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GetBaseURL returns the base URL
|
||||
func GetBaseURL() url.URL {
|
||||
return url.URL{
|
||||
Scheme: "https",
|
||||
Host: "builds.coreos.fedoraproject.org",
|
||||
}
|
||||
}
|
||||
|
||||
// GetReleaseIndexURL returns the URL for the release index of a given stream.
|
||||
// Avoid this unless you have a specific need to test a specific release.
|
||||
func GetReleaseIndexURL(stream string) url.URL {
|
||||
u := GetBaseURL()
|
||||
u.Path = fmt.Sprintf("prod/streams/%s/releases.json", stream)
|
||||
return u
|
||||
}
|
||||
|
||||
// GetCosaBuild returns the coreos-assembler build URL
|
||||
func GetCosaBuild(stream, buildID, arch string) url.URL {
|
||||
u := GetBaseURL()
|
||||
u.Path = fmt.Sprintf("prod/streams/%s/builds/%s/%s/", stream, buildID, arch)
|
||||
return u
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package rhcos
|
||||
|
||||
// Extensions is data specific to Red Hat Enterprise Linux CoreOS
|
||||
type Extensions struct {
|
||||
AzureDisk *AzureDisk `json:"azure-disk,omitempty"`
|
||||
}
|
||||
|
||||
// AzureDisk represents an Azure disk image that can be imported
|
||||
// into an image gallery or otherwise replicated, and then used
|
||||
// as a boot source for virtual machines.
|
||||
type AzureDisk struct {
|
||||
// Release is the source release version
|
||||
Release string `json:"release"`
|
||||
// URL to an image already stored in Azure infrastructure
|
||||
// that can be copied into an image gallery. Avoid creating VMs directly
|
||||
// from this URL as that may lead to performance limitations.
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Package stream models a CoreOS "stream", which is
|
||||
// a description of the recommended set of binary images for CoreOS. Use
|
||||
// this API to find cloud images, bare metal disk images, etc.
|
||||
package stream
|
||||
|
||||
import (
|
||||
"github.com/coreos/stream-metadata-go/stream/rhcos"
|
||||
)
|
||||
|
||||
// Stream contains artifacts available in a stream
|
||||
type Stream struct {
|
||||
Stream string `json:"stream"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Architectures map[string]Arch `json:"architectures"`
|
||||
}
|
||||
|
||||
// Metadata for a release or stream
|
||||
type Metadata struct {
|
||||
LastModified string `json:"last-modified"`
|
||||
}
|
||||
|
||||
// Arch contains release details for a particular hardware architecture
|
||||
type Arch struct {
|
||||
Artifacts map[string]PlatformArtifacts `json:"artifacts"`
|
||||
Images Images `json:"images,omitempty"`
|
||||
// RHELCoreOSExtensions is data specific to Red Hat Enterprise Linux CoreOS
|
||||
RHELCoreOSExtensions *rhcos.Extensions `json:"rhel-coreos-extensions,omitempty"`
|
||||
}
|
||||
|
||||
// PlatformArtifacts contains images for a platform
|
||||
type PlatformArtifacts struct {
|
||||
Release string `json:"release"`
|
||||
Formats map[string]ImageFormat `json:"formats"`
|
||||
}
|
||||
|
||||
// ImageFormat contains all artifacts for a single OS image
|
||||
type ImageFormat struct {
|
||||
Disk *Artifact `json:"disk,omitempty"`
|
||||
Kernel *Artifact `json:"kernel,omitempty"`
|
||||
Initramfs *Artifact `json:"initramfs,omitempty"`
|
||||
Rootfs *Artifact `json:"rootfs,omitempty"`
|
||||
}
|
||||
|
||||
// Artifact represents one image file, plus its metadata
|
||||
type Artifact struct {
|
||||
Location string `json:"location"`
|
||||
Signature string `json:"signature"`
|
||||
Sha256 string `json:"sha256"`
|
||||
UncompressedSha256 string `json:"uncompressed-sha256,omitempty"`
|
||||
}
|
||||
|
||||
// Images contains images available in cloud providers
|
||||
type Images struct {
|
||||
Aws *AwsImage `json:"aws,omitempty"`
|
||||
Gcp *GcpImage `json:"gcp,omitempty"`
|
||||
}
|
||||
|
||||
// AwsImage represents an image across all AWS regions
|
||||
type AwsImage struct {
|
||||
Regions map[string]AwsRegionImage `json:"regions,omitempty"`
|
||||
}
|
||||
|
||||
// AwsRegionImage represents an image in one AWS region
|
||||
type AwsRegionImage struct {
|
||||
Release string `json:"release"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
// GcpImage represents a GCP cloud image
|
||||
type GcpImage struct {
|
||||
Project string `json:"project,omitempty"`
|
||||
Family string `json:"family,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
47
vendor/github.com/coreos/stream-metadata-go/stream/stream_utils.go
generated
vendored
Normal file
47
vendor/github.com/coreos/stream-metadata-go/stream/stream_utils.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package stream
|
||||
|
||||
import "fmt"
|
||||
|
||||
// FormatPrefix describes a stream+architecture combination, intended for prepending to error messages
|
||||
func (st *Stream) FormatPrefix(archname string) string {
|
||||
return fmt.Sprintf("%s/%s", st.Stream, archname)
|
||||
}
|
||||
|
||||
// GetArchitecture loads the architecture-specific builds from a stream,
|
||||
// with a useful descriptive error message if the architecture is not found.
|
||||
func (st *Stream) GetArchitecture(archname string) (*Arch, error) {
|
||||
archdata, ok := st.Architectures[archname]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("stream:%s does not have architecture '%s'", st.Stream, archname)
|
||||
}
|
||||
return &archdata, nil
|
||||
}
|
||||
|
||||
// GetAwsRegionImage returns the release data (AMI and release ID) for a particular
|
||||
// architecture and region.
|
||||
func (st *Stream) GetAwsRegionImage(archname, region string) (*AwsRegionImage, error) {
|
||||
starch, err := st.GetArchitecture(archname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
awsimages := starch.Images.Aws
|
||||
if awsimages == nil {
|
||||
return nil, fmt.Errorf("%s: No AWS images", st.FormatPrefix(archname))
|
||||
}
|
||||
var regionVal AwsRegionImage
|
||||
var ok bool
|
||||
if regionVal, ok = awsimages.Regions[region]; !ok {
|
||||
return nil, fmt.Errorf("%s: No AWS images in region %s", st.FormatPrefix(archname), region)
|
||||
}
|
||||
|
||||
return ®ionVal, nil
|
||||
}
|
||||
|
||||
// GetAMI returns the AWS machine image for a particular architecture and region.
|
||||
func (st *Stream) GetAMI(archname, region string) (string, error) {
|
||||
regionVal, err := st.GetAwsRegionImage(archname, region)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return regionVal.Image, nil
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
language: go
|
||||
os: linux
|
||||
dist: bionic
|
||||
sudo: require
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ccache
|
||||
|
||||
go:
|
||||
- "1.15"
|
||||
|
||||
env:
|
||||
global:
|
||||
- CCACHE_TEMPDIR=/tmp/.ccache-temp
|
||||
matrix:
|
||||
- LIBVIRT=2.3.0 EXT=xz
|
||||
- LIBVIRT=3.1.0 EXT=xz
|
||||
- LIBVIRT=5.1.0 EXT=xz
|
||||
|
||||
before_install:
|
||||
- go get golang.org/x/lint/golint
|
||||
- go get golang.org/x/tools/cmd/goyacc
|
||||
|
||||
install:
|
||||
# credit here goes to the go-libvirt authors,
|
||||
# see: https://github.com/rgbkrk/libvirt-go/blob/master/.travis.yml
|
||||
- sudo apt-get -qqy build-dep libvirt
|
||||
- sudo apt-get -qqy install curl qemu-system-x86
|
||||
- sudo mkdir -p /usr/src && sudo chown $(id -u) /usr/src
|
||||
- curl -O -s https://libvirt.org/sources/libvirt-${LIBVIRT}.tar.${EXT}
|
||||
- tar -C /usr/src -xf libvirt-${LIBVIRT}.tar.${EXT}
|
||||
- pushd /usr/src/libvirt-${LIBVIRT}
|
||||
- ccache --show-stats
|
||||
- |
|
||||
env PATH=/usr/lib/ccache:$PATH \
|
||||
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc \
|
||||
--without-polkit \
|
||||
--without-esx --without-vbox --without-xen --without-libxl --without-lxc \
|
||||
--with-qemu
|
||||
- make
|
||||
- sudo make install
|
||||
- ccache --show-stats
|
||||
- popd
|
||||
- sudo libvirtd -d -l -f libvirtd.conf
|
||||
- sudo virtlogd -d || true
|
||||
|
||||
before_script:
|
||||
- go get -d ./...
|
||||
- sudo qemu-img create -f raw -o size=10M /var/lib/libvirt/images/test.raw
|
||||
- sudo virsh define .travis/test-domain.xml
|
||||
- sudo virsh start test
|
||||
- sudo virsh pool-create .travis/test-pool.xml
|
||||
- sudo virsh secret-define .travis/test-secret.xml
|
||||
|
||||
script:
|
||||
- ./scripts/licensecheck.sh
|
||||
- LIBVIRT_SOURCE=/usr/src/libvirt-${LIBVIRT} go generate ./...
|
||||
- go build ./...
|
||||
- golint -set_exit_status ./...
|
||||
- go vet ./...
|
||||
- go test -v -tags=integration ./...
|
|
@ -0,0 +1,24 @@
|
|||
Maintainer
|
||||
----------
|
||||
DigitalOcean, Inc
|
||||
|
||||
Original Authors
|
||||
----------------
|
||||
Ben LeMasurier <blemasurier@digitalocean.com>
|
||||
Matt Layher <mlayher@digitalocean.com>
|
||||
|
||||
Contributors
|
||||
------------
|
||||
Justin Kim <justin@digitalocean.com>
|
||||
Ricky Medina <rm@do.co>
|
||||
Charlie Drage <charlie@charliedrage.com>
|
||||
Michael Koppmann <me@mkoppmann.at>
|
||||
Simarpreet Singh <simar@linux.com>
|
||||
Alexander Polyakov <apolyakov@beget.com>
|
||||
Amanda Andrade <amanda.andrade@serpro.gov.br>
|
||||
Geoff Hickey <ghickey@digitalocean.com>
|
||||
Yuriy Taraday <yorik.sar@gmail.com>
|
||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||
David Schneider <dsbrng25b@gmail.com>
|
||||
Alec Hothan <ahothan@gmail.com>
|
||||
Akos Varga <vrgakos@gmail.com>
|
|
@ -0,0 +1,30 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
The `go-libvirt` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/)
|
||||
for contributions.
|
||||
|
||||
If you'd like to contribute to the project, please
|
||||
[open an issue](https://github.com/digitalocean/go-libvirt/issues/new) or find an
|
||||
[existing issue](https://github.com/digitalocean/go-libvirt/issues) that you'd like
|
||||
to take on. This ensures that efforts are not duplicated, and that a new feature
|
||||
aligns with the focus of the rest of the repository.
|
||||
|
||||
Once your suggestion has been submitted and discussed, please be sure that your
|
||||
code meets the following criteria:
|
||||
- code is completely `gofmt`'d
|
||||
- new features or codepaths have appropriate test coverage
|
||||
- `go test ./...` passes
|
||||
- `go vet ./...` passes
|
||||
- `golint ./...` returns no warnings, including documentation comment warnings
|
||||
|
||||
In addition, if this is your first time contributing to the `go-libvirt` project,
|
||||
add your name and email address to the
|
||||
[AUTHORS](https://github.com/digitalocean/go-libvirt/blob/master/AUTHORS) file
|
||||
under the "Contributors" section using the format:
|
||||
`First Last <email@example.com>`.
|
||||
|
||||
Finally, submit a pull request for review!
|
||||
|
||||
Questions? Feel free to join us in [`#go-qemu` on freenode](https://webchat.freenode.net/)
|
||||
if you'd like to discuss the project.
|
|
@ -0,0 +1,195 @@
|
|||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 4. Redistribution
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
#### 6. Trademarks
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
### APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets `[]` replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same “printed page” as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
libvirt [](http://godoc.org/github.com/digitalocean/go-libvirt) [](https://travis-ci.org/digitalocean/go-libvirt) [](https://goreportcard.com/report/github.com/digitalocean/go-libvirt)
|
||||
====
|
||||
|
||||
Package `go-libvirt` provides a pure Go interface for interacting with libvirt.
|
||||
|
||||
Rather than using libvirt's C bindings, this package makes use of
|
||||
libvirt's RPC interface, as documented [here](https://libvirt.org/internals/rpc.html).
|
||||
Connections to the libvirt server may be local, or remote. RPC packets are encoded
|
||||
using the XDR standard as defined by [RFC 4506](https://tools.ietf.org/html/rfc4506.html).
|
||||
|
||||
libvirt's RPC interface is quite extensive, and changes from one version to the
|
||||
next, so this project uses a pair of code generators to build the go bindings.
|
||||
The code generators should be run whenever you want to build go-libvirt for a
|
||||
new version of libvirt. See the next section for directions on re-generating
|
||||
go-libvirt.
|
||||
|
||||
[Pull requests are welcome](https://github.com/digitalocean/go-libvirt/blob/master/CONTRIBUTING.md)!
|
||||
|
||||
Running the Code Generators
|
||||
---------------------------
|
||||
|
||||
The code generator doesn't run automatically when you build go-libvirt. It's
|
||||
meant to be run manually any time you change the version of libvirt you're
|
||||
using. When you download go-libvirt it will come with generated files
|
||||
corresponding to a particular version of libvirt. You can use the library as-is,
|
||||
but the generated code may be missing libvirt functions, if you're using a newer
|
||||
version of libvirt, or it may have extra functions that will return
|
||||
'unimplemented' errors if you try to call them. If this is a problem, you should
|
||||
re-run the code generator. To do this, follow these steps:
|
||||
|
||||
- First, download a copy of the libvirt sources corresponding to the version you
|
||||
want to use.
|
||||
- Next, run `autogen.sh` in the libvirt directory. The autotools will check for
|
||||
necessary libraries and prepare libvirt for building. We don't actually need
|
||||
to build libvirt, but we do require some header files that are produced in
|
||||
this step.
|
||||
- Finally, set the environment variable `LIBVIRT_SOURCE` to the directory you
|
||||
put libvirt into, and run `go generate ./...` from the go-libvirt directory.
|
||||
This runs both of the go-libvirt's code generators.
|
||||
|
||||
How to Use This Library
|
||||
-----------------------
|
||||
|
||||
Once you've vendored go-libvirt into your project, you'll probably want to call
|
||||
some libvirt functions. There's some example code below showing how to connect
|
||||
to libvirt and make one such call, but once you get past the introduction you'll
|
||||
next want to call some other libvirt functions. How do you find them?
|
||||
|
||||
Start with the [libvirt API reference](https://libvirt.org/html/index.html).
|
||||
Let's say you want to gracefully shutdown a VM, and after reading through the
|
||||
libvirt docs you determine that virDomainShutdown() is the function you want to
|
||||
call to do that. Where's that function in go-libvirt? We transform the names
|
||||
slightly when building the go bindings. There's no need for a global prefix like
|
||||
"vir" in Go, since all our functions are inside the package namespace, so we
|
||||
drop it. That means the Go function for `virDomainShutdown()` is just `DomainShutdown()`,
|
||||
and sure enough, you can find the Go function `DomainShutdown()` in libvirt.gen.go,
|
||||
with parameters and return values equivalent to those documented in the API
|
||||
reference.
|
||||
|
||||
Suppose you then decide you need more control over your shutdown, so you switch
|
||||
over to `virDomainShutdownFlags()`. As its name suggests, this function takes a
|
||||
flag parameter which has possible values specified in an enum called
|
||||
`virDomainShutdownFlagValues`. Flag types like this are a little tricky for the
|
||||
code generator, because the C functions just take an integer type - only the
|
||||
libvirt documentation actually ties the flags to the enum types. In most cases
|
||||
though we're able to generate a wrapper function with a distinct flag type,
|
||||
making it easier for Go tooling to suggest possible flag values while you're
|
||||
working. Checking the documentation for this function:
|
||||
|
||||
`godoc github.com/digitalocean/go-libvirt DomainShutdownFlags`
|
||||
|
||||
returns this:
|
||||
|
||||
`func (l *Libvirt) DomainShutdownFlags(Dom Domain, Flags DomainShutdownFlagValues) (err error)`
|
||||
|
||||
If you want to see the possible flag values, `godoc` can help again:
|
||||
|
||||
```
|
||||
$ godoc github.com/digitalocean/go-libvirt DomainShutdownFlagValues
|
||||
|
||||
type DomainShutdownFlagValues int32
|
||||
DomainShutdownFlagValues as declared in libvirt/libvirt-domain.h:1121
|
||||
|
||||
const (
|
||||
DomainShutdownDefault DomainShutdownFlagValues = iota
|
||||
DomainShutdownAcpiPowerBtn DomainShutdownFlagValues = 1
|
||||
DomainShutdownGuestAgent DomainShutdownFlagValues = 2
|
||||
DomainShutdownInitctl DomainShutdownFlagValues = 4
|
||||
DomainShutdownSignal DomainShutdownFlagValues = 8
|
||||
DomainShutdownParavirt DomainShutdownFlagValues = 16
|
||||
)
|
||||
DomainShutdownFlagValues enumeration from libvirt/libvirt-domain.h:1121
|
||||
```
|
||||
|
||||
One other suggestion: most of the code in go-libvirt is now generated, but a few
|
||||
hand-written routines still exist in libvirt.go, and wrap calls to the generated
|
||||
code with slightly different parameters or return values. We suggest avoiding
|
||||
these hand-written routines and calling the generated routines in libvirt.gen.go
|
||||
instead. Over time these handwritten routines will be removed from go-libvirt.
|
||||
|
||||
Warning
|
||||
-------
|
||||
|
||||
While these package are reasonably well-tested and have seen some use inside of
|
||||
DigitalOcean, there may be subtle bugs which could cause the packages to act
|
||||
in unexpected ways. Use at your own risk!
|
||||
|
||||
In addition, the API is not considered stable at this time. If you would like
|
||||
to include package `libvirt` in a project, we highly recommend vendoring it into
|
||||
your project.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This dials libvirt on the local machine, but you can substitute the first
|
||||
// two parameters with "tcp", "<ip address>:<port>" to connect to libvirt on
|
||||
// a remote machine.
|
||||
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to dial libvirt: %v", err)
|
||||
}
|
||||
|
||||
l := libvirt.New(c)
|
||||
if err := l.Connect(); err != nil {
|
||||
log.Fatalf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
v, err := l.Version()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve libvirt version: %v", err)
|
||||
}
|
||||
fmt.Println("Version:", v)
|
||||
|
||||
domains, err := l.Domains()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve domains: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("ID\tName\t\tUUID")
|
||||
fmt.Printf("--------------------------------------------------------\n")
|
||||
for _, d := range domains {
|
||||
fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
|
||||
}
|
||||
|
||||
if err := l.Disconnect(); err != nil {
|
||||
log.Fatalf("failed to disconnect: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
Version: 1.3.4
|
||||
ID Name UUID
|
||||
--------------------------------------------------------
|
||||
1 Test-1 dc329f87d4de47198cfd2e21c6105b01
|
||||
2 Test-2 dc229f87d4de47198cfd2e21c6105b01
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2016 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package libvirt provides a pure Go interface for Libvirt.
|
||||
|
||||
Rather than using Libvirt's C bindings, this package makes use of
|
||||
Libvirt's RPC interface, as documented here: https://libvirt.org/internals/rpc.html.
|
||||
Connections to the libvirt server may be local, or remote. RPC packets are encoded
|
||||
using the XDR standard as defined by RFC 4506.
|
||||
|
||||
This should be considered a work in progress. Most functionaly provided by the C
|
||||
bindings have not yet made their way into this library. Pull requests are welcome!
|
||||
The definition of the RPC protocol is in the libvirt source tree under src/rpc/virnetprotocol.x.
|
||||
|
||||
Example usage:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//c, err := net.DialTimeout("tcp", "127.0.0.1:16509", 2*time.Second)
|
||||
//c, err := net.DialTimeout("tcp", "192.168.1.12:16509", 2*time.Second)
|
||||
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to dial libvirt: %v", err)
|
||||
}
|
||||
|
||||
l := libvirt.New(c)
|
||||
if err := l.Connect(); err != nil {
|
||||
log.Fatalf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
v, err := l.Version()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve libvirt version: %v", err)
|
||||
}
|
||||
fmt.Println("Version:", v)
|
||||
|
||||
domains, err := l.Domains()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to retrieve domains: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("ID\tName\t\tUUID")
|
||||
fmt.Printf("--------------------------------------------------------\n")
|
||||
for _, d := range domains {
|
||||
fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
|
||||
}
|
||||
|
||||
if err := l.Disconnect(); err != nil {
|
||||
log.Fatal("failed to disconnect: %v", err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
package libvirt
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/digitalocean/go-libvirt
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
||||
golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d
|
||||
)
|
|
@ -0,0 +1,35 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d h1:F3OmlXCzYtG9YE6tXDnUOlJBzVzHF8EcmZ1yTJlcgIk=
|
||||
golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
47
vendor/github.com/digitalocean/go-libvirt/internal/constants/qemu_protocol.gen.go
generated
vendored
Normal file
47
vendor/github.com/digitalocean/go-libvirt/internal/constants/qemu_protocol.gen.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//
|
||||
// Code generated by internal/lvgen/generate.go. DO NOT EDIT.
|
||||
//
|
||||
// To regenerate, run 'go generate' in internal/lvgen.
|
||||
//
|
||||
|
||||
package constants
|
||||
|
||||
// These are libvirt procedure numbers which correspond to each respective
|
||||
// API call between remote_internal driver and libvirtd. Each procedure is
|
||||
// identified by a unique number.
|
||||
const (
|
||||
// From enums:
|
||||
// QEMUProcDomainMonitorCommand is libvirt's QEMU_PROC_DOMAIN_MONITOR_COMMAND
|
||||
QEMUProcDomainMonitorCommand = 1
|
||||
// QEMUProcDomainAttach is libvirt's QEMU_PROC_DOMAIN_ATTACH
|
||||
QEMUProcDomainAttach = 2
|
||||
// QEMUProcDomainAgentCommand is libvirt's QEMU_PROC_DOMAIN_AGENT_COMMAND
|
||||
QEMUProcDomainAgentCommand = 3
|
||||
// QEMUProcConnectDomainMonitorEventRegister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER
|
||||
QEMUProcConnectDomainMonitorEventRegister = 4
|
||||
// QEMUProcConnectDomainMonitorEventDeregister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER
|
||||
QEMUProcConnectDomainMonitorEventDeregister = 5
|
||||
// QEMUProcDomainMonitorEvent is libvirt's QEMU_PROC_DOMAIN_MONITOR_EVENT
|
||||
QEMUProcDomainMonitorEvent = 6
|
||||
|
||||
|
||||
// From consts:
|
||||
// QEMUProgram is libvirt's QEMU_PROGRAM
|
||||
QEMUProgram = 0x20008087
|
||||
// QEMUProtocolVersion is libvirt's QEMU_PROTOCOL_VERSION
|
||||
QEMUProtocolVersion = 1
|
||||
)
|
1019
vendor/github.com/digitalocean/go-libvirt/internal/constants/remote_protocol.gen.go
generated
vendored
Normal file
1019
vendor/github.com/digitalocean/go-libvirt/internal/constants/remote_protocol.gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2020 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package event
|
||||
|
||||
// Event represents an internal Event.
|
||||
type Event interface {
|
||||
GetCallbackID() int32
|
||||
}
|
145
vendor/github.com/digitalocean/go-libvirt/internal/event/stream.go
generated
vendored
Normal file
145
vendor/github.com/digitalocean/go-libvirt/internal/event/stream.go
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Copyright 2020 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package event
|
||||
|
||||
import "context"
|
||||
|
||||
// Stream is an unbounded buffered event channel. The implementation
|
||||
// consists of a pair of unbuffered channels and a goroutine to manage them.
|
||||
// Client behavior will not cause incoming events to block.
|
||||
type Stream struct {
|
||||
// Program specifies the source of the events - libvirt or QEMU.
|
||||
Program uint32
|
||||
|
||||
// CallbackID is returned by the event registration call.
|
||||
CallbackID int32
|
||||
|
||||
// manage unbounded channel behavior.
|
||||
queue []Event
|
||||
in, out chan Event
|
||||
|
||||
// terminates processing
|
||||
shutdown context.CancelFunc
|
||||
}
|
||||
|
||||
// Recv returns the next available event from the Stream's queue.
|
||||
func (s *Stream) Recv() chan Event {
|
||||
return s.out
|
||||
}
|
||||
|
||||
// Push appends a new event to the queue.
|
||||
func (s *Stream) Push(e Event) {
|
||||
s.in <- e
|
||||
}
|
||||
|
||||
// Shutdown gracefully terminates Stream processing, releasing all
|
||||
// internal resources. Events which have not yet been received by the client
|
||||
// will be dropped. Subsequent calls to Shutdown() are idempotent.
|
||||
func (s *Stream) Shutdown() {
|
||||
if s.shutdown != nil {
|
||||
s.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// start starts the event processing loop, which will continue to run until
|
||||
// terminated by the returned context.CancelFunc. Starting a previously started
|
||||
// Stream is an idempotent operation.
|
||||
func (s *Stream) start() context.CancelFunc {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go s.process(ctx)
|
||||
|
||||
return cancel
|
||||
}
|
||||
|
||||
// process manages an Stream's lifecycle until canceled by the provided
|
||||
// context. Incoming events are appended to a queue which is then relayed to
|
||||
// the a listening client. New events pushed onto the queue will not block due
|
||||
// to client behavior.
|
||||
func (s *Stream) process(ctx context.Context) {
|
||||
defer func() {
|
||||
close(s.in)
|
||||
close(s.out)
|
||||
}()
|
||||
|
||||
for {
|
||||
// informs send() to stop trying
|
||||
nctx, next := context.WithCancel(ctx)
|
||||
defer next()
|
||||
|
||||
select {
|
||||
// new event received, append to queue
|
||||
case e := <-s.in:
|
||||
s.queue = append(s.queue, e)
|
||||
|
||||
// client recieved an event, pop from queue
|
||||
case <-s.send(nctx):
|
||||
if len(s.queue) > 1 {
|
||||
s.queue = s.queue[1:]
|
||||
} else {
|
||||
s.queue = []Event{}
|
||||
}
|
||||
|
||||
// shutdown requested
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
// send returns a channel which blocks until either the first item on the queue
|
||||
// (if existing) is sent to the client, or the provided context is canceled.
|
||||
// The stream's queue is never modified.
|
||||
func (s *Stream) send(ctx context.Context) <-chan struct{} {
|
||||
ch := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
|
||||
// do nothing and block if the queue is empty
|
||||
if len(s.queue) == 0 {
|
||||
<-ctx.Done()
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, attempt to send the event
|
||||
select {
|
||||
case s.out <- s.queue[0]:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// NewStream configures a new Event Stream. Incoming events are appended to a
|
||||
// queue, which is then relayed to the listening client. Client behavior will
|
||||
// not cause incoming events to block. It is the responsibility of the caller
|
||||
// to terminate the Stream via Shutdown() when no longer in use.
|
||||
func NewStream(program uint32, cbID int32) *Stream {
|
||||
ic := &Stream{
|
||||
Program: program,
|
||||
CallbackID: cbID,
|
||||
in: make(chan Event),
|
||||
out: make(chan Event),
|
||||
}
|
||||
|
||||
ic.shutdown = ic.start()
|
||||
|
||||
return ic
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
896
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/decode.go
generated
vendored
Normal file
896
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/decode.go
generated
vendored
Normal file
|
@ -0,0 +1,896 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
errMaxSlice = "data exceeds max slice limit"
|
||||
errIODecode = "%s while decoding %d bytes"
|
||||
)
|
||||
|
||||
/*
|
||||
Unmarshal parses XDR-encoded data into the value pointed to by v reading from
|
||||
reader r and returning the total number of bytes read. An addressable pointer
|
||||
must be provided since Unmarshal needs to both store the result of the decode as
|
||||
well as obtain target type information. Unmarhsal traverses v recursively and
|
||||
automatically indirects pointers through arbitrary depth, allocating them as
|
||||
necessary, to decode the data into the underlying value pointed to.
|
||||
|
||||
Unmarshal uses reflection to determine the type of the concrete value contained
|
||||
by v and performs a mapping of underlying XDR types to Go types as follows:
|
||||
|
||||
Go Type <- XDR Type
|
||||
--------------------
|
||||
int8, int16, int32, int <- XDR Integer
|
||||
uint8, uint16, uint32, uint <- XDR Unsigned Integer
|
||||
int64 <- XDR Hyper Integer
|
||||
uint64 <- XDR Unsigned Hyper Integer
|
||||
bool <- XDR Boolean
|
||||
float32 <- XDR Floating-Point
|
||||
float64 <- XDR Double-Precision Floating-Point
|
||||
string <- XDR String
|
||||
byte <- XDR Integer
|
||||
[]byte <- XDR Variable-Length Opaque Data
|
||||
[#]byte <- XDR Fixed-Length Opaque Data
|
||||
[]<type> <- XDR Variable-Length Array
|
||||
[#]<type> <- XDR Fixed-Length Array
|
||||
struct <- XDR Structure
|
||||
map <- XDR Variable-Length Array of two-element XDR Structures
|
||||
time.Time <- XDR String encoded with RFC3339 nanosecond precision
|
||||
|
||||
Notes and Limitations:
|
||||
|
||||
* Automatic unmarshalling of variable and fixed-length arrays of uint8s
|
||||
requires a special struct tag `xdropaque:"false"` since byte slices
|
||||
and byte arrays are assumed to be opaque data and byte is a Go alias
|
||||
for uint8 thus indistinguishable under reflection
|
||||
* Cyclic data structures are not supported and will result in infinite
|
||||
loops
|
||||
|
||||
If any issues are encountered during the unmarshalling process, an
|
||||
UnmarshalError is returned with a human readable description as well as
|
||||
an ErrorCode value for further inspection from sophisticated callers. Some
|
||||
potential issues are unsupported Go types, attempting to decode a value which is
|
||||
too large to fit into a specified Go type, and exceeding max slice limitations.
|
||||
*/
|
||||
func Unmarshal(r io.Reader, v interface{}) (int, error) {
|
||||
d := Decoder{r: r}
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// UnmarshalLimited is identical to Unmarshal but it sets maxReadSize in order
|
||||
// to cap reads.
|
||||
func UnmarshalLimited(r io.Reader, v interface{}, maxSize uint) (int, error) {
|
||||
d := Decoder{r: r, maxReadSize: maxSize}
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// TypeDecoder lets a caller provide a custom decode routine for a custom type.
|
||||
type TypeDecoder interface {
|
||||
Decode(*Decoder, reflect.Value) (int, error)
|
||||
}
|
||||
|
||||
// A Decoder wraps an io.Reader that is expected to provide an XDR-encoded byte
|
||||
// stream and provides several exposed methods to manually decode various XDR
|
||||
// primitives without relying on reflection. The NewDecoder function can be
|
||||
// used to get a new Decoder directly.
|
||||
//
|
||||
// Typically, Unmarshal should be used instead of manual decoding. A Decoder
|
||||
// is exposed so it is possible to perform manual decoding should it be
|
||||
// necessary in complex scenarios where automatic reflection-based decoding
|
||||
// won't work.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
|
||||
// maxReadSize is the default maximum bytes an element can contain. 0
|
||||
// is unlimited and provides backwards compatability. Setting it to a
|
||||
// non-zero value caps reads.
|
||||
maxReadSize uint
|
||||
|
||||
// customTypes is a map allowing the caller to provide decoder routines for
|
||||
// custom types known only to itself.
|
||||
customTypes map[string]TypeDecoder
|
||||
}
|
||||
|
||||
// DecodeInt treats the next 4 bytes as an XDR encoded integer and returns the
|
||||
// result as an int32 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.1 - Integer
|
||||
// 32-bit big-endian signed integer in range [-2147483648, 2147483647]
|
||||
func (d *Decoder) DecodeInt() (int32, int, error) {
|
||||
var buf [4]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
|
||||
err := unmarshalError("DecodeInt", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := int32(buf[3]) | int32(buf[2])<<8 |
|
||||
int32(buf[1])<<16 | int32(buf[0])<<24
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeUint treats the next 4 bytes as an XDR encoded unsigned integer and
|
||||
// returns the result as a uint32 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.2 - Unsigned Integer
|
||||
// 32-bit big-endian unsigned integer in range [0, 4294967295]
|
||||
func (d *Decoder) DecodeUint() (uint32, int, error) {
|
||||
var buf [4]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
|
||||
err := unmarshalError("DecodeUint", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := uint32(buf[3]) | uint32(buf[2])<<8 |
|
||||
uint32(buf[1])<<16 | uint32(buf[0])<<24
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeEnum treats the next 4 bytes as an XDR encoded enumeration value and
|
||||
// returns the result as an int32 after verifying that the value is in the
|
||||
// provided map of valid values. It also returns the number of bytes actually
|
||||
// read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the parsed enumeration value is not one of the provided valid values.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.3 - Enumeration
|
||||
// Represented as an XDR encoded signed integer
|
||||
func (d *Decoder) DecodeEnum(validEnums map[int32]bool) (int32, int, error) {
|
||||
val, n, err := d.DecodeInt()
|
||||
if err != nil {
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
if !validEnums[val] {
|
||||
err := unmarshalError("DecodeEnum", ErrBadEnumValue,
|
||||
"invalid enum", val, nil)
|
||||
return 0, n, err
|
||||
}
|
||||
return val, n, nil
|
||||
}
|
||||
|
||||
// DecodeBool treats the next 4 bytes as an XDR encoded boolean value and
|
||||
// returns the result as a bool along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the parsed value is not a 0 or 1.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.4 - Boolean
|
||||
// Represented as an XDR encoded enumeration where 0 is false and 1 is true
|
||||
func (d *Decoder) DecodeBool() (bool, int, error) {
|
||||
val, n, err := d.DecodeInt()
|
||||
if err != nil {
|
||||
return false, n, err
|
||||
}
|
||||
switch val {
|
||||
case 0:
|
||||
return false, n, nil
|
||||
case 1:
|
||||
return true, n, nil
|
||||
}
|
||||
|
||||
err = unmarshalError("DecodeBool", ErrBadEnumValue, "bool not 0 or 1",
|
||||
val, nil)
|
||||
return false, n, err
|
||||
}
|
||||
|
||||
// DecodeHyper treats the next 8 bytes as an XDR encoded hyper value and
|
||||
// returns the result as an int64 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Hyper Integer
|
||||
// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807]
|
||||
func (d *Decoder) DecodeHyper() (int64, int, error) {
|
||||
var buf [8]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
|
||||
err := unmarshalError("DecodeHyper", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := int64(buf[7]) | int64(buf[6])<<8 |
|
||||
int64(buf[5])<<16 | int64(buf[4])<<24 |
|
||||
int64(buf[3])<<32 | int64(buf[2])<<40 |
|
||||
int64(buf[1])<<48 | int64(buf[0])<<56
|
||||
return rv, n, err
|
||||
}
|
||||
|
||||
// DecodeUhyper treats the next 8 bytes as an XDR encoded unsigned hyper value
|
||||
// and returns the result as a uint64 along with the number of bytes actually
|
||||
// read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Unsigned Hyper Integer
|
||||
// 64-bit big-endian unsigned integer in range [0, 18446744073709551615]
|
||||
func (d *Decoder) DecodeUhyper() (uint64, int, error) {
|
||||
var buf [8]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
|
||||
err := unmarshalError("DecodeUhyper", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
rv := uint64(buf[7]) | uint64(buf[6])<<8 |
|
||||
uint64(buf[5])<<16 | uint64(buf[4])<<24 |
|
||||
uint64(buf[3])<<32 | uint64(buf[2])<<40 |
|
||||
uint64(buf[1])<<48 | uint64(buf[0])<<56
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeFloat treats the next 4 bytes as an XDR encoded floating point and
|
||||
// returns the result as a float32 along with the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.6 - Floating Point
|
||||
// 32-bit single-precision IEEE 754 floating point
|
||||
func (d *Decoder) DecodeFloat() (float32, int, error) {
|
||||
var buf [4]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
|
||||
err := unmarshalError("DecodeFloat", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
val := uint32(buf[3]) | uint32(buf[2])<<8 |
|
||||
uint32(buf[1])<<16 | uint32(buf[0])<<24
|
||||
return math.Float32frombits(val), n, nil
|
||||
}
|
||||
|
||||
// DecodeDouble treats the next 8 bytes as an XDR encoded double-precision
|
||||
// floating point and returns the result as a float64 along with the number of
|
||||
// bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.7 - Double-Precision Floating Point
|
||||
// 64-bit double-precision IEEE 754 floating point
|
||||
func (d *Decoder) DecodeDouble() (float64, int, error) {
|
||||
var buf [8]byte
|
||||
n, err := io.ReadFull(d.r, buf[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
|
||||
err := unmarshalError("DecodeDouble", ErrIO, msg, buf[:n], err)
|
||||
return 0, n, err
|
||||
}
|
||||
|
||||
val := uint64(buf[7]) | uint64(buf[6])<<8 |
|
||||
uint64(buf[5])<<16 | uint64(buf[4])<<24 |
|
||||
uint64(buf[3])<<32 | uint64(buf[2])<<40 |
|
||||
uint64(buf[1])<<48 | uint64(buf[0])<<56
|
||||
return math.Float64frombits(val), n, nil
|
||||
}
|
||||
|
||||
// RFC Section 4.8 - Quadruple-Precision Floating Point
|
||||
// 128-bit quadruple-precision floating point
|
||||
// Not Implemented
|
||||
|
||||
// DecodeFixedOpaque treats the next 'size' bytes as XDR encoded opaque data and
|
||||
// returns the result as a byte slice along with the number of bytes actually
|
||||
// read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining to
|
||||
// satisfy the passed size, including the necessary padding to make it a
|
||||
// multiple of 4.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.9 - Fixed-Length Opaque Data
|
||||
// Fixed-length uninterpreted data zero-padded to a multiple of four
|
||||
func (d *Decoder) DecodeFixedOpaque(size int32) ([]byte, int, error) {
|
||||
// Nothing to do if size is 0.
|
||||
if size == 0 {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
pad := (4 - (size % 4)) % 4
|
||||
paddedSize := size + pad
|
||||
if uint(paddedSize) > uint(math.MaxInt32) {
|
||||
err := unmarshalError("DecodeFixedOpaque", ErrOverflow,
|
||||
errMaxSlice, paddedSize, nil)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
buf := make([]byte, paddedSize)
|
||||
n, err := io.ReadFull(d.r, buf)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIODecode, err.Error(), paddedSize)
|
||||
err := unmarshalError("DecodeFixedOpaque", ErrIO, msg, buf[:n],
|
||||
err)
|
||||
return nil, n, err
|
||||
}
|
||||
return buf[0:size], n, nil
|
||||
}
|
||||
|
||||
// DecodeOpaque treats the next bytes as variable length XDR encoded opaque
|
||||
// data and returns the result as a byte slice along with the number of bytes
|
||||
// actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the opaque data is larger than the max length of a Go slice.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.10 - Variable-Length Opaque Data
|
||||
// Unsigned integer length followed by fixed opaque data of that length
|
||||
func (d *Decoder) DecodeOpaque() ([]byte, int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return nil, n, err
|
||||
}
|
||||
if uint(dataLen) > uint(math.MaxInt32) ||
|
||||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
|
||||
err := unmarshalError("DecodeOpaque", ErrOverflow, errMaxSlice,
|
||||
dataLen, nil)
|
||||
return nil, n, err
|
||||
}
|
||||
|
||||
rv, n2, err := d.DecodeFixedOpaque(int32(dataLen))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return nil, n, err
|
||||
}
|
||||
return rv, n, nil
|
||||
}
|
||||
|
||||
// DecodeString treats the next bytes as a variable length XDR encoded string
|
||||
// and returns the result as a string along with the number of bytes actually
|
||||
// read. Character encoding is assumed to be UTF-8 and therefore ASCII
|
||||
// compatible. If the underlying character encoding is not compatibile with
|
||||
// this assumption, the data can instead be read as variable-length opaque data
|
||||
// (DecodeOpaque) and manually converted as needed.
|
||||
//
|
||||
// An UnmarshalError is returned if there are insufficient bytes remaining or
|
||||
// the string data is larger than the max length of a Go slice.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.11 - String
|
||||
// Unsigned integer length followed by bytes zero-padded to a multiple of
|
||||
// four
|
||||
func (d *Decoder) DecodeString() (string, int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return "", n, err
|
||||
}
|
||||
if uint(dataLen) > uint(math.MaxInt32) ||
|
||||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
|
||||
err = unmarshalError("DecodeString", ErrOverflow, errMaxSlice,
|
||||
dataLen, nil)
|
||||
return "", n, err
|
||||
}
|
||||
|
||||
opaque, n2, err := d.DecodeFixedOpaque(int32(dataLen))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return "", n, err
|
||||
}
|
||||
return string(opaque), n, nil
|
||||
}
|
||||
|
||||
// decodeFixedArray treats the next bytes as a series of XDR encoded elements
|
||||
// of the same type as the array represented by the reflection value and decodes
|
||||
// each element into the passed array. The ignoreOpaque flag controls whether
|
||||
// or not uint8 (byte) elements should be decoded individually or as a fixed
|
||||
// sequence of opaque data. It returns the the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.12 - Fixed-Length Array
|
||||
// Individually XDR encoded array elements
|
||||
func (d *Decoder) decodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
// Treat [#]byte (byte is alias for uint8) as opaque data unless
|
||||
// ignored.
|
||||
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
data, n, err := d.DecodeFixedOpaque(int32(v.Len()))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
reflect.Copy(v, reflect.ValueOf(data))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode each array element.
|
||||
var n int
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
n2, err := d.decode(v.Index(i))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeArray treats the next bytes as a variable length series of XDR encoded
|
||||
// elements of the same type as the array represented by the reflection value.
|
||||
// The number of elements is obtained by first decoding the unsigned integer
|
||||
// element count. Then each element is decoded into the passed array. The
|
||||
// ignoreOpaque flag controls whether or not uint8 (byte) elements should be
|
||||
// decoded individually or as a variable sequence of opaque data. It returns
|
||||
// the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.13 - Variable-Length Array
|
||||
// Unsigned integer length followed by individually XDR encoded array
|
||||
// elements
|
||||
func (d *Decoder) decodeArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if uint(dataLen) > uint(math.MaxInt32) ||
|
||||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
|
||||
err := unmarshalError("decodeArray", ErrOverflow, errMaxSlice,
|
||||
dataLen, nil)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Allocate storage for the slice elements (the underlying array) if
|
||||
// existing slice does not have enough capacity.
|
||||
sliceLen := int(dataLen)
|
||||
if v.Cap() < sliceLen {
|
||||
v.Set(reflect.MakeSlice(v.Type(), sliceLen, sliceLen))
|
||||
}
|
||||
if v.Len() < sliceLen {
|
||||
v.SetLen(sliceLen)
|
||||
}
|
||||
|
||||
// Treat []byte (byte is alias for uint8) as opaque data unless ignored.
|
||||
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
data, n2, err := d.DecodeFixedOpaque(int32(sliceLen))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
v.SetBytes(data)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode each slice element.
|
||||
for i := 0; i < sliceLen; i++ {
|
||||
n2, err := d.decode(v.Index(i))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeStruct treats the next bytes as a series of XDR encoded elements
|
||||
// of the same type as the exported fields of the struct represented by the
|
||||
// passed reflection value. Pointers are automatically indirected and
|
||||
// allocated as necessary. It returns the the number of bytes actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.14 - Structure
|
||||
// XDR encoded elements in the order of their declaration in the struct
|
||||
func (d *Decoder) decodeStruct(v reflect.Value) (int, error) {
|
||||
var n int
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// Skip unexported fields.
|
||||
vtf := vt.Field(i)
|
||||
if vtf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Indirect through pointers allocating them as needed and
|
||||
// ensure the field is settable.
|
||||
vf := v.Field(i)
|
||||
vf, err := d.indirect(vf)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if !vf.CanSet() {
|
||||
msg := fmt.Sprintf("can't decode to unsettable '%v'",
|
||||
vf.Type().String())
|
||||
err := unmarshalError("decodeStruct", ErrNotSettable,
|
||||
msg, nil, nil)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Handle non-opaque data to []uint8 and [#]uint8 based on
|
||||
// struct tag.
|
||||
tag := vtf.Tag.Get("xdropaque")
|
||||
if tag == "false" {
|
||||
switch vf.Kind() {
|
||||
case reflect.Slice:
|
||||
n2, err := d.decodeArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
|
||||
case reflect.Array:
|
||||
n2, err := d.decodeFixedArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Decode each struct field.
|
||||
n2, err := d.decode(vf)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// RFC Section 4.15 - Discriminated Union
|
||||
// RFC Section 4.16 - Void
|
||||
// RFC Section 4.17 - Constant
|
||||
// RFC Section 4.18 - Typedef
|
||||
// RFC Section 4.19 - Optional data
|
||||
// RFC Sections 4.15 though 4.19 only apply to the data specification language
|
||||
// which is not implemented by this package. In the case of discriminated
|
||||
// unions, struct tags are used to perform a similar function.
|
||||
|
||||
// decodeMap treats the next bytes as an XDR encoded variable array of 2-element
|
||||
// structures whose fields are of the same type as the map keys and elements
|
||||
// represented by the passed reflection value. Pointers are automatically
|
||||
// indirected and allocated as necessary. It returns the the number of bytes
|
||||
// actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the elements.
|
||||
func (d *Decoder) decodeMap(v reflect.Value) (int, error) {
|
||||
dataLen, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Allocate storage for the underlying map if needed.
|
||||
vt := v.Type()
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.MakeMap(vt))
|
||||
}
|
||||
|
||||
// Decode each key and value according to their type.
|
||||
keyType := vt.Key()
|
||||
elemType := vt.Elem()
|
||||
for i := uint32(0); i < dataLen; i++ {
|
||||
key := reflect.New(keyType).Elem()
|
||||
n2, err := d.decode(key)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
val := reflect.New(elemType).Elem()
|
||||
n2, err = d.decode(val)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
v.SetMapIndex(key, val)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeInterface examines the interface represented by the passed reflection
|
||||
// value to detect whether it is an interface that can be decoded into and
|
||||
// if it is, extracts the underlying value to pass back into the decode function
|
||||
// for decoding according to its type. It returns the the number of bytes
|
||||
// actually read.
|
||||
//
|
||||
// An UnmarshalError is returned if any issues are encountered while decoding
|
||||
// the interface.
|
||||
func (d *Decoder) decodeInterface(v reflect.Value) (int, error) {
|
||||
if v.IsNil() || !v.CanInterface() {
|
||||
msg := fmt.Sprintf("can't decode to nil interface")
|
||||
err := unmarshalError("decodeInterface", ErrNilInterface, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Extract underlying value from the interface and indirect through
|
||||
// pointers allocating them as needed.
|
||||
ve := reflect.ValueOf(v.Interface())
|
||||
ve, err := d.indirect(ve)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ve.CanSet() {
|
||||
msg := fmt.Sprintf("can't decode to unsettable '%v'",
|
||||
ve.Type().String())
|
||||
err := unmarshalError("decodeInterface", ErrNotSettable, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
return d.decode(ve)
|
||||
}
|
||||
|
||||
// decode is the main workhorse for unmarshalling via reflection. It uses
|
||||
// the passed reflection value to choose the XDR primitives to decode from
|
||||
// the encapsulated reader. It is a recursive function,
|
||||
// so cyclic data structures are not supported and will result in an infinite
|
||||
// loop. It returns the the number of bytes actually read.
|
||||
func (d *Decoder) decode(v reflect.Value) (int, error) {
|
||||
if !v.IsValid() {
|
||||
msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String())
|
||||
err := unmarshalError("decode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Indirect through pointers allocating them as needed.
|
||||
ve, err := d.indirect(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Handle time.Time values by decoding them as an RFC3339 formatted
|
||||
// string with nanosecond precision. Check the type string rather
|
||||
// than doing a full blown conversion to interface and type assertion
|
||||
// since checking a string is much quicker.
|
||||
switch ve.Type().String() {
|
||||
case "time.Time":
|
||||
// Read the value as a string and parse it.
|
||||
timeString, n, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ttv, err := time.Parse(time.RFC3339, timeString)
|
||||
if err != nil {
|
||||
err := unmarshalError("decode", ErrParseTime,
|
||||
err.Error(), timeString, err)
|
||||
return n, err
|
||||
}
|
||||
ve.Set(reflect.ValueOf(ttv))
|
||||
return n, nil
|
||||
}
|
||||
// If this type is in our custom types map, call the decode routine set up
|
||||
// for it.
|
||||
if dt, ok := d.customTypes[ve.Type().String()]; ok {
|
||||
return dt.Decode(d, v)
|
||||
}
|
||||
|
||||
// Handle native Go types.
|
||||
switch ve.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:
|
||||
i, n, err := d.DecodeInt()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if ve.OverflowInt(int64(i)) {
|
||||
msg := fmt.Sprintf("signed integer too large to fit '%s'",
|
||||
ve.Kind().String())
|
||||
err = unmarshalError("decode", ErrOverflow, msg, i, nil)
|
||||
return n, err
|
||||
}
|
||||
ve.SetInt(int64(i))
|
||||
return n, nil
|
||||
|
||||
case reflect.Int64:
|
||||
i, n, err := d.DecodeHyper()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetInt(i)
|
||||
return n, nil
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint:
|
||||
ui, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if ve.OverflowUint(uint64(ui)) {
|
||||
msg := fmt.Sprintf("unsigned integer too large to fit '%s'",
|
||||
ve.Kind().String())
|
||||
err = unmarshalError("decode", ErrOverflow, msg, ui, nil)
|
||||
return n, err
|
||||
}
|
||||
ve.SetUint(uint64(ui))
|
||||
return n, nil
|
||||
|
||||
case reflect.Uint64:
|
||||
ui, n, err := d.DecodeUhyper()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetUint(ui)
|
||||
return n, nil
|
||||
|
||||
case reflect.Bool:
|
||||
b, n, err := d.DecodeBool()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetBool(b)
|
||||
return n, nil
|
||||
|
||||
case reflect.Float32:
|
||||
f, n, err := d.DecodeFloat()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetFloat(float64(f))
|
||||
return n, nil
|
||||
|
||||
case reflect.Float64:
|
||||
f, n, err := d.DecodeDouble()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetFloat(f)
|
||||
return n, nil
|
||||
|
||||
case reflect.String:
|
||||
s, n, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
ve.SetString(s)
|
||||
return n, nil
|
||||
|
||||
case reflect.Array:
|
||||
n, err := d.decodeFixedArray(ve, false)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Slice:
|
||||
n, err := d.decodeArray(ve, false)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Struct:
|
||||
n, err := d.decodeStruct(ve)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Map:
|
||||
n, err := d.decodeMap(ve)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
|
||||
case reflect.Interface:
|
||||
n, err := d.decodeInterface(ve)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// The only unhandled types left are unsupported. At the time of this
|
||||
// writing the only remaining unsupported types that exist are
|
||||
// reflect.Uintptr and reflect.UnsafePointer.
|
||||
msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String())
|
||||
err = unmarshalError("decode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// indirect dereferences pointers allocating them as needed until it reaches
|
||||
// a non-pointer. This allows transparent decoding through arbitrary levels
|
||||
// of indirection.
|
||||
func (d *Decoder) indirect(v reflect.Value) (reflect.Value, error) {
|
||||
rv := v
|
||||
for rv.Kind() == reflect.Ptr {
|
||||
// Allocate pointer if needed.
|
||||
isNil := rv.IsNil()
|
||||
if isNil && !rv.CanSet() {
|
||||
msg := fmt.Sprintf("unable to allocate pointer for '%v'",
|
||||
rv.Type().String())
|
||||
err := unmarshalError("indirect", ErrNotSettable, msg,
|
||||
nil, nil)
|
||||
return rv, err
|
||||
}
|
||||
if isNil {
|
||||
rv.Set(reflect.New(rv.Type().Elem()))
|
||||
}
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Decode operates identically to the Unmarshal function with the exception of
|
||||
// using the reader associated with the Decoder as the source of XDR-encoded
|
||||
// data instead of a user-supplied reader. See the Unmarhsal documentation for
|
||||
// specifics.
|
||||
func (d *Decoder) Decode(v interface{}) (int, error) {
|
||||
if v == nil {
|
||||
msg := "can't unmarshal to nil interface"
|
||||
return 0, unmarshalError("Unmarshal", ErrNilInterface, msg, nil,
|
||||
nil)
|
||||
}
|
||||
|
||||
vv := reflect.ValueOf(v)
|
||||
if vv.Kind() != reflect.Ptr {
|
||||
msg := fmt.Sprintf("can't unmarshal to non-pointer '%v' - use "+
|
||||
"& operator", vv.Type().String())
|
||||
err := unmarshalError("Unmarshal", ErrBadArguments, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
if vv.IsNil() && !vv.CanSet() {
|
||||
msg := fmt.Sprintf("can't unmarshal to unsettable '%v' - use "+
|
||||
"& operator", vv.Type().String())
|
||||
err := unmarshalError("Unmarshal", ErrNotSettable, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return d.decode(vv)
|
||||
}
|
||||
|
||||
// NewDecoder returns a Decoder that can be used to manually decode XDR data
|
||||
// from a provided reader. Typically, Unmarshal should be used instead of
|
||||
// manually creating a Decoder.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
// NewDecoderLimited is identical to NewDecoder but it sets maxReadSize in
|
||||
// order to cap reads.
|
||||
func NewDecoderLimited(r io.Reader, maxSize uint) *Decoder {
|
||||
return &Decoder{r: r, maxReadSize: maxSize}
|
||||
}
|
||||
|
||||
// NewDecoderCustomTypes returns a decoder with support for custom types known
|
||||
// to the caller. The second parameter is a map of the type name to the decoder
|
||||
// routine. When the decoder finds a type matching one of the entries in the map
|
||||
// it will call the custom routine for that type.
|
||||
func NewDecoderCustomTypes(r io.Reader, maxSize uint, ct map[string]TypeDecoder) *Decoder {
|
||||
return &Decoder{r: r, maxReadSize: maxSize, customTypes: ct}
|
||||
}
|
171
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/doc.go
generated
vendored
Normal file
171
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package xdr implements the data representation portion of the External Data
|
||||
Representation (XDR) standard protocol as specified in RFC 4506 (obsoletes
|
||||
RFC 1832 and RFC 1014).
|
||||
|
||||
The XDR RFC defines both a data specification language and a data
|
||||
representation standard. This package implements methods to encode and decode
|
||||
XDR data per the data representation standard with the exception of 128-bit
|
||||
quadruple-precision floating points. It does not currently implement parsing of
|
||||
the data specification language. In other words, the ability to automatically
|
||||
generate Go code by parsing an XDR data specification file (typically .x
|
||||
extension) is not supported. In practice, this limitation of the package is
|
||||
fairly minor since it is largely unnecessary due to the reflection capabilities
|
||||
of Go as described below.
|
||||
|
||||
This package provides two approaches for encoding and decoding XDR data:
|
||||
|
||||
1) Marshal/Unmarshal functions which automatically map between XDR and Go types
|
||||
2) Individual Encoder/Decoder objects to manually work with XDR primitives
|
||||
|
||||
For the Marshal/Unmarshal functions, Go reflection capabilities are used to
|
||||
choose the type of the underlying XDR data based upon the Go type to encode or
|
||||
the target Go type to decode into. A description of how each type is mapped is
|
||||
provided below, however one important type worth reviewing is Go structs. In
|
||||
the case of structs, each exported field (first letter capitalized) is reflected
|
||||
and mapped in order. As a result, this means a Go struct with exported fields
|
||||
of the appropriate types listed in the expected order can be used to
|
||||
automatically encode / decode the XDR data thereby eliminating the need to write
|
||||
a lot of boilerplate code to encode/decode and error check each piece of XDR
|
||||
data as is typically required with C based XDR libraries.
|
||||
|
||||
Go Type to XDR Type Mappings
|
||||
|
||||
The following chart shows an overview of how Go types are mapped to XDR types
|
||||
for automatic marshalling and unmarshalling. The documentation for the Marshal
|
||||
and Unmarshal functions has specific details of how the mapping proceeds.
|
||||
|
||||
Go Type <-> XDR Type
|
||||
--------------------
|
||||
int8, int16, int32, int <-> XDR Integer
|
||||
uint8, uint16, uint32, uint <-> XDR Unsigned Integer
|
||||
int64 <-> XDR Hyper Integer
|
||||
uint64 <-> XDR Unsigned Hyper Integer
|
||||
bool <-> XDR Boolean
|
||||
float32 <-> XDR Floating-Point
|
||||
float64 <-> XDR Double-Precision Floating-Point
|
||||
string <-> XDR String
|
||||
byte <-> XDR Integer
|
||||
[]byte <-> XDR Variable-Length Opaque Data
|
||||
[#]byte <-> XDR Fixed-Length Opaque Data
|
||||
[]<type> <-> XDR Variable-Length Array
|
||||
[#]<type> <-> XDR Fixed-Length Array
|
||||
struct <-> XDR Structure
|
||||
map <-> XDR Variable-Length Array of two-element XDR Structures
|
||||
time.Time <-> XDR String encoded with RFC3339 nanosecond precision
|
||||
|
||||
Notes and Limitations:
|
||||
|
||||
* Automatic marshalling and unmarshalling of variable and fixed-length
|
||||
arrays of uint8s require a special struct tag `xdropaque:"false"`
|
||||
since byte slices and byte arrays are assumed to be opaque data and
|
||||
byte is a Go alias for uint8 thus indistinguishable under reflection
|
||||
* Channel, complex, and function types cannot be encoded
|
||||
* Interfaces without a concrete value cannot be encoded
|
||||
* Cyclic data structures are not supported and will result in infinite
|
||||
loops
|
||||
* Strings are marshalled and unmarshalled with UTF-8 character encoding
|
||||
which differs from the XDR specification of ASCII, however UTF-8 is
|
||||
backwards compatible with ASCII so this should rarely cause issues
|
||||
|
||||
|
||||
Encoding
|
||||
|
||||
To encode XDR data, use the Marshal function.
|
||||
func Marshal(w io.Writer, v interface{}) (int, error)
|
||||
|
||||
For example, given the following code snippet:
|
||||
|
||||
type ImageHeader struct {
|
||||
Signature [3]byte
|
||||
Version uint32
|
||||
IsGrayscale bool
|
||||
NumSections uint32
|
||||
}
|
||||
h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10}
|
||||
|
||||
var w bytes.Buffer
|
||||
bytesWritten, err := xdr.Marshal(&w, &h)
|
||||
// Error check elided
|
||||
|
||||
The result, encodedData, will then contain the following XDR encoded byte
|
||||
sequence:
|
||||
|
||||
0xAB, 0xCD, 0xEF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x0A
|
||||
|
||||
|
||||
In addition, while the automatic marshalling discussed above will work for the
|
||||
vast majority of cases, an Encoder object is provided that can be used to
|
||||
manually encode XDR primitives for complex scenarios where automatic
|
||||
reflection-based encoding won't work. The included examples provide a sample of
|
||||
manual usage via an Encoder.
|
||||
|
||||
|
||||
Decoding
|
||||
|
||||
To decode XDR data, use the Unmarshal function.
|
||||
func Unmarshal(r io.Reader, v interface{}) (int, error)
|
||||
|
||||
For example, given the following code snippet:
|
||||
|
||||
type ImageHeader struct {
|
||||
Signature [3]byte
|
||||
Version uint32
|
||||
IsGrayscale bool
|
||||
NumSections uint32
|
||||
}
|
||||
|
||||
// Using output from the Encoding section above.
|
||||
encodedData := []byte{
|
||||
0xAB, 0xCD, 0xEF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x0A,
|
||||
}
|
||||
|
||||
var h ImageHeader
|
||||
bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h)
|
||||
// Error check elided
|
||||
|
||||
The struct instance, h, will then contain the following values:
|
||||
|
||||
h.Signature = [3]byte{0xAB, 0xCD, 0xEF}
|
||||
h.Version = 2
|
||||
h.IsGrayscale = true
|
||||
h.NumSections = 10
|
||||
|
||||
In addition, while the automatic unmarshalling discussed above will work for the
|
||||
vast majority of cases, a Decoder object is provided that can be used to
|
||||
manually decode XDR primitives for complex scenarios where automatic
|
||||
reflection-based decoding won't work. The included examples provide a sample of
|
||||
manual usage via a Decoder.
|
||||
|
||||
Errors
|
||||
|
||||
All errors are either of type UnmarshalError or MarshalError. Both provide
|
||||
human-readable output as well as an ErrorCode field which can be inspected by
|
||||
sophisticated callers if necessary.
|
||||
|
||||
See the documentation of UnmarshalError, MarshalError, and ErrorCode for further
|
||||
details.
|
||||
*/
|
||||
package xdr
|
669
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/encode.go
generated
vendored
Normal file
669
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/encode.go
generated
vendored
Normal file
|
@ -0,0 +1,669 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errIOEncode = "%s while encoding %d bytes"
|
||||
|
||||
/*
|
||||
Marshal writes the XDR encoding of v to writer w and returns the number of bytes
|
||||
written. It traverses v recursively and automatically indirects pointers
|
||||
through arbitrary depth to encode the actual value pointed to.
|
||||
|
||||
Marshal uses reflection to determine the type of the concrete value contained by
|
||||
v and performs a mapping of Go types to the underlying XDR types as follows:
|
||||
|
||||
Go Type -> XDR Type
|
||||
--------------------
|
||||
int8, int16, int32, int -> XDR Integer
|
||||
uint8, uint16, uint32, uint -> XDR Unsigned Integer
|
||||
int64 -> XDR Hyper Integer
|
||||
uint64 -> XDR Unsigned Hyper Integer
|
||||
bool -> XDR Boolean
|
||||
float32 -> XDR Floating-Point
|
||||
float64 -> XDR Double-Precision Floating-Point
|
||||
string -> XDR String
|
||||
byte -> XDR Integer
|
||||
[]byte -> XDR Variable-Length Opaque Data
|
||||
[#]byte -> XDR Fixed-Length Opaque Data
|
||||
[]<type> -> XDR Variable-Length Array
|
||||
[#]<type> -> XDR Fixed-Length Array
|
||||
struct -> XDR Structure
|
||||
map -> XDR Variable-Length Array of two-element XDR Structures
|
||||
time.Time -> XDR String encoded with RFC3339 nanosecond precision
|
||||
|
||||
Notes and Limitations:
|
||||
|
||||
* Automatic marshalling of variable and fixed-length arrays of uint8s
|
||||
requires a special struct tag `xdropaque:"false"` since byte slices and
|
||||
byte arrays are assumed to be opaque data and byte is a Go alias for uint8
|
||||
thus indistinguishable under reflection
|
||||
* Channel, complex, and function types cannot be encoded
|
||||
* Interfaces without a concrete value cannot be encoded
|
||||
* Cyclic data structures are not supported and will result in infinite loops
|
||||
* Strings are marshalled with UTF-8 character encoding which differs from
|
||||
the XDR specification of ASCII, however UTF-8 is backwards compatible with
|
||||
ASCII so this should rarely cause issues
|
||||
|
||||
If any issues are encountered during the marshalling process, a MarshalError is
|
||||
returned with a human readable description as well as an ErrorCode value for
|
||||
further inspection from sophisticated callers. Some potential issues are
|
||||
unsupported Go types, attempting to encode more opaque data than can be
|
||||
represented by a single opaque XDR entry, and exceeding max slice limitations.
|
||||
*/
|
||||
func Marshal(w io.Writer, v interface{}) (int, error) {
|
||||
enc := Encoder{w: w}
|
||||
return enc.Encode(v)
|
||||
}
|
||||
|
||||
// An Encoder wraps an io.Writer that will receive the XDR encoded byte stream.
|
||||
// See NewEncoder.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// EncodeInt writes the XDR encoded representation of the passed 32-bit signed
|
||||
// integer to the encapsulated writer and returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.1 - Integer
|
||||
// 32-bit big-endian signed integer in range [-2147483648, 2147483647]
|
||||
func (enc *Encoder) EncodeInt(v int32) (int, error) {
|
||||
var b [4]byte
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 4)
|
||||
err := marshalError("EncodeInt", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeUint writes the XDR encoded representation of the passed 32-bit
|
||||
// unsigned integer to the encapsulated writer and returns the number of bytes
|
||||
// written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.2 - Unsigned Integer
|
||||
// 32-bit big-endian unsigned integer in range [0, 4294967295]
|
||||
func (enc *Encoder) EncodeUint(v uint32) (int, error) {
|
||||
var b [4]byte
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 4)
|
||||
err := marshalError("EncodeUint", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeEnum treats the passed 32-bit signed integer as an enumeration value
|
||||
// and, if it is in the list of passed valid enumeration values, writes the XDR
|
||||
// encoded representation of it to the encapsulated writer. It returns the
|
||||
// number of bytes written.
|
||||
//
|
||||
// A MarshalError is returned if the enumeration value is not one of the
|
||||
// provided valid values or if writing the data fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.3 - Enumeration
|
||||
// Represented as an XDR encoded signed integer
|
||||
func (enc *Encoder) EncodeEnum(v int32, validEnums map[int32]bool) (int, error) {
|
||||
if !validEnums[v] {
|
||||
err := marshalError("EncodeEnum", ErrBadEnumValue,
|
||||
"invalid enum", v, nil)
|
||||
return 0, err
|
||||
}
|
||||
return enc.EncodeInt(v)
|
||||
}
|
||||
|
||||
// EncodeBool writes the XDR encoded representation of the passed boolean to the
|
||||
// encapsulated writer and returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.4 - Boolean
|
||||
// Represented as an XDR encoded enumeration where 0 is false and 1 is true
|
||||
func (enc *Encoder) EncodeBool(v bool) (int, error) {
|
||||
i := int32(0)
|
||||
if v == true {
|
||||
i = 1
|
||||
}
|
||||
return enc.EncodeInt(i)
|
||||
}
|
||||
|
||||
// EncodeHyper writes the XDR encoded representation of the passed 64-bit
|
||||
// signed integer to the encapsulated writer and returns the number of bytes
|
||||
// written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Hyper Integer
|
||||
// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807]
|
||||
func (enc *Encoder) EncodeHyper(v int64) (int, error) {
|
||||
var b [8]byte
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 8)
|
||||
err := marshalError("EncodeHyper", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeUhyper writes the XDR encoded representation of the passed 64-bit
|
||||
// unsigned integer to the encapsulated writer and returns the number of bytes
|
||||
// written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.5 - Unsigned Hyper Integer
|
||||
// 64-bit big-endian unsigned integer in range [0, 18446744073709551615]
|
||||
func (enc *Encoder) EncodeUhyper(v uint64) (int, error) {
|
||||
var b [8]byte
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
|
||||
n, err := enc.w.Write(b[:])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), 8)
|
||||
err := marshalError("EncodeUhyper", ErrIO, msg, b[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeFloat writes the XDR encoded representation of the passed 32-bit
|
||||
// (single-precision) floating point to the encapsulated writer and returns the
|
||||
// number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.6 - Floating Point
|
||||
// 32-bit single-precision IEEE 754 floating point
|
||||
func (enc *Encoder) EncodeFloat(v float32) (int, error) {
|
||||
ui := math.Float32bits(v)
|
||||
return enc.EncodeUint(ui)
|
||||
}
|
||||
|
||||
// EncodeDouble writes the XDR encoded representation of the passed 64-bit
|
||||
// (double-precision) floating point to the encapsulated writer and returns the
|
||||
// number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.7 - Double-Precision Floating Point
|
||||
// 64-bit double-precision IEEE 754 floating point
|
||||
func (enc *Encoder) EncodeDouble(v float64) (int, error) {
|
||||
ui := math.Float64bits(v)
|
||||
return enc.EncodeUhyper(ui)
|
||||
}
|
||||
|
||||
// RFC Section 4.8 - Quadruple-Precision Floating Point
|
||||
// 128-bit quadruple-precision floating point
|
||||
// Not Implemented
|
||||
|
||||
// EncodeFixedOpaque treats the passed byte slice as opaque data of a fixed
|
||||
// size and writes the XDR encoded representation of it to the encapsulated
|
||||
// writer. It returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.9 - Fixed-Length Opaque Data
|
||||
// Fixed-length uninterpreted data zero-padded to a multiple of four
|
||||
func (enc *Encoder) EncodeFixedOpaque(v []byte) (int, error) {
|
||||
l := len(v)
|
||||
pad := (4 - (l % 4)) % 4
|
||||
|
||||
// Write the actual bytes.
|
||||
n, err := enc.w.Write(v)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), len(v))
|
||||
err := marshalError("EncodeFixedOpaque", ErrIO, msg, v[:n], err)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write any padding if needed.
|
||||
if pad > 0 {
|
||||
b := make([]byte, pad)
|
||||
n2, err := enc.w.Write(b)
|
||||
n += n2
|
||||
if err != nil {
|
||||
written := make([]byte, l+n2)
|
||||
copy(written, v)
|
||||
copy(written[l:], b[:n2])
|
||||
msg := fmt.Sprintf(errIOEncode, err.Error(), l+pad)
|
||||
err := marshalError("EncodeFixedOpaque", ErrIO, msg,
|
||||
written, err)
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// EncodeOpaque treats the passed byte slice as opaque data of a variable
|
||||
// size and writes the XDR encoded representation of it to the encapsulated
|
||||
// writer. It returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.10 - Variable-Length Opaque Data
|
||||
// Unsigned integer length followed by fixed opaque data of that length
|
||||
func (enc *Encoder) EncodeOpaque(v []byte) (int, error) {
|
||||
// Length of opaque data.
|
||||
n, err := enc.EncodeUint(uint32(len(v)))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err := enc.EncodeFixedOpaque(v)
|
||||
n += n2
|
||||
return n, err
|
||||
}
|
||||
|
||||
// EncodeString writes the XDR encoded representation of the passed string
|
||||
// to the encapsulated writer and returns the number of bytes written.
|
||||
// Character encoding is assumed to be UTF-8 and therefore ASCII compatible. If
|
||||
// the underlying character encoding is not compatible with this assumption, the
|
||||
// data can instead be written as variable-length opaque data (EncodeOpaque) and
|
||||
// manually converted as needed.
|
||||
//
|
||||
// A MarshalError with an error code of ErrIO is returned if writing the data
|
||||
// fails.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.11 - String
|
||||
// Unsigned integer length followed by bytes zero-padded to a multiple of four
|
||||
func (enc *Encoder) EncodeString(v string) (int, error) {
|
||||
// Length of string.
|
||||
n, err := enc.EncodeUint(uint32(len(v)))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err := enc.EncodeFixedOpaque([]byte(v))
|
||||
n += n2
|
||||
return n, err
|
||||
}
|
||||
|
||||
// encodeFixedArray writes the XDR encoded representation of each element
|
||||
// in the passed array represented by the reflection value to the encapsulated
|
||||
// writer and returns the number of bytes written. The ignoreOpaque flag
|
||||
// controls whether or not uint8 (byte) elements should be encoded individually
|
||||
// or as a fixed sequence of opaque data.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.12 - Fixed-Length Array
|
||||
// Individually XDR encoded array elements
|
||||
func (enc *Encoder) encodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
// Treat [#]byte (byte is alias for uint8) as opaque data unless ignored.
|
||||
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// Create a slice of the underlying array for better efficiency
|
||||
// when possible. Can't create a slice of an unaddressable
|
||||
// value.
|
||||
if v.CanAddr() {
|
||||
return enc.EncodeFixedOpaque(v.Slice(0, v.Len()).Bytes())
|
||||
}
|
||||
|
||||
// When the underlying array isn't addressable fall back to
|
||||
// copying the array into a new slice. This is rather ugly, but
|
||||
// the inability to create a constant slice from an
|
||||
// unaddressable array is a limitation of Go.
|
||||
slice := make([]byte, v.Len(), v.Len())
|
||||
reflect.Copy(reflect.ValueOf(slice), v)
|
||||
return enc.EncodeFixedOpaque(slice)
|
||||
}
|
||||
|
||||
// Encode each array element.
|
||||
var n int
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
n2, err := enc.encode(v.Index(i))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// encodeArray writes an XDR encoded integer representing the number of
|
||||
// elements in the passed slice represented by the reflection value followed by
|
||||
// the XDR encoded representation of each element in slice to the encapsulated
|
||||
// writer and returns the number of bytes written. The ignoreOpaque flag
|
||||
// controls whether or not uint8 (byte) elements should be encoded individually
|
||||
// or as a variable sequence of opaque data.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the array elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.13 - Variable-Length Array
|
||||
// Unsigned integer length followed by individually XDR encoded array elements
|
||||
func (enc *Encoder) encodeArray(v reflect.Value, ignoreOpaque bool) (int, error) {
|
||||
numItems := uint32(v.Len())
|
||||
n, err := enc.EncodeUint(numItems)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err := enc.encodeFixedArray(v, ignoreOpaque)
|
||||
n += n2
|
||||
return n, err
|
||||
}
|
||||
|
||||
// encodeStruct writes an XDR encoded representation of each value in the
|
||||
// exported fields of the struct represented by the passed reflection value to
|
||||
// the encapsulated writer and returns the number of bytes written. Pointers
|
||||
// are automatically indirected through arbitrary depth to encode the actual
|
||||
// value pointed to.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the elements.
|
||||
//
|
||||
// Reference:
|
||||
// RFC Section 4.14 - Structure
|
||||
// XDR encoded elements in the order of their declaration in the struct
|
||||
func (enc *Encoder) encodeStruct(v reflect.Value) (int, error) {
|
||||
var n int
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// Skip unexported fields and indirect through pointers.
|
||||
vtf := vt.Field(i)
|
||||
if vtf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
vf := v.Field(i)
|
||||
vf = enc.indirect(vf)
|
||||
|
||||
// Handle non-opaque data to []uint8 and [#]uint8 based on struct tag.
|
||||
tag := vtf.Tag.Get("xdropaque")
|
||||
if tag == "false" {
|
||||
switch vf.Kind() {
|
||||
case reflect.Slice:
|
||||
n2, err := enc.encodeArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
|
||||
case reflect.Array:
|
||||
n2, err := enc.encodeFixedArray(vf, true)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Encode each struct field.
|
||||
n2, err := enc.encode(vf)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// RFC Section 4.15 - Discriminated Union
|
||||
// RFC Section 4.16 - Void
|
||||
// RFC Section 4.17 - Constant
|
||||
// RFC Section 4.18 - Typedef
|
||||
// RFC Section 4.19 - Optional data
|
||||
// RFC Sections 4.15 though 4.19 only apply to the data specification language
|
||||
// which is not implemented by this package. In the case of discriminated
|
||||
// unions, struct tags are used to perform a similar function.
|
||||
|
||||
// encodeMap treats the map represented by the passed reflection value as a
|
||||
// variable-length array of 2-element structures whose fields are of the same
|
||||
// type as the map keys and elements and writes its XDR encoded representation
|
||||
// to the encapsulated writer. It returns the number of bytes written.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the elements.
|
||||
func (enc *Encoder) encodeMap(v reflect.Value) (int, error) {
|
||||
// Number of elements.
|
||||
n, err := enc.EncodeUint(uint32(v.Len()))
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Encode each key and value according to their type.
|
||||
for _, key := range v.MapKeys() {
|
||||
n2, err := enc.encode(key)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n2, err = enc.encode(v.MapIndex(key))
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// encodeInterface examines the interface represented by the passed reflection
|
||||
// value to detect whether it is an interface that can be encoded if it is,
|
||||
// extracts the underlying value to pass back into the encode function for
|
||||
// encoding according to its type.
|
||||
//
|
||||
// A MarshalError is returned if any issues are encountered while encoding
|
||||
// the interface.
|
||||
func (enc *Encoder) encodeInterface(v reflect.Value) (int, error) {
|
||||
if v.IsNil() || !v.CanInterface() {
|
||||
msg := fmt.Sprintf("can't encode nil interface")
|
||||
err := marshalError("encodeInterface", ErrNilInterface, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Extract underlying value from the interface and indirect through pointers.
|
||||
ve := reflect.ValueOf(v.Interface())
|
||||
ve = enc.indirect(ve)
|
||||
return enc.encode(ve)
|
||||
}
|
||||
|
||||
// encode is the main workhorse for marshalling via reflection. It uses
|
||||
// the passed reflection value to choose the XDR primitives to encode into
|
||||
// the encapsulated writer and returns the number of bytes written. It is a
|
||||
// recursive function, so cyclic data structures are not supported and will
|
||||
// result in an infinite loop.
|
||||
func (enc *Encoder) encode(v reflect.Value) (int, error) {
|
||||
if !v.IsValid() {
|
||||
msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String())
|
||||
err := marshalError("encode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Indirect through pointers to get at the concrete value.
|
||||
ve := enc.indirect(v)
|
||||
|
||||
// Handle time.Time values by encoding them as an RFC3339 formatted
|
||||
// string with nanosecond precision. Check the type string before
|
||||
// doing a full blown conversion to interface and type assertion since
|
||||
// checking a string is much quicker.
|
||||
if ve.Type().String() == "time.Time" && ve.CanInterface() {
|
||||
viface := ve.Interface()
|
||||
if tv, ok := viface.(time.Time); ok {
|
||||
return enc.EncodeString(tv.Format(time.RFC3339Nano))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle native Go types.
|
||||
switch ve.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:
|
||||
return enc.EncodeInt(int32(ve.Int()))
|
||||
|
||||
case reflect.Int64:
|
||||
return enc.EncodeHyper(ve.Int())
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint:
|
||||
return enc.EncodeUint(uint32(ve.Uint()))
|
||||
|
||||
case reflect.Uint64:
|
||||
return enc.EncodeUhyper(ve.Uint())
|
||||
|
||||
case reflect.Bool:
|
||||
return enc.EncodeBool(ve.Bool())
|
||||
|
||||
case reflect.Float32:
|
||||
return enc.EncodeFloat(float32(ve.Float()))
|
||||
|
||||
case reflect.Float64:
|
||||
return enc.EncodeDouble(ve.Float())
|
||||
|
||||
case reflect.String:
|
||||
return enc.EncodeString(ve.String())
|
||||
|
||||
case reflect.Array:
|
||||
return enc.encodeFixedArray(ve, false)
|
||||
|
||||
case reflect.Slice:
|
||||
return enc.encodeArray(ve, false)
|
||||
|
||||
case reflect.Struct:
|
||||
return enc.encodeStruct(ve)
|
||||
|
||||
case reflect.Map:
|
||||
return enc.encodeMap(ve)
|
||||
|
||||
case reflect.Interface:
|
||||
return enc.encodeInterface(ve)
|
||||
}
|
||||
|
||||
// The only unhandled types left are unsupported. At the time of this
|
||||
// writing the only remaining unsupported types that exist are
|
||||
// reflect.Uintptr and reflect.UnsafePointer.
|
||||
msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String())
|
||||
err := marshalError("encode", ErrUnsupportedType, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// indirect dereferences pointers until it reaches a non-pointer. This allows
|
||||
// transparent encoding through arbitrary levels of indirection.
|
||||
func (enc *Encoder) indirect(v reflect.Value) reflect.Value {
|
||||
rv := v
|
||||
for rv.Kind() == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// Encode operates identically to the Marshal function with the exception of
|
||||
// using the writer associated with the Encoder for the destination of the
|
||||
// XDR-encoded data instead of a user-supplied writer. See the Marshal
|
||||
// documentation for specifics.
|
||||
func (enc *Encoder) Encode(v interface{}) (int, error) {
|
||||
if v == nil {
|
||||
msg := "can't marshal nil interface"
|
||||
err := marshalError("Marshal", ErrNilInterface, msg, nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
vv := reflect.ValueOf(v)
|
||||
vve := vv
|
||||
for vve.Kind() == reflect.Ptr {
|
||||
if vve.IsNil() {
|
||||
msg := fmt.Sprintf("can't marshal nil pointer '%v'",
|
||||
vv.Type().String())
|
||||
err := marshalError("Marshal", ErrBadArguments, msg,
|
||||
nil, nil)
|
||||
return 0, err
|
||||
}
|
||||
vve = vve.Elem()
|
||||
}
|
||||
|
||||
return enc.encode(vve)
|
||||
}
|
||||
|
||||
// NewEncoder returns an object that can be used to manually choose fields to
|
||||
// XDR encode to the passed writer w. Typically, Marshal should be used instead
|
||||
// of manually creating an Encoder. An Encoder, along with several of its
|
||||
// methods to encode XDR primitives, is exposed so it is possible to perform
|
||||
// manual encoding of data without relying on reflection should it be necessary
|
||||
// in complex scenarios where automatic reflection-based encoding won't work.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: w}
|
||||
}
|
177
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/error.go
generated
vendored
Normal file
177
vendor/github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2/error.go
generated
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package xdr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrorCode identifies a kind of error.
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
// ErrBadArguments indicates arguments passed to the function are not
|
||||
// what was expected.
|
||||
ErrBadArguments ErrorCode = iota
|
||||
|
||||
// ErrUnsupportedType indicates the Go type is not a supported type for
|
||||
// marshalling and unmarshalling XDR data.
|
||||
ErrUnsupportedType
|
||||
|
||||
// ErrBadEnumValue indicates an enumeration value is not in the list of
|
||||
// valid values.
|
||||
ErrBadEnumValue
|
||||
|
||||
// ErrNotSettable indicates an interface value cannot be written to.
|
||||
// This usually means the interface value was not passed with the &
|
||||
// operator, but it can also happen if automatic pointer allocation
|
||||
// fails.
|
||||
ErrNotSettable
|
||||
|
||||
// ErrOverflow indicates that the data in question is too large to fit
|
||||
// into the corresponding Go or XDR data type. For example, an integer
|
||||
// decoded from XDR that is too large to fit into a target type of int8,
|
||||
// or opaque data that exceeds the max length of a Go slice.
|
||||
ErrOverflow
|
||||
|
||||
// ErrNilInterface indicates an interface with no concrete type
|
||||
// information was encountered. Type information is necessary to
|
||||
// perform mapping between XDR and Go types.
|
||||
ErrNilInterface
|
||||
|
||||
// ErrIO indicates an error was encountered while reading or writing to
|
||||
// an io.Reader or io.Writer, respectively. The actual underlying error
|
||||
// will be available via the Err field of the MarshalError or
|
||||
// UnmarshalError struct.
|
||||
ErrIO
|
||||
|
||||
// ErrParseTime indicates an error was encountered while parsing an
|
||||
// RFC3339 formatted time value. The actual underlying error will be
|
||||
// available via the Err field of the UnmarshalError struct.
|
||||
ErrParseTime
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrBadArguments: "ErrBadArguments",
|
||||
ErrUnsupportedType: "ErrUnsupportedType",
|
||||
ErrBadEnumValue: "ErrBadEnumValue",
|
||||
ErrNotSettable: "ErrNotSettable",
|
||||
ErrOverflow: "ErrOverflow",
|
||||
ErrNilInterface: "ErrNilInterface",
|
||||
ErrIO: "ErrIO",
|
||||
ErrParseTime: "ErrParseTime",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
func (e ErrorCode) String() string {
|
||||
if s := errorCodeStrings[e]; s != "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown ErrorCode (%d)", e)
|
||||
}
|
||||
|
||||
// UnmarshalError describes a problem encountered while unmarshaling data.
|
||||
// Some potential issues are unsupported Go types, attempting to decode a value
|
||||
// which is too large to fit into a specified Go type, and exceeding max slice
|
||||
// limitations.
|
||||
type UnmarshalError struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Func string // Function name
|
||||
Value interface{} // Value actually parsed where appropriate
|
||||
Description string // Human readable description of the issue
|
||||
Err error // The underlying error for IO errors
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e *UnmarshalError) Error() string {
|
||||
switch e.ErrorCode {
|
||||
case ErrBadEnumValue, ErrOverflow, ErrIO, ErrParseTime:
|
||||
return fmt.Sprintf("xdr:%s: %s - read: '%v'", e.Func,
|
||||
e.Description, e.Value)
|
||||
}
|
||||
return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description)
|
||||
}
|
||||
|
||||
// unmarshalError creates an error given a set of arguments and will copy byte
|
||||
// slices into the Value field since they might otherwise be changed from from
|
||||
// the original value.
|
||||
func unmarshalError(f string, c ErrorCode, desc string, v interface{}, err error) *UnmarshalError {
|
||||
e := &UnmarshalError{ErrorCode: c, Func: f, Description: desc, Err: err}
|
||||
switch t := v.(type) {
|
||||
case []byte:
|
||||
slice := make([]byte, len(t))
|
||||
copy(slice, t)
|
||||
e.Value = slice
|
||||
default:
|
||||
e.Value = v
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// IsIO returns a boolean indicating whether the error is known to report that
|
||||
// the underlying reader or writer encountered an ErrIO.
|
||||
func IsIO(err error) bool {
|
||||
switch e := err.(type) {
|
||||
case *UnmarshalError:
|
||||
return e.ErrorCode == ErrIO
|
||||
case *MarshalError:
|
||||
return e.ErrorCode == ErrIO
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalError describes a problem encountered while marshaling data.
|
||||
// Some potential issues are unsupported Go types, attempting to encode more
|
||||
// opaque data than can be represented by a single opaque XDR entry, and
|
||||
// exceeding max slice limitations.
|
||||
type MarshalError struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Func string // Function name
|
||||
Value interface{} // Value actually parsed where appropriate
|
||||
Description string // Human readable description of the issue
|
||||
Err error // The underlying error for IO errors
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e *MarshalError) Error() string {
|
||||
switch e.ErrorCode {
|
||||
case ErrIO:
|
||||
return fmt.Sprintf("xdr:%s: %s - wrote: '%v'", e.Func,
|
||||
e.Description, e.Value)
|
||||
case ErrBadEnumValue:
|
||||
return fmt.Sprintf("xdr:%s: %s - value: '%v'", e.Func,
|
||||
e.Description, e.Value)
|
||||
}
|
||||
return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description)
|
||||
}
|
||||
|
||||
// marshalError creates an error given a set of arguments and will copy byte
|
||||
// slices into the Value field since they might otherwise be changed from from
|
||||
// the original value.
|
||||
func marshalError(f string, c ErrorCode, desc string, v interface{}, err error) *MarshalError {
|
||||
e := &MarshalError{ErrorCode: c, Func: f, Description: desc, Err: err}
|
||||
switch t := v.(type) {
|
||||
case []byte:
|
||||
slice := make([]byte, len(t))
|
||||
copy(slice, t)
|
||||
e.Value = slice
|
||||
default:
|
||||
e.Value = v
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
|
@ -0,0 +1,608 @@
|
|||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package libvirt is a pure Go implementation of the libvirt RPC protocol.
|
||||
// For more information on the protocol, see https://libvirt.org/internals/l.html
|
||||
package libvirt
|
||||
|
||||
// We'll use c-for-go to extract the consts and typedefs from the libvirt
|
||||
// sources so we don't have to duplicate them here.
|
||||
//go:generate scripts/gen-consts.sh
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
"github.com/digitalocean/go-libvirt/internal/event"
|
||||
xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
|
||||
)
|
||||
|
||||
// ErrEventsNotSupported is returned by Events() if event streams
|
||||
// are unsupported by either QEMU or libvirt.
|
||||
var ErrEventsNotSupported = errors.New("event monitor is not supported")
|
||||
|
||||
// Libvirt implements libvirt's remote procedure call protocol.
|
||||
type Libvirt struct {
|
||||
conn net.Conn
|
||||
r *bufio.Reader
|
||||
w *bufio.Writer
|
||||
mu *sync.Mutex
|
||||
|
||||
// method callbacks
|
||||
cmux sync.RWMutex
|
||||
callbacks map[int32]chan response
|
||||
|
||||
// event listeners
|
||||
emux sync.RWMutex
|
||||
events map[int32]*event.Stream
|
||||
|
||||
// next request serial number
|
||||
s int32
|
||||
}
|
||||
|
||||
// DomainEvent represents a libvirt domain event.
|
||||
type DomainEvent struct {
|
||||
CallbackID int32
|
||||
Domain Domain
|
||||
Event string
|
||||
Seconds uint64
|
||||
Microseconds uint32
|
||||
Padding uint8
|
||||
Details []byte
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID of a QEMU domain event.
|
||||
func (de DomainEvent) GetCallbackID() int32 {
|
||||
return de.CallbackID
|
||||
}
|
||||
|
||||
// GetCallbackID returns the callback ID of a libvirt lifecycle event.
|
||||
func (m DomainEventCallbackLifecycleMsg) GetCallbackID() int32 {
|
||||
return m.CallbackID
|
||||
}
|
||||
|
||||
// qemuError represents a QEMU process error.
|
||||
type qemuError struct {
|
||||
Error struct {
|
||||
Class string `json:"class"`
|
||||
Description string `json:"desc"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
// Capabilities returns an XML document describing the host's capabilties.
|
||||
func (l *Libvirt) Capabilities() ([]byte, error) {
|
||||
caps, err := l.ConnectGetCapabilities()
|
||||
return []byte(caps), err
|
||||
}
|
||||
|
||||
// Connect establishes communication with the libvirt server.
|
||||
// The underlying libvirt socket connection must be previously established.
|
||||
func (l *Libvirt) Connect() error {
|
||||
payload := struct {
|
||||
Padding [3]byte
|
||||
Name string
|
||||
Flags uint32
|
||||
}{
|
||||
Padding: [3]byte{0x1, 0x0, 0x0},
|
||||
Name: "qemu:///system",
|
||||
Flags: 0,
|
||||
}
|
||||
|
||||
buf, err := encode(&payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// libvirt requires that we call auth-list prior to connecting,
|
||||
// event when no authentication is used.
|
||||
_, err = l.request(constants.ProcAuthList, constants.Program, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = l.request(constants.ProcConnectOpen, constants.Program, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect shuts down communication with the libvirt server and closes the
|
||||
// underlying net.Conn.
|
||||
func (l *Libvirt) Disconnect() error {
|
||||
// close event streams
|
||||
for _, ev := range l.events {
|
||||
l.unsubscribeEvents(ev)
|
||||
}
|
||||
|
||||
// Deregister all callbacks to prevent blocking on clients with
|
||||
// outstanding requests
|
||||
l.deregisterAll()
|
||||
|
||||
_, err := l.request(constants.ProcConnectClose, constants.Program, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.conn.Close()
|
||||
}
|
||||
|
||||
// Domains returns a list of all domains managed by libvirt.
|
||||
//
|
||||
// Deprecated: use ConnectListAllDomains instead.
|
||||
func (l *Libvirt) Domains() ([]Domain, error) {
|
||||
// these are the flags as passed by `virsh list --all`
|
||||
flags := ConnectListDomainsActive | ConnectListDomainsInactive
|
||||
domains, _, err := l.ConnectListAllDomains(1, flags)
|
||||
return domains, err
|
||||
}
|
||||
|
||||
// DomainState returns state of the domain managed by libvirt.
|
||||
//
|
||||
// Deprecated: use DomainGetState instead.
|
||||
func (l *Libvirt) DomainState(dom string) (DomainState, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return DomainNostate, err
|
||||
}
|
||||
|
||||
state, _, err := l.DomainGetState(d, 0)
|
||||
return DomainState(state), err
|
||||
}
|
||||
|
||||
// SubscribeQEMUEvents streams domain events until the provided context is
|
||||
// cancelled. If a problem is encountered setting up the event monitor
|
||||
// connection an error will be returned. Errors encountered during streaming
|
||||
// will cause the returned event channel to be closed. QEMU domain events.
|
||||
func (l *Libvirt) SubscribeQEMUEvents(ctx context.Context, dom string) (<-chan DomainEvent, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callbackID, err := l.QEMUConnectDomainMonitorEventRegister([]Domain{d}, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := event.NewStream(constants.QEMUProgram, callbackID)
|
||||
l.addStream(stream)
|
||||
ch := make(chan DomainEvent)
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
defer l.unsubscribeQEMUEvents(stream)
|
||||
defer stream.Shutdown()
|
||||
defer func() { close(ch) }()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, ok := <-stream.Recv():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- *ev.(*DomainEvent)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// unsubscribeQEMUEvents stops the flow of events from QEMU through libvirt.
|
||||
func (l *Libvirt) unsubscribeQEMUEvents(stream *event.Stream) error {
|
||||
err := l.QEMUConnectDomainMonitorEventDeregister(stream.CallbackID)
|
||||
l.removeStream(stream.CallbackID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SubscribeEvents allows the caller to subscribe to any of the event types
|
||||
// supported by libvirt. The events will continue to be streamed until the
|
||||
// caller cancels the provided context. After canceling the context, callers
|
||||
// should wait until the channel is closed to be sure they're collected all the
|
||||
// events.
|
||||
func (l *Libvirt) SubscribeEvents(ctx context.Context, eventID DomainEventID,
|
||||
dom OptDomain) (<-chan interface{}, error) {
|
||||
|
||||
callbackID, err := l.ConnectDomainEventCallbackRegisterAny(int32(eventID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := event.NewStream(constants.QEMUProgram, callbackID)
|
||||
l.addStream(stream)
|
||||
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
defer l.unsubscribeEvents(stream)
|
||||
defer stream.Shutdown()
|
||||
defer func() { close(ch) }()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, ok := <-stream.Recv():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- ev
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// unsubscribeEvents stops the flow of the specified events from libvirt. There
|
||||
// are two steps to this process: a call to libvirt to deregister our callback,
|
||||
// and then removing the callback from the list used by the `route` fucntion. If
|
||||
// the deregister call fails, we'll return the error, but still remove the
|
||||
// callback from the list. That's ok; if any events arrive after this point, the
|
||||
// route function will drop them when it finds no registered handler.
|
||||
func (l *Libvirt) unsubscribeEvents(stream *event.Stream) error {
|
||||
err := l.ConnectDomainEventCallbackDeregisterAny(stream.CallbackID)
|
||||
l.removeStream(stream.CallbackID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// LifecycleEvents streams lifecycle events until the provided context is
|
||||
// cancelled. If a problem is encountered setting up the event monitor
|
||||
// connection, an error will be returned. Errors encountered during streaming
|
||||
// will cause the returned event channel to be closed.
|
||||
func (l *Libvirt) LifecycleEvents(ctx context.Context) (<-chan DomainEventLifecycleMsg, error) {
|
||||
callbackID, err := l.ConnectDomainEventCallbackRegisterAny(int32(DomainEventIDLifecycle), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := event.NewStream(constants.Program, callbackID)
|
||||
l.addStream(stream)
|
||||
|
||||
ch := make(chan DomainEventLifecycleMsg)
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
defer l.unsubscribeEvents(stream)
|
||||
defer stream.Shutdown()
|
||||
defer func() { close(ch) }()
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev, ok := <-stream.Recv():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- ev.(*DomainEventCallbackLifecycleMsg).Msg
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// Run executes the given QAPI command against a domain's QEMU instance.
|
||||
// For a list of available QAPI commands, see:
|
||||
// http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD
|
||||
func (l *Libvirt) Run(dom string, cmd []byte) ([]byte, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := struct {
|
||||
Domain Domain
|
||||
Command []byte
|
||||
Flags uint32
|
||||
}{
|
||||
Domain: d,
|
||||
Command: cmd,
|
||||
Flags: 0,
|
||||
}
|
||||
|
||||
buf, err := encode(&payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := l.request(constants.QEMUProcDomainMonitorCommand, constants.QEMUProgram, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for QEMU process errors
|
||||
if err = getQEMUError(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := bytes.NewReader(res.Payload)
|
||||
dec := xdr.NewDecoder(r)
|
||||
data, _, err := dec.DecodeFixedOpaque(int32(r.Len()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// drop QMP control characters from start of line, and drop
|
||||
// any trailing NULL characters from the end
|
||||
return bytes.TrimRight(data[4:], "\x00"), nil
|
||||
}
|
||||
|
||||
// Secrets returns all secrets managed by the libvirt daemon.
|
||||
//
|
||||
// Deprecated: use ConnectListAllSecrets instead.
|
||||
func (l *Libvirt) Secrets() ([]Secret, error) {
|
||||
secrets, _, err := l.ConnectListAllSecrets(1, 0)
|
||||
return secrets, err
|
||||
}
|
||||
|
||||
// StoragePool returns the storage pool associated with the provided name.
|
||||
// An error is returned if the requested storage pool is not found.
|
||||
//
|
||||
// Deprecated: use StoragePoolLookupByName instead.
|
||||
func (l *Libvirt) StoragePool(name string) (StoragePool, error) {
|
||||
return l.StoragePoolLookupByName(name)
|
||||
}
|
||||
|
||||
// StoragePools returns a list of defined storage pools. Pools are filtered by
|
||||
// the provided flags. See StoragePools*.
|
||||
//
|
||||
// Deprecated: use ConnectListAllStoragePools instead.
|
||||
func (l *Libvirt) StoragePools(flags ConnectListAllStoragePoolsFlags) ([]StoragePool, error) {
|
||||
pools, _, err := l.ConnectListAllStoragePools(1, flags)
|
||||
return pools, err
|
||||
}
|
||||
|
||||
// Undefine undefines the domain specified by dom, e.g., 'prod-lb-01'.
|
||||
// The flags argument allows additional options to be specified such as
|
||||
// cleaning up snapshot metadata. For more information on available
|
||||
// flags, see DomainUndefine*.
|
||||
//
|
||||
// Deprecated: use DomainUndefineFlags instead.
|
||||
func (l *Libvirt) Undefine(dom string, flags DomainUndefineFlagsValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainUndefineFlags(d, flags)
|
||||
}
|
||||
|
||||
// Destroy destroys the domain specified by dom, e.g., 'prod-lb-01'.
|
||||
// The flags argument allows additional options to be specified such as
|
||||
// allowing a graceful shutdown with SIGTERM than SIGKILL.
|
||||
// For more information on available flags, see DomainDestroy*.
|
||||
//
|
||||
// Deprecated: use DomainDestroyFlags instead.
|
||||
func (l *Libvirt) Destroy(dom string, flags DomainDestroyFlagsValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainDestroyFlags(d, flags)
|
||||
}
|
||||
|
||||
// XML returns a domain's raw XML definition, akin to `virsh dumpxml <domain>`.
|
||||
// See DomainXMLFlag* for optional flags.
|
||||
//
|
||||
// Deprecated: use DomainGetXMLDesc instead.
|
||||
func (l *Libvirt) XML(dom string, flags DomainXMLFlags) ([]byte, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xml, err := l.DomainGetXMLDesc(d, flags)
|
||||
return []byte(xml), err
|
||||
}
|
||||
|
||||
// DefineXML defines a domain, but does not start it.
|
||||
//
|
||||
// Deprecated: use DomainDefineXMLFlags instead.
|
||||
func (l *Libvirt) DefineXML(x []byte, flags DomainDefineFlags) error {
|
||||
_, err := l.DomainDefineXMLFlags(string(x), flags)
|
||||
return err
|
||||
}
|
||||
|
||||
// Version returns the version of the libvirt daemon.
|
||||
//
|
||||
// Deprecated: use ConnectGetLibVersion instead.
|
||||
func (l *Libvirt) Version() (string, error) {
|
||||
ver, err := l.ConnectGetLibVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The version is provided as an int following this formula:
|
||||
// version * 1,000,000 + minor * 1000 + micro
|
||||
// See src/libvirt-host.c # virConnectGetLibVersion
|
||||
major := ver / 1000000
|
||||
ver %= 1000000
|
||||
minor := ver / 1000
|
||||
ver %= 1000
|
||||
micro := ver
|
||||
|
||||
versionString := fmt.Sprintf("%d.%d.%d", major, minor, micro)
|
||||
return versionString, nil
|
||||
}
|
||||
|
||||
// Shutdown shuts down a domain. Note that the guest OS may ignore the request.
|
||||
// If flags is set to 0 then the hypervisor will choose the method of shutdown it considers best.
|
||||
//
|
||||
// Deprecated: use DomainShutdownFlags instead.
|
||||
func (l *Libvirt) Shutdown(dom string, flags DomainShutdownFlagValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainShutdownFlags(d, flags)
|
||||
}
|
||||
|
||||
// Reboot reboots the domain. Note that the guest OS may ignore the request.
|
||||
// If flags is set to zero, then the hypervisor will choose the method of shutdown it considers best.
|
||||
//
|
||||
// Deprecated: use DomainReboot instead.
|
||||
func (l *Libvirt) Reboot(dom string, flags DomainRebootFlagValues) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainReboot(d, flags)
|
||||
}
|
||||
|
||||
// Reset resets domain immediately without any guest OS shutdown
|
||||
//
|
||||
// Deprecated: use DomainReset instead.
|
||||
func (l *Libvirt) Reset(dom string) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.DomainReset(d, 0)
|
||||
}
|
||||
|
||||
// BlockLimit contains a name and value pair for a Get/SetBlockIOTune limit. The
|
||||
// Name field is the name of the limit (to see a list of the limits that can be
|
||||
// applied, execute the 'blkdeviotune' command on a VM in virsh). Callers can
|
||||
// use the QEMUBlockIO... constants below for the Name value. The Value field is
|
||||
// the limit to apply.
|
||||
type BlockLimit struct {
|
||||
Name string
|
||||
Value uint64
|
||||
}
|
||||
|
||||
// SetBlockIOTune changes the per-device block I/O tunables within a guest.
|
||||
// Parameters are the name of the VM, the name of the disk device to which the
|
||||
// limits should be applied, and 1 or more BlockLimit structs containing the
|
||||
// actual limits.
|
||||
//
|
||||
// The limits which can be applied here are enumerated in the QEMUBlockIO...
|
||||
// constants above, and you can also see the full list by executing the
|
||||
// 'blkdeviotune' command on a VM in virsh.
|
||||
//
|
||||
// Example usage:
|
||||
// SetBlockIOTune("vm-name", "vda", BlockLimit{libvirt.QEMUBlockIOWriteBytesSec, 1000000})
|
||||
//
|
||||
// Deprecated: use DomainSetBlockIOTune instead.
|
||||
func (l *Libvirt) SetBlockIOTune(dom string, disk string, limits ...BlockLimit) error {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := make([]TypedParam, len(limits))
|
||||
for ix, limit := range limits {
|
||||
tpval := NewTypedParamValueUllong(limit.Value)
|
||||
params[ix] = TypedParam{Field: limit.Name, Value: *tpval}
|
||||
}
|
||||
|
||||
return l.DomainSetBlockIOTune(d, disk, params, uint32(DomainAffectLive))
|
||||
}
|
||||
|
||||
// GetBlockIOTune returns a slice containing the current block I/O tunables for
|
||||
// a disk.
|
||||
//
|
||||
// Deprecated: use DomainGetBlockIOTune instead.
|
||||
func (l *Libvirt) GetBlockIOTune(dom string, disk string) ([]BlockLimit, error) {
|
||||
d, err := l.lookup(dom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lims, _, err := l.DomainGetBlockIOTune(d, []string{disk}, 32, uint32(TypedParamStringOkay))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var limits []BlockLimit
|
||||
|
||||
// now decode each of the returned TypedParams. To do this we read the field
|
||||
// name and type, then use the type information to decode the value.
|
||||
for _, lim := range lims {
|
||||
var l BlockLimit
|
||||
name := lim.Field
|
||||
switch lim.Value.I.(type) {
|
||||
case uint64:
|
||||
l = BlockLimit{Name: name, Value: lim.Value.I.(uint64)}
|
||||
}
|
||||
limits = append(limits, l)
|
||||
}
|
||||
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
// lookup returns a domain as seen by libvirt.
|
||||
func (l *Libvirt) lookup(name string) (Domain, error) {
|
||||
return l.DomainLookupByName(name)
|
||||
}
|
||||
|
||||
// getQEMUError checks the provided response for QEMU process errors.
|
||||
// If an error is found, it is extracted an returned, otherwise nil.
|
||||
func getQEMUError(r response) error {
|
||||
pl := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoder(pl)
|
||||
|
||||
s, _, err := dec.DecodeString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var e qemuError
|
||||
if err = json.Unmarshal([]byte(s), &e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.Error.Description != "" {
|
||||
return errors.New(e.Error.Description)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// New configures a new Libvirt RPC connection.
|
||||
func New(conn net.Conn) *Libvirt {
|
||||
l := &Libvirt{
|
||||
conn: conn,
|
||||
s: 0,
|
||||
r: bufio.NewReader(conn),
|
||||
w: bufio.NewWriter(conn),
|
||||
mu: &sync.Mutex{},
|
||||
callbacks: make(map[int32]chan response),
|
||||
events: make(map[int32]*event.Stream),
|
||||
}
|
||||
|
||||
go l.listen()
|
||||
|
||||
return l
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
# Configuration file for c-for-go, which go-libvirt uses to translate the const
|
||||
# and type definitions from the C-language sources in the libvirt project into
|
||||
# Go. This file is used by the c-for-go binary (github.com/xlab/c-for-go), which
|
||||
# is called when 'go generate' is run. See libvirt.go for the command line used.
|
||||
---
|
||||
GENERATOR:
|
||||
PackageName: libvirt
|
||||
PackageLicense: |
|
||||
Copyright 2018 The go-libvirt Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Includes: []
|
||||
|
||||
PARSER:
|
||||
# We can't use environment variables here, but we don't want to process the
|
||||
# libvirt version installed in the system folders (if any). Instead we'll
|
||||
# rely on our caller to link the libvirt source directory to lv_source/, and
|
||||
# run on that code. This isn't ideal, but changes to c-for-go are needed to
|
||||
# fix it.
|
||||
IncludePaths: [./lv_source/include]
|
||||
SourcesPaths:
|
||||
- libvirt/libvirt.h
|
||||
- libvirt/virterror.h
|
||||
|
||||
TRANSLATOR:
|
||||
ConstRules:
|
||||
defines: eval
|
||||
Rules:
|
||||
global:
|
||||
- {action: accept, from: "^vir"}
|
||||
post-global:
|
||||
- {action: replace, from: "^vir"}
|
||||
- {load: snakecase}
|
||||
# Follow golint's capitalization conventions.
|
||||
- {action: replace, from: "Api([A-Z]|$)", to: "API$1"}
|
||||
- {action: replace, from: "Cpu([A-Z]|$)", to: "CPU$1"}
|
||||
- {action: replace, from: "Dns([A-Z]|$)", to: "DNS$1"}
|
||||
- {action: replace, from: "Eof([A-Z]|$)", to: "EOF$1"}
|
||||
- {action: replace, from: "Id([A-Z]|$)", to: "ID$1"}
|
||||
- {action: replace, from: "Ip([A-Z]|$)", to: "IP$1"}
|
||||
- {action: replace, from: "Tls([A-Z]|$)", to: "TLS$1"}
|
||||
- {action: replace, from: "Uuid([A-Z]|$)", to: "UUID$1"}
|
||||
- {action: replace, from: "Uri([A-Z]|$)", to: "URI$1"}
|
||||
- {action: replace, from: "Vcpu([A-Z]|$)", to: "VCPU$1"}
|
||||
- {action: replace, from: "Xml([A-Z]|$)", to: "XML$1"}
|
||||
- {action: replace, from: "Rpc([A-Z]|$)", to: "RPC$1"}
|
||||
- {action: replace, from: "Ssh([A-Z]|$)", to: "SSH$1"}
|
||||
- {action: replace, from: "Http([A-Z]|$)", to: "HTTP$1"}
|
||||
- {transform: unexport, from: "^(Err|From|War)"}
|
||||
const:
|
||||
- {action: accept, from: "^VIR_"}
|
||||
# Special case to prevent a collision with a type:
|
||||
- {action: replace, from: "^VIR_DOMAIN_JOB_OPERATION", to: "VIR_DOMAIN_JOB_OPERATION_STR"}
|
||||
- {transform: lower}
|
|
@ -0,0 +1,7 @@
|
|||
# libvirtd configuration for travis-ci
|
||||
listen_tls = 0
|
||||
listen_tcp = 1
|
||||
tcp_port = "16509"
|
||||
listen_addr = "127.0.0.1"
|
||||
auth_unix_rw = "none"
|
||||
auth_tcp = "none"
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//
|
||||
// Code generated by internal/lvgen/generate.go. DO NOT EDIT.
|
||||
//
|
||||
// To regenerate, run 'go generate' in internal/lvgen.
|
||||
//
|
||||
|
||||
package libvirt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
"github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
|
||||
)
|
||||
|
||||
// References to prevent "imported and not used" errors.
|
||||
var (
|
||||
_ = bytes.Buffer{}
|
||||
_ = io.Copy
|
||||
_ = constants.Program
|
||||
_ = xdr.Unmarshal
|
||||
)
|
||||
|
||||
//
|
||||
// Typedefs:
|
||||
//
|
||||
|
||||
//
|
||||
// Enums:
|
||||
//
|
||||
// QEMUProcedure is libvirt's qemu_procedure
|
||||
type QEMUProcedure int32
|
||||
|
||||
//
|
||||
// Structs:
|
||||
//
|
||||
// QEMUDomainMonitorCommandArgs is libvirt's qemu_domain_monitor_command_args
|
||||
type QEMUDomainMonitorCommandArgs struct {
|
||||
Dom Domain
|
||||
Cmd string
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUDomainMonitorCommandRet is libvirt's qemu_domain_monitor_command_ret
|
||||
type QEMUDomainMonitorCommandRet struct {
|
||||
Result string
|
||||
}
|
||||
|
||||
// QEMUDomainAttachArgs is libvirt's qemu_domain_attach_args
|
||||
type QEMUDomainAttachArgs struct {
|
||||
PidValue uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUDomainAttachRet is libvirt's qemu_domain_attach_ret
|
||||
type QEMUDomainAttachRet struct {
|
||||
Dom Domain
|
||||
}
|
||||
|
||||
// QEMUDomainAgentCommandArgs is libvirt's qemu_domain_agent_command_args
|
||||
type QEMUDomainAgentCommandArgs struct {
|
||||
Dom Domain
|
||||
Cmd string
|
||||
Timeout int32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUDomainAgentCommandRet is libvirt's qemu_domain_agent_command_ret
|
||||
type QEMUDomainAgentCommandRet struct {
|
||||
Result OptString
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventRegisterArgs is libvirt's qemu_connect_domain_monitor_event_register_args
|
||||
type QEMUConnectDomainMonitorEventRegisterArgs struct {
|
||||
Dom OptDomain
|
||||
Event OptString
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventRegisterRet is libvirt's qemu_connect_domain_monitor_event_register_ret
|
||||
type QEMUConnectDomainMonitorEventRegisterRet struct {
|
||||
CallbackID int32
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventDeregisterArgs is libvirt's qemu_connect_domain_monitor_event_deregister_args
|
||||
type QEMUConnectDomainMonitorEventDeregisterArgs struct {
|
||||
CallbackID int32
|
||||
}
|
||||
|
||||
// QEMUDomainMonitorEventMsg is libvirt's qemu_domain_monitor_event_msg
|
||||
type QEMUDomainMonitorEventMsg struct {
|
||||
CallbackID int32
|
||||
Dom Domain
|
||||
Event string
|
||||
Seconds int64
|
||||
Micros uint32
|
||||
Details OptString
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// QEMUDomainMonitorCommand is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_COMMAND.
|
||||
func (l *Libvirt) QEMUDomainMonitorCommand(Dom Domain, Cmd string, Flags uint32) (rResult string, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUDomainMonitorCommandArgs {
|
||||
Dom: Dom,
|
||||
Cmd: Cmd,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(1, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// Result: string
|
||||
_, err = dec.Decode(&rResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUDomainAttach is the go wrapper for QEMU_PROC_DOMAIN_ATTACH.
|
||||
func (l *Libvirt) QEMUDomainAttach(PidValue uint32, Flags uint32) (rDom Domain, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUDomainAttachArgs {
|
||||
PidValue: PidValue,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(2, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// Dom: Domain
|
||||
_, err = dec.Decode(&rDom)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUDomainAgentCommand is the go wrapper for QEMU_PROC_DOMAIN_AGENT_COMMAND.
|
||||
func (l *Libvirt) QEMUDomainAgentCommand(Dom Domain, Cmd string, Timeout int32, Flags uint32) (rResult OptString, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUDomainAgentCommandArgs {
|
||||
Dom: Dom,
|
||||
Cmd: Cmd,
|
||||
Timeout: Timeout,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(3, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// Result: OptString
|
||||
_, err = dec.Decode(&rResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventRegister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER.
|
||||
func (l *Libvirt) QEMUConnectDomainMonitorEventRegister(Dom OptDomain, Event OptString, Flags uint32) (rCallbackID int32, err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUConnectDomainMonitorEventRegisterArgs {
|
||||
Dom: Dom,
|
||||
Event: Event,
|
||||
Flags: Flags,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var r response
|
||||
r, err = l.requestStream(4, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return value unmarshaling
|
||||
tpd := typedParamDecoder{}
|
||||
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
|
||||
rdr := bytes.NewReader(r.Payload)
|
||||
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
|
||||
// CallbackID: int32
|
||||
_, err = dec.Decode(&rCallbackID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUConnectDomainMonitorEventDeregister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER.
|
||||
func (l *Libvirt) QEMUConnectDomainMonitorEventDeregister(CallbackID int32) (err error) {
|
||||
var buf []byte
|
||||
|
||||
args := QEMUConnectDomainMonitorEventDeregisterArgs {
|
||||
CallbackID: CallbackID,
|
||||
}
|
||||
|
||||
buf, err = encode(&args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
_, err = l.requestStream(5, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QEMUDomainMonitorEvent is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_EVENT.
|
||||
func (l *Libvirt) QEMUDomainMonitorEvent() (err error) {
|
||||
var buf []byte
|
||||
|
||||
|
||||
_, err = l.requestStream(6, constants.QEMUProgram, buf, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
16449
vendor/github.com/digitalocean/go-libvirt/remote_protocol.gen.go
generated
vendored
Normal file
16449
vendor/github.com/digitalocean/go-libvirt/remote_protocol.gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,606 @@
|
|||
// Copyright 2018 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package libvirt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/digitalocean/go-libvirt/internal/constants"
|
||||
"github.com/digitalocean/go-libvirt/internal/event"
|
||||
xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
|
||||
)
|
||||
|
||||
// ErrUnsupported is returned if a procedure is not supported by libvirt
|
||||
var ErrUnsupported = errors.New("unsupported procedure requested")
|
||||
|
||||
// request and response types
|
||||
const (
|
||||
// Call is used when making calls to the remote server.
|
||||
Call = iota
|
||||
|
||||
// Reply indicates a server reply.
|
||||
Reply
|
||||
|
||||
// Message is an asynchronous notification.
|
||||
Message
|
||||
|
||||
// Stream represents a stream data packet.
|
||||
Stream
|
||||
|
||||
// CallWithFDs is used by a client to indicate the request has
|
||||
// arguments with file descriptors.
|
||||
CallWithFDs
|
||||
|
||||
// ReplyWithFDs is used by a server to indicate the request has
|
||||
// arguments with file descriptors.
|
||||
ReplyWithFDs
|
||||
)
|
||||
|
||||
// request and response statuses
|
||||
const (
|
||||
// StatusOK is always set for method calls or events.
|
||||
// For replies it indicates successful completion of the method.
|
||||
// For streams it indicates confirmation of the end of file on the stream.
|
||||
StatusOK = iota
|
||||
|
||||
// StatusError for replies indicates that the method call failed
|
||||
// and error information is being returned. For streams this indicates
|
||||
// that not all data was sent and the stream has aborted.
|
||||
StatusError
|
||||
|
||||
// StatusContinue is only used for streams.
|
||||
// This indicates that further data packets will be following.
|
||||
StatusContinue
|
||||
)
|
||||
|
||||
// header is a libvirt rpc packet header
|
||||
type header struct {
|
||||
// Program identifier
|
||||
Program uint32
|
||||
|
||||
// Program version
|
||||
Version uint32
|
||||
|
||||
// Remote procedure identifier
|
||||
Procedure uint32
|
||||
|
||||
// Call type, e.g., Reply
|
||||
Type uint32
|
||||
|
||||
// Call serial number
|
||||
Serial int32
|
||||
|
||||
// Request status, e.g., StatusOK
|
||||
Status uint32
|
||||
}
|
||||
|
||||
// packet represents a RPC request or response.
|
||||
type packet struct {
|
||||
// Size of packet, in bytes, including length.
|
||||
// Len + Header + Payload
|
||||
Len uint32
|
||||
Header header
|
||||
}
|
||||
|
||||
// Global packet instance, for use with unsafe.Sizeof()
|
||||
var _p packet
|
||||
|
||||
// internal rpc response
|
||||
type response struct {
|
||||
Payload []byte
|
||||
Status uint32
|
||||
}
|
||||
|
||||
// libvirt error response
|
||||
type libvirtError struct {
|
||||
Code uint32
|
||||
DomainID uint32
|
||||
Padding uint8
|
||||
Message string
|
||||
Level uint32
|
||||
}
|
||||
|
||||
func (e libvirtError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// checkError is used to check whether an error is a libvirtError, and if it is,
|
||||
// whether its error code matches the one passed in. It will return false if
|
||||
// these conditions are not met.
|
||||
func checkError(err error, expectedError errorNumber) bool {
|
||||
e, ok := err.(libvirtError)
|
||||
if ok {
|
||||
return e.Code == uint32(expectedError)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNotFound detects libvirt's ERR_NO_DOMAIN.
|
||||
func IsNotFound(err error) bool {
|
||||
return checkError(err, errNoDomain)
|
||||
}
|
||||
|
||||
// listen processes incoming data and routes
|
||||
// responses to their respective callback handler.
|
||||
func (l *Libvirt) listen() {
|
||||
for {
|
||||
// response packet length
|
||||
length, err := pktlen(l.r)
|
||||
if err != nil {
|
||||
// When the underlying connection EOFs or is closed, stop
|
||||
// this goroutine
|
||||
if err == io.EOF || strings.Contains(err.Error(), "use of closed network connection") {
|
||||
return
|
||||
}
|
||||
|
||||
// invalid packet
|
||||
continue
|
||||
}
|
||||
|
||||
// response header
|
||||
h, err := extractHeader(l.r)
|
||||
if err != nil {
|
||||
// invalid packet
|
||||
continue
|
||||
}
|
||||
|
||||
// payload: packet length minus what was previously read
|
||||
size := int(length) - int(unsafe.Sizeof(_p))
|
||||
buf := make([]byte, size)
|
||||
_, err = io.ReadFull(l.r, buf)
|
||||
if err != nil {
|
||||
// invalid packet
|
||||
continue
|
||||
}
|
||||
|
||||
// route response to caller
|
||||
l.route(h, buf)
|
||||
}
|
||||
}
|
||||
|
||||
// callback sends RPC responses to respective callers.
|
||||
func (l *Libvirt) callback(id int32, res response) {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
c, ok := l.callbacks[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
c <- res
|
||||
}
|
||||
|
||||
// route sends incoming packets to their listeners.
|
||||
func (l *Libvirt) route(h *header, buf []byte) {
|
||||
// route events to their respective listener
|
||||
var event event.Event
|
||||
|
||||
switch {
|
||||
case h.Program == constants.QEMUProgram && h.Procedure == constants.QEMUProcDomainMonitorEvent:
|
||||
event = &DomainEvent{}
|
||||
case h.Program == constants.Program && h.Procedure == constants.ProcDomainEventCallbackLifecycle:
|
||||
event = &DomainEventCallbackLifecycleMsg{}
|
||||
}
|
||||
|
||||
if event != nil {
|
||||
err := eventDecoder(buf, event)
|
||||
if err != nil { // event was malformed, drop.
|
||||
return
|
||||
}
|
||||
|
||||
l.stream(event)
|
||||
return
|
||||
}
|
||||
|
||||
// send response to caller
|
||||
l.callback(h.Serial, response{Payload: buf, Status: h.Status})
|
||||
}
|
||||
|
||||
// serial provides atomic access to the next sequential request serial number.
|
||||
func (l *Libvirt) serial() int32 {
|
||||
return atomic.AddInt32(&l.s, 1)
|
||||
}
|
||||
|
||||
// stream decodes and relays domain events to their respective listener.
|
||||
func (l *Libvirt) stream(e event.Event) {
|
||||
l.emux.RLock()
|
||||
defer l.emux.RUnlock()
|
||||
|
||||
q, ok := l.events[e.GetCallbackID()]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
q.Push(e)
|
||||
}
|
||||
|
||||
// addStream configures the routing for an event stream.
|
||||
func (l *Libvirt) addStream(s *event.Stream) {
|
||||
l.emux.Lock()
|
||||
defer l.emux.Unlock()
|
||||
|
||||
l.events[s.CallbackID] = s
|
||||
}
|
||||
|
||||
// removeStream notifies the libvirt server to stop sending events for the
|
||||
// provided callback ID. Upon successful de-registration the callback handler
|
||||
// is destroyed. Subsequent calls to removeStream are idempotent and return
|
||||
// nil.
|
||||
// TODO: Fix this comment
|
||||
func (l *Libvirt) removeStream(id int32) error {
|
||||
l.emux.Lock()
|
||||
defer l.emux.Unlock()
|
||||
|
||||
// if the event is already removed, just return nil
|
||||
_, ok := l.events[id]
|
||||
if ok {
|
||||
delete(l.events, id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// register configures a method response callback
|
||||
func (l *Libvirt) register(id int32, c chan response) {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
l.callbacks[id] = c
|
||||
}
|
||||
|
||||
// deregister destroys a method response callback. It is the responsibility of
|
||||
// the caller to manage locking (l.cmux) during this call.
|
||||
func (l *Libvirt) deregister(id int32) {
|
||||
_, ok := l.callbacks[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
close(l.callbacks[id])
|
||||
delete(l.callbacks, id)
|
||||
}
|
||||
|
||||
// deregisterAll closes all waiting callback channels. This is used to clean up
|
||||
// if the connection to libvirt is lost. Callers waiting for responses will
|
||||
// return an error when the response channel is closed, rather than just
|
||||
// hanging.
|
||||
func (l *Libvirt) deregisterAll() {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
for id := range l.callbacks {
|
||||
l.deregister(id)
|
||||
}
|
||||
}
|
||||
|
||||
// request performs a libvirt RPC request.
|
||||
// returns response returned by server.
|
||||
// if response is not OK, decodes error from it and returns it.
|
||||
func (l *Libvirt) request(proc uint32, program uint32, payload []byte) (response, error) {
|
||||
return l.requestStream(proc, program, payload, nil, nil)
|
||||
}
|
||||
|
||||
// requestStream performs a libvirt RPC request. The `out` and `in` parameters
|
||||
// are optional, and should be nil when RPC endpoints don't return a stream.
|
||||
func (l *Libvirt) requestStream(proc uint32, program uint32, payload []byte,
|
||||
out io.Reader, in io.Writer) (response, error) {
|
||||
serial := l.serial()
|
||||
c := make(chan response)
|
||||
|
||||
l.register(serial, c)
|
||||
defer func() {
|
||||
l.cmux.Lock()
|
||||
defer l.cmux.Unlock()
|
||||
|
||||
l.deregister(serial)
|
||||
}()
|
||||
|
||||
err := l.sendPacket(serial, proc, program, payload, Call, StatusOK)
|
||||
if err != nil {
|
||||
return response{}, err
|
||||
}
|
||||
|
||||
resp, err := l.getResponse(c)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
abort := make(chan bool)
|
||||
outErr := make(chan error)
|
||||
go func() {
|
||||
outErr <- l.sendStream(serial, proc, program, out, abort)
|
||||
}()
|
||||
|
||||
// Even without incoming stream server sends confirmation once all data is received
|
||||
resp, err = l.processIncomingStream(c, in)
|
||||
if err != nil {
|
||||
abort <- true
|
||||
return resp, err
|
||||
}
|
||||
|
||||
err = <-outErr
|
||||
if err != nil {
|
||||
return response{}, err
|
||||
}
|
||||
}
|
||||
|
||||
switch in {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return l.processIncomingStream(c, in)
|
||||
}
|
||||
}
|
||||
|
||||
// processIncomingStream is called once we've successfully sent a request to
|
||||
// libvirt. It writes the responses back to the stream passed by the caller
|
||||
// until libvirt sends a packet with statusOK or an error.
|
||||
func (l *Libvirt) processIncomingStream(c chan response, inStream io.Writer) (response, error) {
|
||||
for {
|
||||
resp, err := l.getResponse(c)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// StatusOK indicates end of stream
|
||||
if resp.Status == StatusOK {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// FIXME: this smells.
|
||||
// StatusError is handled in getResponse, so this must be StatusContinue
|
||||
// StatusContinue is only valid here for stream packets
|
||||
// libvirtd breaks protocol and returns StatusContinue with an
|
||||
// empty response Payload when the stream finishes
|
||||
if len(resp.Payload) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
if inStream != nil {
|
||||
_, err = inStream.Write(resp.Payload)
|
||||
if err != nil {
|
||||
return response{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Libvirt) sendStream(serial int32, proc uint32, program uint32, stream io.Reader, abort chan bool) error {
|
||||
// Keep total packet length under 4 MiB to follow possible limitation in libvirt server code
|
||||
buf := make([]byte, 4*MiB-unsafe.Sizeof(_p))
|
||||
for {
|
||||
select {
|
||||
case <-abort:
|
||||
return l.sendPacket(serial, proc, program, nil, Stream, StatusError)
|
||||
default:
|
||||
}
|
||||
n, err := stream.Read(buf)
|
||||
if n > 0 {
|
||||
err2 := l.sendPacket(serial, proc, program, buf[:n], Stream, StatusContinue)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return l.sendPacket(serial, proc, program, nil, Stream, StatusOK)
|
||||
}
|
||||
// keep original error
|
||||
err2 := l.sendPacket(serial, proc, program, nil, Stream, StatusError)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Libvirt) sendPacket(serial int32, proc uint32, program uint32, payload []byte, typ uint32, status uint32) error {
|
||||
|
||||
p := packet{
|
||||
Header: header{
|
||||
Program: program,
|
||||
Version: constants.ProtocolVersion,
|
||||
Procedure: proc,
|
||||
Type: typ,
|
||||
Serial: serial,
|
||||
Status: status,
|
||||
},
|
||||
}
|
||||
|
||||
size := int(unsafe.Sizeof(p.Len)) + int(unsafe.Sizeof(p.Header))
|
||||
if payload != nil {
|
||||
size += len(payload)
|
||||
}
|
||||
p.Len = uint32(size)
|
||||
|
||||
// write header
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
err := binary.Write(l.w, binary.BigEndian, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write payload
|
||||
if payload != nil {
|
||||
err = binary.Write(l.w, binary.BigEndian, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return l.w.Flush()
|
||||
}
|
||||
|
||||
func (l *Libvirt) getResponse(c chan response) (response, error) {
|
||||
resp := <-c
|
||||
if resp.Status == StatusError {
|
||||
return resp, decodeError(resp.Payload)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// encode XDR encodes the provided data.
|
||||
func encode(data interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
_, err := xdr.Marshal(&buf, data)
|
||||
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// decodeError extracts an error message from the provider buffer.
|
||||
func decodeError(buf []byte) error {
|
||||
var e libvirtError
|
||||
|
||||
dec := xdr.NewDecoder(bytes.NewReader(buf))
|
||||
_, err := dec.Decode(&e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(e.Message, "unknown procedure") {
|
||||
return ErrUnsupported
|
||||
}
|
||||
// if libvirt returns ERR_OK, ignore the error
|
||||
if checkError(e, errOk) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// eventDecoder decodes an event from a xdr buffer.
|
||||
func eventDecoder(buf []byte, e interface{}) error {
|
||||
dec := xdr.NewDecoder(bytes.NewReader(buf))
|
||||
_, err := dec.Decode(e)
|
||||
return err
|
||||
}
|
||||
|
||||
// pktlen returns the length of an incoming RPC packet. Read errors will
|
||||
// result in a returned response length of 0 and a non-nil error.
|
||||
func pktlen(r io.Reader) (uint32, error) {
|
||||
buf := make([]byte, unsafe.Sizeof(_p.Len))
|
||||
|
||||
// extract the packet's length from the header
|
||||
_, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint32(buf), nil
|
||||
}
|
||||
|
||||
// extractHeader returns the decoded header from an incoming response.
|
||||
func extractHeader(r io.Reader) (*header, error) {
|
||||
buf := make([]byte, unsafe.Sizeof(_p.Header))
|
||||
|
||||
// extract the packet's header from r
|
||||
_, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &header{
|
||||
Program: binary.BigEndian.Uint32(buf[0:4]),
|
||||
Version: binary.BigEndian.Uint32(buf[4:8]),
|
||||
Procedure: binary.BigEndian.Uint32(buf[8:12]),
|
||||
Type: binary.BigEndian.Uint32(buf[12:16]),
|
||||
Serial: int32(binary.BigEndian.Uint32(buf[16:20])),
|
||||
Status: binary.BigEndian.Uint32(buf[20:24]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type typedParamDecoder struct{}
|
||||
|
||||
// Decode decodes a TypedParam. These are part of the libvirt spec, and not xdr
|
||||
// proper. TypedParams contain a name, which is called Field for some reason,
|
||||
// and a Value, which itself has a "discriminant" - an integer enum encoding the
|
||||
// actual type, and a value, the length of which varies based on the actual
|
||||
// type.
|
||||
func (tpd typedParamDecoder) Decode(d *xdr.Decoder, v reflect.Value) (int, error) {
|
||||
// Get the name of the typed param first
|
||||
name, n, err := d.DecodeString()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
val, n2, err := tpd.decodeTypedParamValue(d)
|
||||
n += n2
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
tp := &TypedParam{Field: name, Value: *val}
|
||||
v.Set(reflect.ValueOf(*tp))
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeTypedParamValue decodes the Value part of a TypedParam.
|
||||
func (typedParamDecoder) decodeTypedParamValue(d *xdr.Decoder) (*TypedParamValue, int, error) {
|
||||
// All TypedParamValues begin with a uint32 discriminant that tells us what
|
||||
// type they are.
|
||||
discriminant, n, err := d.DecodeUint()
|
||||
if err != nil {
|
||||
return nil, n, err
|
||||
}
|
||||
var n2 int
|
||||
var tpv *TypedParamValue
|
||||
switch discriminant {
|
||||
case 1:
|
||||
var val int32
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 2:
|
||||
var val uint32
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 3:
|
||||
var val int64
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 4:
|
||||
var val uint64
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 5:
|
||||
var val float64
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 6:
|
||||
var val int32
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
case 7:
|
||||
var val string
|
||||
n2, err = d.Decode(&val)
|
||||
tpv = &TypedParamValue{D: discriminant, I: val}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("invalid parameter type %v", discriminant)
|
||||
}
|
||||
n += n2
|
||||
|
||||
return tpv, n, err
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2016 The go-libvirt Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This module provides different units of measurement to make other
|
||||
// code more readable.
|
||||
|
||||
package libvirt
|
||||
|
||||
const (
|
||||
// B - byte
|
||||
B = 1
|
||||
// KiB - kibibyte
|
||||
KiB = 1024 * B
|
||||
// MiB - mebibyte
|
||||
MiB = 1024 * KiB
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
Maintainer
|
||||
----------
|
||||
DigitalOcean, Inc
|
||||
|
||||
Original Authors
|
||||
----------------
|
||||
Ben LeMasurier <blemasurier@digitalocean.com>
|
||||
Matt Layher <mlayher@digitalocean.com>
|
||||
|
||||
Contributors
|
||||
------------
|
||||
David Anderson <dave@natulte.net>
|
||||
Justin Kim <justin@digitalocean.com>
|
||||
Luis Sagastume <lsagastume1990@gmail.com>
|
||||
Nedim Dedic <nedim_dedic@yahoo.com>
|
||||
Roberto J Rojas <robertojrojas@gmail.com>
|
||||
Marko Mudrinic <mudrinic.mare@gmail.com>
|
|
@ -0,0 +1,195 @@
|
|||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 4. Redistribution
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
#### 6. Trademarks
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
### APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets `[]` replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same “printed page” as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
QMP
|
||||
===
|
||||
|
||||
Package `qmp` enables interaction with QEMU instances via the QEMU Machine Protocol (QMP).
|
||||
|
||||
## Available Drivers
|
||||
|
||||
### Libvirt
|
||||
|
||||
If your environment is managed by Libvirt, QMP interaction must be proxied through the Libvirt daemon. This can be be done through two available drivers:
|
||||
|
||||
#### RPC
|
||||
|
||||
The RPC driver provides a pure Go implementation of Libvirt's RPC protocol.
|
||||
|
||||
```go
|
||||
//conn, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
|
||||
conn, err := net.DialTimeout("tcp", "192.168.1.1:16509", 2*time.Second)
|
||||
monitor := libvirtrpc.New("stage-lb-1", conn)
|
||||
```
|
||||
|
||||
#### virsh
|
||||
|
||||
A connection to the monitor socket is provided by proxing requests through the `virsh` executable.
|
||||
|
||||
```go
|
||||
monitor, err := qmp.NewLibvirtMonitor("qemu:///system", "stage-lb-1")
|
||||
```
|
||||
|
||||
### Socket
|
||||
|
||||
If your QEMU instances are not managed by libvirt, direct communication over its UNIX socket is available.
|
||||
|
||||
```go
|
||||
monitor, err := qmp.NewSocketMonitor("unix", "/var/lib/qemu/example.monitor", 2*time.Second)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Using the above to establish a new `qmp.Monitor`, the following examples provide a brief overview of QMP usage.
|
||||
|
||||
_error checking omitted for the sake of brevity._
|
||||
|
||||
### Command Execution
|
||||
```go
|
||||
type StatusResult struct {
|
||||
ID string `json:"id"`
|
||||
Return struct {
|
||||
Running bool `json:"running"`
|
||||
Singlestep bool `json:"singlestep"`
|
||||
Status string `json:"status"`
|
||||
} `json:"return"`
|
||||
}
|
||||
|
||||
monitor.Connect()
|
||||
defer monitor.Disconnect()
|
||||
|
||||
cmd := []byte(`{ "execute": "query-status" }`)
|
||||
raw, _ := monitor.Run(cmd)
|
||||
|
||||
var result StatusResult
|
||||
json.Unmarshal(raw, &result)
|
||||
|
||||
fmt.Println(result.Return.Status)
|
||||
```
|
||||
|
||||
```
|
||||
running
|
||||
```
|
||||
|
||||
### Event Monitor
|
||||
|
||||
```go
|
||||
monitor.Connect()
|
||||
defer monitor.Disconnect()
|
||||
|
||||
stream, _ := monitor.Events()
|
||||
for e := range stream {
|
||||
log.Printf("EVENT: %s", e.Event)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
$ virsh reboot example
|
||||
Domain example is being rebooted
|
||||
```
|
||||
|
||||
```
|
||||
EVENT: POWERDOWN
|
||||
EVENT: SHUTDOWN
|
||||
EVENT: STOP
|
||||
EVENT: RESET
|
||||
EVENT: RESUME
|
||||
EVENT: RESET
|
||||
...
|
||||
```
|
||||
|
||||
## More information
|
||||
|
||||
* [QEMU QMP Wiki](http://wiki.qemu.org/QMP)
|
||||
* [QEMU QMP Intro](http://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/qmp-intro.txt;hb=HEAD)
|
||||
* [QEMU QMP Events](http://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/qmp-events.txt;hb=HEAD)
|
||||
* [QEMU QMP Spec](http://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/qmp-spec.txt;hb=HEAD)
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2016 The go-qemu Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package qmp enables interaction with QEMU instances
|
||||
// via the QEMU Machine Protocol (QMP).
|
||||
package qmp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrEventsNotSupported is returned by Events() if event streams
|
||||
// are unsupported by either QEMU or libvirt.
|
||||
var ErrEventsNotSupported = errors.New("event monitor is not supported")
|
||||
|
||||
// Monitor represents a QEMU Machine Protocol socket.
|
||||
// See: http://wiki.qemu.org/QMP
|
||||
type Monitor interface {
|
||||
Connect() error
|
||||
Disconnect() error
|
||||
Run(command []byte) (out []byte, err error)
|
||||
Events(context.Context) (events <-chan Event, err error)
|
||||
}
|
||||
|
||||
// Command represents a QMP command.
|
||||
type Command struct {
|
||||
// Name of the command to run
|
||||
Execute string `json:"execute"`
|
||||
|
||||
// Optional arguments for the above command.
|
||||
Args interface{} `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
ID string `json:"id"`
|
||||
Return interface{} `json:"return,omitempty"`
|
||||
Error struct {
|
||||
Class string `json:"class"`
|
||||
Desc string `json:"desc"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r *response) Err() error {
|
||||
if r.Error.Desc == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(r.Error.Desc)
|
||||
}
|
||||
|
||||
// Event represents a QEMU QMP event.
|
||||
// See http://wiki.qemu.org/QMP
|
||||
type Event struct {
|
||||
// Event name, e.g., BLOCK_JOB_COMPLETE
|
||||
Event string `json:"event"`
|
||||
|
||||
// Arbitrary event data
|
||||
Data map[string]interface{} `json:"data"`
|
||||
|
||||
// Event timestamp, provided by QEMU.
|
||||
Timestamp struct {
|
||||
Seconds int64 `json:"seconds"`
|
||||
Microseconds int64 `json:"microseconds"`
|
||||
} `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Version is the QEMU version structure returned when a QMP connection is
|
||||
// initiated.
|
||||
type Version struct {
|
||||
Package string `json:"package"`
|
||||
QEMU struct {
|
||||
Major int `json:"major"`
|
||||
Micro int `json:"micro"`
|
||||
Minor int `json:"minor"`
|
||||
} `json:"qemu"`
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
q := v.QEMU
|
||||
return fmt.Sprintf("%d.%d.%d", q.Major, q.Minor, q.Micro)
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2016 The go-qemu Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package qmp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
var _ Monitor = &LibvirtRPCMonitor{}
|
||||
|
||||
// A LibvirtRPCMonitor implements LibVirt's remote procedure call protocol.
|
||||
type LibvirtRPCMonitor struct {
|
||||
l *libvirt.Libvirt
|
||||
// Domain name as seen by libvirt, e.g., stage-lb-1
|
||||
Domain string
|
||||
}
|
||||
|
||||
// NewLibvirtRPCMonitor configures a new Libvirt RPC Monitor connection.
|
||||
// The provided domain should be the name of the domain as seen
|
||||
// by libvirt, e.g., stage-lb-1.
|
||||
func NewLibvirtRPCMonitor(domain string, conn net.Conn) *LibvirtRPCMonitor {
|
||||
l := libvirt.New(conn)
|
||||
|
||||
return &LibvirtRPCMonitor{
|
||||
l: l,
|
||||
Domain: domain,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect establishes communication with the libvirt server.
|
||||
// The underlying libvirt socket connection must be previously established.
|
||||
func (rpc *LibvirtRPCMonitor) Connect() error {
|
||||
return rpc.l.Connect()
|
||||
}
|
||||
|
||||
// Disconnect shuts down communication with the libvirt server
|
||||
// and closes the underlying net.Conn.
|
||||
func (rpc *LibvirtRPCMonitor) Disconnect() error {
|
||||
return rpc.l.Disconnect()
|
||||
}
|
||||
|
||||
// Events streams QEMU QMP Events until the provided context is cancelled.
|
||||
// If a problem is encountered setting up the event monitor connection
|
||||
// an error will be returned. Errors encountered during streaming will
|
||||
// cause the returned event channel to be closed.
|
||||
func (rpc *LibvirtRPCMonitor) Events(ctx context.Context) (<-chan Event, error) {
|
||||
events, err := rpc.l.SubscribeQEMUEvents(ctx, rpc.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := make(chan Event)
|
||||
go func() {
|
||||
// process events
|
||||
for e := range events {
|
||||
qe, err := qmpEvent(&e)
|
||||
if err != nil {
|
||||
close(c)
|
||||
break
|
||||
}
|
||||
|
||||
c <- *qe
|
||||
}
|
||||
}()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Run executes the given QAPI command against a domain's QEMU instance.
|
||||
// For a list of available QAPI commands, see:
|
||||
// http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD
|
||||
func (rpc *LibvirtRPCMonitor) Run(cmd []byte) ([]byte, error) {
|
||||
return rpc.l.Run(rpc.Domain, cmd)
|
||||
}
|
||||
|
||||
// qmpEvent takes a libvirt DomainEvent and returns the QMP equivalent.
|
||||
func qmpEvent(e *libvirt.DomainEvent) (*Event, error) {
|
||||
var qe Event
|
||||
|
||||
if e.Details != nil {
|
||||
if err := json.Unmarshal(e.Details, &qe.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
qe.Event = e.Event
|
||||
qe.Timestamp.Seconds = int64(e.Seconds)
|
||||
qe.Timestamp.Microseconds = int64(e.Microseconds)
|
||||
|
||||
return &qe, nil
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
// Copyright 2016 The go-qemu Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package qmp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A SocketMonitor is a Monitor which speaks directly to a QEMU Machine Protocol
|
||||
// (QMP) socket. Communication is performed directly using a QEMU monitor socket,
|
||||
// typically using a UNIX socket or TCP connection. Multiple connections to the
|
||||
// same domain are not permitted, and will result in the monitor blocking until
|
||||
// the existing connection is closed.
|
||||
type SocketMonitor struct {
|
||||
// QEMU version reported by a connected monitor socket.
|
||||
Version *Version
|
||||
|
||||
// QEMU QMP capabiltiies reported by a connected monitor socket.
|
||||
Capabilities []string
|
||||
|
||||
// Underlying connection
|
||||
c net.Conn
|
||||
|
||||
// Serialize running command against domain
|
||||
mu sync.Mutex
|
||||
|
||||
// Send command responses and errors
|
||||
stream <-chan streamResponse
|
||||
|
||||
// Send domain events to listeners when available
|
||||
listeners *int32
|
||||
events <-chan Event
|
||||
}
|
||||
|
||||
// NewSocketMonitor configures a connection to the provided QEMU monitor socket.
|
||||
// An error is returned if the socket cannot be successfully dialed, or the
|
||||
// dial attempt times out.
|
||||
//
|
||||
// NewSocketMonitor may dial the QEMU socket using a variety of connection types:
|
||||
// NewSocketMonitor("unix", "/var/lib/qemu/example.monitor", 2 * time.Second)
|
||||
// NewSocketMonitor("tcp", "8.8.8.8:4444", 2 * time.Second)
|
||||
func NewSocketMonitor(network, addr string, timeout time.Duration) (*SocketMonitor, error) {
|
||||
c, err := net.DialTimeout(network, addr, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mon := &SocketMonitor{
|
||||
c: c,
|
||||
listeners: new(int32),
|
||||
}
|
||||
|
||||
return mon, nil
|
||||
}
|
||||
|
||||
// Listen creates a new SocketMonitor listening for a single connection to the provided socket file or address.
|
||||
// An error is returned if unable to listen at the specified file path or port.
|
||||
//
|
||||
// Listen will wait for a QEMU socket connection using a variety connection types:
|
||||
// Listen("unix", "/var/lib/qemu/example.monitor")
|
||||
// Listen("tcp", "0.0.0.0:4444")
|
||||
func Listen(network, addr string) (*SocketMonitor, error) {
|
||||
l, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := l.Accept()
|
||||
defer l.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mon := &SocketMonitor{
|
||||
c: c,
|
||||
listeners: new(int32),
|
||||
}
|
||||
|
||||
return mon, nil
|
||||
}
|
||||
|
||||
// Disconnect closes the QEMU monitor socket connection.
|
||||
func (mon *SocketMonitor) Disconnect() error {
|
||||
atomic.StoreInt32(mon.listeners, 0)
|
||||
err := mon.c.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// qmpCapabilities is the command which must be executed to perform the
|
||||
// QEMU QMP handshake.
|
||||
const qmpCapabilities = "qmp_capabilities"
|
||||
|
||||
// Connect sets up a QEMU QMP connection by connecting directly to the QEMU
|
||||
// monitor socket. An error is returned if the capabilities handshake does
|
||||
// not succeed.
|
||||
func (mon *SocketMonitor) Connect() error {
|
||||
enc := json.NewEncoder(mon.c)
|
||||
dec := json.NewDecoder(mon.c)
|
||||
|
||||
// Check for banner on startup
|
||||
var ban banner
|
||||
if err := dec.Decode(&ban); err != nil {
|
||||
return err
|
||||
}
|
||||
mon.Version = &ban.QMP.Version
|
||||
mon.Capabilities = ban.QMP.Capabilities
|
||||
|
||||
// Issue capabilities handshake
|
||||
cmd := Command{Execute: qmpCapabilities}
|
||||
if err := enc.Encode(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for no error on return
|
||||
var r response
|
||||
if err := dec.Decode(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize socket listener for command responses and asynchronous
|
||||
// events
|
||||
events := make(chan Event)
|
||||
stream := make(chan streamResponse)
|
||||
go mon.listen(mon.c, events, stream)
|
||||
|
||||
mon.events = events
|
||||
mon.stream = stream
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Events streams QEMU QMP Events.
|
||||
// Events should only be called once per Socket. If used with a qemu.Domain,
|
||||
// qemu.Domain.Events should be called to retrieve events instead.
|
||||
func (mon *SocketMonitor) Events(context.Context) (<-chan Event, error) {
|
||||
atomic.AddInt32(mon.listeners, 1)
|
||||
return mon.events, nil
|
||||
}
|
||||
|
||||
// listen listens for incoming data from a QEMU monitor socket. It determines
|
||||
// if the data is an asynchronous event or a response to a command, and returns
|
||||
// the data on the appropriate channel.
|
||||
func (mon *SocketMonitor) listen(r io.Reader, events chan<- Event, stream chan<- streamResponse) {
|
||||
defer close(events)
|
||||
defer close(stream)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
var e Event
|
||||
|
||||
b := scanner.Bytes()
|
||||
if err := json.Unmarshal(b, &e); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If data does not have an event type, it must be in response to a command.
|
||||
if e.Event == "" {
|
||||
stream <- streamResponse{buf: b}
|
||||
continue
|
||||
}
|
||||
|
||||
// If nobody is listening for events, do not bother sending them.
|
||||
if atomic.LoadInt32(mon.listeners) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
events <- e
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
stream <- streamResponse{err: err}
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the given QAPI command against a domain's QEMU instance.
|
||||
// For a list of available QAPI commands, see:
|
||||
// http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD
|
||||
func (mon *SocketMonitor) Run(command []byte) ([]byte, error) {
|
||||
// Just call RunWithFile with no file
|
||||
return mon.RunWithFile(command, nil)
|
||||
}
|
||||
|
||||
// RunWithFile behaves like Run but allows for passing a file through out-of-band data.
|
||||
func (mon *SocketMonitor) RunWithFile(command []byte, file *os.File) ([]byte, error) {
|
||||
// Only allow a single command to be run at a time to ensure that responses
|
||||
// to a command cannot be mixed with responses from another command
|
||||
mon.mu.Lock()
|
||||
defer mon.mu.Unlock()
|
||||
|
||||
if file == nil {
|
||||
// Just send a normal command through.
|
||||
if _, err := mon.c.Write(command); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
unixConn, ok := mon.c.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("RunWithFile only works with unix monitor sockets")
|
||||
}
|
||||
|
||||
oobSupported := false
|
||||
for _, capability := range mon.Capabilities {
|
||||
if capability == "oob" {
|
||||
oobSupported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !oobSupported {
|
||||
return nil, fmt.Errorf("The QEMU server doesn't support oob (needed for RunWithFile)")
|
||||
}
|
||||
|
||||
// Send the command along with the file descriptor.
|
||||
oob := getUnixRights(file)
|
||||
if _, _, err := unixConn.WriteMsgUnix(command, oob, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for a response or error to our command
|
||||
res := <-mon.stream
|
||||
if res.err != nil {
|
||||
return nil, res.err
|
||||
}
|
||||
|
||||
// Check for QEMU errors
|
||||
var r response
|
||||
if err := json.Unmarshal(res.buf, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.buf, nil
|
||||
}
|
||||
|
||||
// banner is a wrapper type around a Version.
|
||||
type banner struct {
|
||||
QMP struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Version Version `json:"version"`
|
||||
} `json:"QMP"`
|
||||
}
|
||||
|
||||
// streamResponse is a struct sent over a channel in response to a command.
|
||||
type streamResponse struct {
|
||||
buf []byte
|
||||
err error
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2016 The go-qemu Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package qmp
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getUnixRights(file *os.File) []byte {
|
||||
return unix.UnixRights(int(file.Fd()))
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2016 The go-qemu Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build windows
|
||||
|
||||
package qmp
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func getUnixRights(file *os.File) []byte {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
|
||||
go:
|
||||
- 1.14.x
|
||||
|
||||
script:
|
||||
- go test -race ./...
|
||||
- for i in _examples/*/; do go build $i/*.go || exit 1; done
|
|
@ -0,0 +1,120 @@
|
|||
# Multi Progress Bar
|
||||
|
||||
[](https://pkg.go.dev/github.com/vbauerster/mpb/v6)
|
||||
[](https://travis-ci.org/vbauerster/mpb)
|
||||
[](https://goreportcard.com/report/github.com/vbauerster/mpb)
|
||||
|
||||
**mpb** is a Go lib for rendering progress bars in terminal applications.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Bars**: Multiple progress bars are supported
|
||||
- **Dynamic Total**: Set total while bar is running
|
||||
- **Dynamic Add/Remove**: Dynamically add or remove bars
|
||||
- **Cancellation**: Cancel whole rendering process
|
||||
- **Predefined Decorators**: Elapsed time, [ewma](https://github.com/VividCortex/ewma) based ETA, Percentage, Bytes counter
|
||||
- **Decorator's width sync**: Synchronized decorator's width among multiple bars
|
||||
|
||||
## Usage
|
||||
|
||||
#### [Rendering single bar](_examples/singleBar/main.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/vbauerster/mpb/v6"
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// initialize progress container, with custom width
|
||||
p := mpb.New(mpb.WithWidth(64))
|
||||
|
||||
total := 100
|
||||
name := "Single Bar:"
|
||||
// adding a single bar, which will inherit container's width
|
||||
bar := p.Add(int64(total),
|
||||
// progress bar filler with customized style
|
||||
mpb.NewBarFiller("╢▌▌░╟"),
|
||||
mpb.PrependDecorators(
|
||||
// display our name with one space on the right
|
||||
decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}),
|
||||
// replace ETA decorator with "done" message, OnComplete event
|
||||
decor.OnComplete(
|
||||
decor.AverageETA(decor.ET_STYLE_GO, decor.WC{W: 4}), "done",
|
||||
),
|
||||
),
|
||||
mpb.AppendDecorators(decor.Percentage()),
|
||||
)
|
||||
// simulating some work
|
||||
max := 100 * time.Millisecond
|
||||
for i := 0; i < total; i++ {
|
||||
time.Sleep(time.Duration(rand.Intn(10)+1) * max / 10)
|
||||
bar.Increment()
|
||||
}
|
||||
// wait for our bar to complete and flush
|
||||
p.Wait()
|
||||
}
|
||||
```
|
||||
|
||||
#### [Rendering multiple bars](_examples/multiBars/main.go)
|
||||
|
||||
```go
|
||||
var wg sync.WaitGroup
|
||||
// pass &wg (optional), so p will wait for it eventually
|
||||
p := mpb.New(mpb.WithWaitGroup(&wg))
|
||||
total, numBars := 100, 3
|
||||
wg.Add(numBars)
|
||||
|
||||
for i := 0; i < numBars; i++ {
|
||||
name := fmt.Sprintf("Bar#%d:", i)
|
||||
bar := p.AddBar(int64(total),
|
||||
mpb.PrependDecorators(
|
||||
// simple name decorator
|
||||
decor.Name(name),
|
||||
// decor.DSyncWidth bit enables column width synchronization
|
||||
decor.Percentage(decor.WCSyncSpace),
|
||||
),
|
||||
mpb.AppendDecorators(
|
||||
// replace ETA decorator with "done" message, OnComplete event
|
||||
decor.OnComplete(
|
||||
// ETA decorator with ewma age of 60
|
||||
decor.EwmaETA(decor.ET_STYLE_GO, 60), "done",
|
||||
),
|
||||
),
|
||||
)
|
||||
// simulating some work
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
max := 100 * time.Millisecond
|
||||
for i := 0; i < total; i++ {
|
||||
// start variable is solely for EWMA calculation
|
||||
// EWMA's unit of measure is an iteration's duration
|
||||
start := time.Now()
|
||||
time.Sleep(time.Duration(rng.Intn(10)+1) * max / 10)
|
||||
bar.Increment()
|
||||
// we need to call DecoratorEwmaUpdate to fulfill ewma decorator's contract
|
||||
bar.DecoratorEwmaUpdate(time.Since(start))
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Waiting for passed &wg and for all bars to complete and flush
|
||||
p.Wait()
|
||||
```
|
||||
|
||||
#### [Dynamic total](_examples/dynTotal/main.go)
|
||||
|
||||

|
||||
|
||||
#### [Complex example](_examples/complex/main.go)
|
||||
|
||||

|
||||
|
||||
#### [Bytes counters](_examples/io/main.go)
|
||||
|
||||

|
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
|
@ -0,0 +1,492 @@
|
|||
package mpb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
)
|
||||
|
||||
// Bar represents a progress bar.
|
||||
type Bar struct {
|
||||
priority int // used by heap
|
||||
index int // used by heap
|
||||
|
||||
extendedLines int
|
||||
toShutdown bool
|
||||
toDrop bool
|
||||
noPop bool
|
||||
hasEwmaDecorators bool
|
||||
operateState chan func(*bState)
|
||||
frameCh chan io.Reader
|
||||
syncTableCh chan [][]chan int
|
||||
completed chan bool
|
||||
|
||||
// cancel is called either by user or on complete event
|
||||
cancel func()
|
||||
// done is closed after cacheState is assigned
|
||||
done chan struct{}
|
||||
// cacheState is populated, right after close(shutdown)
|
||||
cacheState *bState
|
||||
|
||||
container *Progress
|
||||
dlogger *log.Logger
|
||||
recoveredPanic interface{}
|
||||
}
|
||||
|
||||
type extenderFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int)
|
||||
|
||||
// bState is actual bar state. It gets passed to *Bar.serve(...) monitor
|
||||
// goroutine.
|
||||
type bState struct {
|
||||
id int
|
||||
priority int
|
||||
reqWidth int
|
||||
total int64
|
||||
current int64
|
||||
refill int64
|
||||
lastN int64
|
||||
iterated bool
|
||||
trimSpace bool
|
||||
completed bool
|
||||
completeFlushed bool
|
||||
triggerComplete bool
|
||||
dropOnComplete bool
|
||||
noPop bool
|
||||
aDecorators []decor.Decorator
|
||||
pDecorators []decor.Decorator
|
||||
averageDecorators []decor.AverageDecorator
|
||||
ewmaDecorators []decor.EwmaDecorator
|
||||
shutdownListeners []decor.ShutdownListener
|
||||
bufP, bufB, bufA *bytes.Buffer
|
||||
filler BarFiller
|
||||
middleware func(BarFiller) BarFiller
|
||||
extender extenderFunc
|
||||
|
||||
// runningBar is a key for *pState.parkedBars
|
||||
runningBar *Bar
|
||||
|
||||
debugOut io.Writer
|
||||
}
|
||||
|
||||
func newBar(container *Progress, bs *bState) *Bar {
|
||||
logPrefix := fmt.Sprintf("%sbar#%02d ", container.dlogger.Prefix(), bs.id)
|
||||
ctx, cancel := context.WithCancel(container.ctx)
|
||||
|
||||
bar := &Bar{
|
||||
container: container,
|
||||
priority: bs.priority,
|
||||
toDrop: bs.dropOnComplete,
|
||||
noPop: bs.noPop,
|
||||
operateState: make(chan func(*bState)),
|
||||
frameCh: make(chan io.Reader, 1),
|
||||
syncTableCh: make(chan [][]chan int, 1),
|
||||
completed: make(chan bool, 1),
|
||||
done: make(chan struct{}),
|
||||
cancel: cancel,
|
||||
dlogger: log.New(bs.debugOut, logPrefix, log.Lshortfile),
|
||||
}
|
||||
|
||||
go bar.serve(ctx, bs)
|
||||
return bar
|
||||
}
|
||||
|
||||
// ProxyReader wraps r with metrics required for progress tracking.
|
||||
// Panics if r is nil.
|
||||
func (b *Bar) ProxyReader(r io.Reader) io.ReadCloser {
|
||||
if r == nil {
|
||||
panic("expected non nil io.Reader")
|
||||
}
|
||||
return newProxyReader(r, b)
|
||||
}
|
||||
|
||||
// ID returs id of the bar.
|
||||
func (b *Bar) ID() int {
|
||||
result := make(chan int)
|
||||
select {
|
||||
case b.operateState <- func(s *bState) { result <- s.id }:
|
||||
return <-result
|
||||
case <-b.done:
|
||||
return b.cacheState.id
|
||||
}
|
||||
}
|
||||
|
||||
// Current returns bar's current number, in other words sum of all increments.
|
||||
func (b *Bar) Current() int64 {
|
||||
result := make(chan int64)
|
||||
select {
|
||||
case b.operateState <- func(s *bState) { result <- s.current }:
|
||||
return <-result
|
||||
case <-b.done:
|
||||
return b.cacheState.current
|
||||
}
|
||||
}
|
||||
|
||||
// SetRefill sets refill flag with specified amount.
|
||||
// The underlying BarFiller will change its visual representation, to
|
||||
// indicate refill event. Refill event may be referred to some retry
|
||||
// operation for example.
|
||||
func (b *Bar) SetRefill(amount int64) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
s.refill = amount
|
||||
}:
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
// TraverseDecorators traverses all available decorators and calls cb func on each.
|
||||
func (b *Bar) TraverseDecorators(cb func(decor.Decorator)) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
for _, decorators := range [...][]decor.Decorator{
|
||||
s.pDecorators,
|
||||
s.aDecorators,
|
||||
} {
|
||||
for _, d := range decorators {
|
||||
cb(extractBaseDecorator(d))
|
||||
}
|
||||
}
|
||||
}:
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
// SetTotal sets total dynamically.
|
||||
// If total is less than or equal to zero it takes progress' current value.
|
||||
func (b *Bar) SetTotal(total int64, triggerComplete bool) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
s.triggerComplete = triggerComplete
|
||||
if total <= 0 {
|
||||
s.total = s.current
|
||||
} else {
|
||||
s.total = total
|
||||
}
|
||||
if s.triggerComplete && !s.completed {
|
||||
s.current = s.total
|
||||
s.completed = true
|
||||
go b.refreshTillShutdown()
|
||||
}
|
||||
}:
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
// SetCurrent sets progress' current to an arbitrary value.
|
||||
// Setting a negative value will cause a panic.
|
||||
func (b *Bar) SetCurrent(current int64) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
s.iterated = true
|
||||
s.lastN = current - s.current
|
||||
s.current = current
|
||||
if s.triggerComplete && s.current >= s.total {
|
||||
s.current = s.total
|
||||
s.completed = true
|
||||
go b.refreshTillShutdown()
|
||||
}
|
||||
}:
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
// Increment is a shorthand for b.IncrInt64(1).
|
||||
func (b *Bar) Increment() {
|
||||
b.IncrInt64(1)
|
||||
}
|
||||
|
||||
// IncrBy is a shorthand for b.IncrInt64(int64(n)).
|
||||
func (b *Bar) IncrBy(n int) {
|
||||
b.IncrInt64(int64(n))
|
||||
}
|
||||
|
||||
// IncrInt64 increments progress by amount of n.
|
||||
func (b *Bar) IncrInt64(n int64) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
s.iterated = true
|
||||
s.lastN = n
|
||||
s.current += n
|
||||
if s.triggerComplete && s.current >= s.total {
|
||||
s.current = s.total
|
||||
s.completed = true
|
||||
go b.refreshTillShutdown()
|
||||
}
|
||||
}:
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
// DecoratorEwmaUpdate updates all EWMA based decorators. Should be
|
||||
// called on each iteration, because EWMA's unit of measure is an
|
||||
// iteration's duration. Panics if called before *Bar.Incr... family
|
||||
// methods.
|
||||
func (b *Bar) DecoratorEwmaUpdate(dur time.Duration) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
ewmaIterationUpdate(false, s, dur)
|
||||
}:
|
||||
case <-b.done:
|
||||
ewmaIterationUpdate(true, b.cacheState, dur)
|
||||
}
|
||||
}
|
||||
|
||||
// DecoratorAverageAdjust adjusts all average based decorators. Call
|
||||
// if you need to adjust start time of all average based decorators
|
||||
// or after progress resume.
|
||||
func (b *Bar) DecoratorAverageAdjust(start time.Time) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
for _, d := range s.averageDecorators {
|
||||
d.AverageAdjust(start)
|
||||
}
|
||||
}:
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
// SetPriority changes bar's order among multiple bars. Zero is highest
|
||||
// priority, i.e. bar will be on top. If you don't need to set priority
|
||||
// dynamically, better use BarPriority option.
|
||||
func (b *Bar) SetPriority(priority int) {
|
||||
select {
|
||||
case <-b.done:
|
||||
default:
|
||||
b.container.setBarPriority(b, priority)
|
||||
}
|
||||
}
|
||||
|
||||
// Abort interrupts bar's running goroutine. Call this, if you'd like
|
||||
// to stop/remove bar before completion event. It has no effect after
|
||||
// completion event. If drop is true bar will be removed as well.
|
||||
func (b *Bar) Abort(drop bool) {
|
||||
select {
|
||||
case <-b.done:
|
||||
default:
|
||||
if drop {
|
||||
b.container.dropBar(b)
|
||||
}
|
||||
b.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// Completed reports whether the bar is in completed state.
|
||||
func (b *Bar) Completed() bool {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) { b.completed <- s.completed }:
|
||||
return <-b.completed
|
||||
case <-b.done:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) serve(ctx context.Context, s *bState) {
|
||||
defer b.container.bwg.Done()
|
||||
for {
|
||||
select {
|
||||
case op := <-b.operateState:
|
||||
op(s)
|
||||
case <-ctx.Done():
|
||||
b.cacheState = s
|
||||
close(b.done)
|
||||
// Notifying decorators about shutdown event
|
||||
for _, sl := range s.shutdownListeners {
|
||||
sl.Shutdown()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) render(tw int) {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
stat := newStatistics(tw, s)
|
||||
defer func() {
|
||||
// recovering if user defined decorator panics for example
|
||||
if p := recover(); p != nil {
|
||||
if b.recoveredPanic == nil {
|
||||
s.extender = makePanicExtender(p)
|
||||
b.toShutdown = !b.toShutdown
|
||||
b.recoveredPanic = p
|
||||
}
|
||||
frame, lines := s.extender(nil, s.reqWidth, stat)
|
||||
b.extendedLines = lines
|
||||
b.frameCh <- frame
|
||||
b.dlogger.Println(p)
|
||||
}
|
||||
s.completeFlushed = s.completed
|
||||
}()
|
||||
frame, lines := s.extender(s.draw(stat), s.reqWidth, stat)
|
||||
b.extendedLines = lines
|
||||
b.toShutdown = s.completed && !s.completeFlushed
|
||||
b.frameCh <- frame
|
||||
}:
|
||||
case <-b.done:
|
||||
s := b.cacheState
|
||||
stat := newStatistics(tw, s)
|
||||
var r io.Reader
|
||||
if b.recoveredPanic == nil {
|
||||
r = s.draw(stat)
|
||||
}
|
||||
frame, lines := s.extender(r, s.reqWidth, stat)
|
||||
b.extendedLines = lines
|
||||
b.frameCh <- frame
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) subscribeDecorators() {
|
||||
var averageDecorators []decor.AverageDecorator
|
||||
var ewmaDecorators []decor.EwmaDecorator
|
||||
var shutdownListeners []decor.ShutdownListener
|
||||
b.TraverseDecorators(func(d decor.Decorator) {
|
||||
if d, ok := d.(decor.AverageDecorator); ok {
|
||||
averageDecorators = append(averageDecorators, d)
|
||||
}
|
||||
if d, ok := d.(decor.EwmaDecorator); ok {
|
||||
ewmaDecorators = append(ewmaDecorators, d)
|
||||
}
|
||||
if d, ok := d.(decor.ShutdownListener); ok {
|
||||
shutdownListeners = append(shutdownListeners, d)
|
||||
}
|
||||
})
|
||||
select {
|
||||
case b.operateState <- func(s *bState) {
|
||||
s.averageDecorators = averageDecorators
|
||||
s.ewmaDecorators = ewmaDecorators
|
||||
s.shutdownListeners = shutdownListeners
|
||||
}:
|
||||
b.hasEwmaDecorators = len(ewmaDecorators) != 0
|
||||
case <-b.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) refreshTillShutdown() {
|
||||
for {
|
||||
select {
|
||||
case b.container.refreshCh <- time.Now():
|
||||
case <-b.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bar) wSyncTable() [][]chan int {
|
||||
select {
|
||||
case b.operateState <- func(s *bState) { b.syncTableCh <- s.wSyncTable() }:
|
||||
return <-b.syncTableCh
|
||||
case <-b.done:
|
||||
return b.cacheState.wSyncTable()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *bState) draw(stat decor.Statistics) io.Reader {
|
||||
if !s.trimSpace {
|
||||
stat.AvailableWidth -= 2
|
||||
s.bufB.WriteByte(' ')
|
||||
defer s.bufB.WriteByte(' ')
|
||||
}
|
||||
|
||||
nlr := strings.NewReader("\n")
|
||||
tw := stat.AvailableWidth
|
||||
for _, d := range s.pDecorators {
|
||||
str := d.Decor(stat)
|
||||
stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str))
|
||||
s.bufP.WriteString(str)
|
||||
}
|
||||
if stat.AvailableWidth <= 0 {
|
||||
trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufP.String()), tw, "…"))
|
||||
s.bufP.Reset()
|
||||
return io.MultiReader(trunc, s.bufB, nlr)
|
||||
}
|
||||
|
||||
tw = stat.AvailableWidth
|
||||
for _, d := range s.aDecorators {
|
||||
str := d.Decor(stat)
|
||||
stat.AvailableWidth -= runewidth.StringWidth(stripansi.Strip(str))
|
||||
s.bufA.WriteString(str)
|
||||
}
|
||||
if stat.AvailableWidth <= 0 {
|
||||
trunc := strings.NewReader(runewidth.Truncate(stripansi.Strip(s.bufA.String()), tw, "…"))
|
||||
s.bufA.Reset()
|
||||
return io.MultiReader(s.bufP, s.bufB, trunc, nlr)
|
||||
}
|
||||
|
||||
s.filler.Fill(s.bufB, s.reqWidth, stat)
|
||||
|
||||
return io.MultiReader(s.bufP, s.bufB, s.bufA, nlr)
|
||||
}
|
||||
|
||||
func (s *bState) wSyncTable() [][]chan int {
|
||||
columns := make([]chan int, 0, len(s.pDecorators)+len(s.aDecorators))
|
||||
var pCount int
|
||||
for _, d := range s.pDecorators {
|
||||
if ch, ok := d.Sync(); ok {
|
||||
columns = append(columns, ch)
|
||||
pCount++
|
||||
}
|
||||
}
|
||||
var aCount int
|
||||
for _, d := range s.aDecorators {
|
||||
if ch, ok := d.Sync(); ok {
|
||||
columns = append(columns, ch)
|
||||
aCount++
|
||||
}
|
||||
}
|
||||
table := make([][]chan int, 2)
|
||||
table[0] = columns[0:pCount]
|
||||
table[1] = columns[pCount : pCount+aCount : pCount+aCount]
|
||||
return table
|
||||
}
|
||||
|
||||
func newStatistics(tw int, s *bState) decor.Statistics {
|
||||
return decor.Statistics{
|
||||
ID: s.id,
|
||||
AvailableWidth: tw,
|
||||
Total: s.total,
|
||||
Current: s.current,
|
||||
Refill: s.refill,
|
||||
Completed: s.completeFlushed,
|
||||
}
|
||||
}
|
||||
|
||||
func extractBaseDecorator(d decor.Decorator) decor.Decorator {
|
||||
if d, ok := d.(decor.Wrapper); ok {
|
||||
return extractBaseDecorator(d.Base())
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func ewmaIterationUpdate(done bool, s *bState, dur time.Duration) {
|
||||
if !done && !s.iterated {
|
||||
panic("increment required before ewma iteration update")
|
||||
} else {
|
||||
s.iterated = false
|
||||
}
|
||||
for _, d := range s.ewmaDecorators {
|
||||
d.EwmaUpdate(s.lastN, dur)
|
||||
}
|
||||
}
|
||||
|
||||
func makePanicExtender(p interface{}) extenderFunc {
|
||||
pstr := fmt.Sprint(p)
|
||||
stack := debug.Stack()
|
||||
stackLines := bytes.Count(stack, []byte("\n"))
|
||||
return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) {
|
||||
mr := io.MultiReader(
|
||||
strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")),
|
||||
strings.NewReader(fmt.Sprintf("\n%#v\n", st)),
|
||||
bytes.NewReader(stack),
|
||||
)
|
||||
return mr, stackLines + 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package mpb
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
)
|
||||
|
||||
// BarFiller interface.
|
||||
// Bar (without decorators) renders itself by calling BarFiller's Fill method.
|
||||
//
|
||||
// reqWidth is requested width, set by `func WithWidth(int) ContainerOption`.
|
||||
// If not set, it defaults to terminal width.
|
||||
//
|
||||
// Default implementations can be obtained via:
|
||||
//
|
||||
// func NewBarFiller(style string) BarFiller
|
||||
// func NewBarFillerRev(style string) BarFiller
|
||||
// func NewBarFillerPick(style string, rev bool) BarFiller
|
||||
// func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller
|
||||
//
|
||||
type BarFiller interface {
|
||||
Fill(w io.Writer, reqWidth int, stat decor.Statistics)
|
||||
}
|
||||
|
||||
// BarFillerFunc is function type adapter to convert function into BarFiller.
|
||||
type BarFillerFunc func(w io.Writer, reqWidth int, stat decor.Statistics)
|
||||
|
||||
func (f BarFillerFunc) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
|
||||
f(w, reqWidth, stat)
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package mpb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
"github.com/vbauerster/mpb/v6/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
rLeft = iota
|
||||
rFill
|
||||
rTip
|
||||
rSpace
|
||||
rRight
|
||||
rRevTip
|
||||
rRefill
|
||||
)
|
||||
|
||||
// BarDefaultStyle is a style for rendering a progress bar.
|
||||
// It consist of 7 ordered runes:
|
||||
//
|
||||
// '1st rune' stands for left boundary rune
|
||||
//
|
||||
// '2nd rune' stands for fill rune
|
||||
//
|
||||
// '3rd rune' stands for tip rune
|
||||
//
|
||||
// '4th rune' stands for space rune
|
||||
//
|
||||
// '5th rune' stands for right boundary rune
|
||||
//
|
||||
// '6th rune' stands for reverse tip rune
|
||||
//
|
||||
// '7th rune' stands for refill rune
|
||||
//
|
||||
const BarDefaultStyle string = "[=>-]<+"
|
||||
|
||||
type barFiller struct {
|
||||
format [][]byte
|
||||
rwidth []int
|
||||
tip []byte
|
||||
refill int64
|
||||
reverse bool
|
||||
flush func(io.Writer, *space, [][]byte)
|
||||
}
|
||||
|
||||
type space struct {
|
||||
space []byte
|
||||
rwidth int
|
||||
count int
|
||||
}
|
||||
|
||||
// NewBarFiller returns a BarFiller implementation which renders a
|
||||
// progress bar in regular direction. If style is empty string,
|
||||
// BarDefaultStyle is applied. To be used with `*Progress.Add(...)
|
||||
// *Bar` method.
|
||||
func NewBarFiller(style string) BarFiller {
|
||||
return newBarFiller(style, false)
|
||||
}
|
||||
|
||||
// NewBarFillerRev returns a BarFiller implementation which renders a
|
||||
// progress bar in reverse direction. If style is empty string,
|
||||
// BarDefaultStyle is applied. To be used with `*Progress.Add(...)
|
||||
// *Bar` method.
|
||||
func NewBarFillerRev(style string) BarFiller {
|
||||
return newBarFiller(style, true)
|
||||
}
|
||||
|
||||
// NewBarFillerPick pick between regular and reverse BarFiller implementation
|
||||
// based on rev param. To be used with `*Progress.Add(...) *Bar` method.
|
||||
func NewBarFillerPick(style string, rev bool) BarFiller {
|
||||
return newBarFiller(style, rev)
|
||||
}
|
||||
|
||||
func newBarFiller(style string, rev bool) BarFiller {
|
||||
bf := &barFiller{
|
||||
format: make([][]byte, len(BarDefaultStyle)),
|
||||
rwidth: make([]int, len(BarDefaultStyle)),
|
||||
reverse: rev,
|
||||
}
|
||||
bf.parse(BarDefaultStyle)
|
||||
if style != "" && style != BarDefaultStyle {
|
||||
bf.parse(style)
|
||||
}
|
||||
return bf
|
||||
}
|
||||
|
||||
func (s *barFiller) parse(style string) {
|
||||
if !utf8.ValidString(style) {
|
||||
panic("invalid bar style")
|
||||
}
|
||||
srcFormat := make([][]byte, 0, len(BarDefaultStyle))
|
||||
srcRwidth := make([]int, 0, len(BarDefaultStyle))
|
||||
gr := uniseg.NewGraphemes(style)
|
||||
for gr.Next() {
|
||||
srcFormat = append(srcFormat, gr.Bytes())
|
||||
srcRwidth = append(srcRwidth, runewidth.StringWidth(gr.Str()))
|
||||
}
|
||||
copy(s.format, srcFormat)
|
||||
copy(s.rwidth, srcRwidth)
|
||||
if s.reverse {
|
||||
s.tip = s.format[rRevTip]
|
||||
s.flush = reverseFlush
|
||||
} else {
|
||||
s.tip = s.format[rTip]
|
||||
s.flush = regularFlush
|
||||
}
|
||||
}
|
||||
|
||||
func (s *barFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
|
||||
width := internal.CheckRequestedWidth(reqWidth, stat.AvailableWidth)
|
||||
brackets := s.rwidth[rLeft] + s.rwidth[rRight]
|
||||
if width < brackets {
|
||||
return
|
||||
}
|
||||
// don't count brackets as progress
|
||||
width -= brackets
|
||||
|
||||
w.Write(s.format[rLeft])
|
||||
defer w.Write(s.format[rRight])
|
||||
|
||||
cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
|
||||
space := &space{
|
||||
space: s.format[rSpace],
|
||||
rwidth: s.rwidth[rSpace],
|
||||
count: width - cwidth,
|
||||
}
|
||||
|
||||
index, refill := 0, 0
|
||||
bb := make([][]byte, cwidth)
|
||||
|
||||
if cwidth > 0 && cwidth != width {
|
||||
bb[index] = s.tip
|
||||
cwidth -= s.rwidth[rTip]
|
||||
index++
|
||||
}
|
||||
|
||||
if stat.Refill > 0 {
|
||||
refill = int(internal.PercentageRound(stat.Total, int64(stat.Refill), width))
|
||||
if refill > cwidth {
|
||||
refill = cwidth
|
||||
}
|
||||
cwidth -= refill
|
||||
}
|
||||
|
||||
for cwidth > 0 {
|
||||
bb[index] = s.format[rFill]
|
||||
cwidth -= s.rwidth[rFill]
|
||||
index++
|
||||
}
|
||||
|
||||
for refill > 0 {
|
||||
bb[index] = s.format[rRefill]
|
||||
refill -= s.rwidth[rRefill]
|
||||
index++
|
||||
}
|
||||
|
||||
if cwidth+refill < 0 || space.rwidth > 1 {
|
||||
buf := new(bytes.Buffer)
|
||||
s.flush(buf, space, bb[:index])
|
||||
io.WriteString(w, runewidth.Truncate(buf.String(), width, "…"))
|
||||
return
|
||||
}
|
||||
|
||||
s.flush(w, space, bb)
|
||||
}
|
||||
|
||||
func regularFlush(w io.Writer, space *space, bb [][]byte) {
|
||||
for i := len(bb) - 1; i >= 0; i-- {
|
||||
w.Write(bb[i])
|
||||
}
|
||||
for space.count > 0 {
|
||||
w.Write(space.space)
|
||||
space.count -= space.rwidth
|
||||
}
|
||||
}
|
||||
|
||||
func reverseFlush(w io.Writer, space *space, bb [][]byte) {
|
||||
for space.count > 0 {
|
||||
w.Write(space.space)
|
||||
space.count -= space.rwidth
|
||||
}
|
||||
for i := 0; i < len(bb); i++ {
|
||||
w.Write(bb[i])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package mpb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
"github.com/vbauerster/mpb/v6/internal"
|
||||
)
|
||||
|
||||
// SpinnerAlignment enum.
|
||||
type SpinnerAlignment int
|
||||
|
||||
// SpinnerAlignment kinds.
|
||||
const (
|
||||
SpinnerOnLeft SpinnerAlignment = iota
|
||||
SpinnerOnMiddle
|
||||
SpinnerOnRight
|
||||
)
|
||||
|
||||
// SpinnerDefaultStyle is a style for rendering a spinner.
|
||||
var SpinnerDefaultStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
||||
|
||||
type spinnerFiller struct {
|
||||
frames []string
|
||||
count uint
|
||||
alignment SpinnerAlignment
|
||||
}
|
||||
|
||||
// NewSpinnerFiller returns a BarFiller implementation which renders
|
||||
// a spinner. If style is nil or zero length, SpinnerDefaultStyle is
|
||||
// applied. To be used with `*Progress.Add(...) *Bar` method.
|
||||
func NewSpinnerFiller(style []string, alignment SpinnerAlignment) BarFiller {
|
||||
if len(style) == 0 {
|
||||
style = SpinnerDefaultStyle
|
||||
}
|
||||
filler := &spinnerFiller{
|
||||
frames: style,
|
||||
alignment: alignment,
|
||||
}
|
||||
return filler
|
||||
}
|
||||
|
||||
func (s *spinnerFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
|
||||
width := internal.CheckRequestedWidth(reqWidth, stat.AvailableWidth)
|
||||
|
||||
frame := s.frames[s.count%uint(len(s.frames))]
|
||||
frameWidth := runewidth.StringWidth(frame)
|
||||
|
||||
if width < frameWidth {
|
||||
return
|
||||
}
|
||||
|
||||
switch rest := width - frameWidth; s.alignment {
|
||||
case SpinnerOnLeft:
|
||||
io.WriteString(w, frame+strings.Repeat(" ", rest))
|
||||
case SpinnerOnMiddle:
|
||||
str := strings.Repeat(" ", rest/2) + frame + strings.Repeat(" ", rest/2+rest%2)
|
||||
io.WriteString(w, str)
|
||||
case SpinnerOnRight:
|
||||
io.WriteString(w, strings.Repeat(" ", rest)+frame)
|
||||
}
|
||||
s.count++
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package mpb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/vbauerster/mpb/v6/decor"
|
||||
"github.com/vbauerster/mpb/v6/internal"
|
||||
)
|
||||
|
||||
// BarOption is a func option to alter default behavior of a bar.
|
||||
type BarOption func(*bState)
|
||||
|
||||
func (s *bState) addDecorators(dest *[]decor.Decorator, decorators ...decor.Decorator) {
|
||||
type mergeWrapper interface {
|
||||
MergeUnwrap() []decor.Decorator
|
||||
}
|
||||
for _, decorator := range decorators {
|
||||
if mw, ok := decorator.(mergeWrapper); ok {
|
||||
*dest = append(*dest, mw.MergeUnwrap()...)
|
||||
}
|
||||
*dest = append(*dest, decorator)
|
||||
}
|
||||
}
|
||||
|
||||
// AppendDecorators let you inject decorators to the bar's right side.
|
||||
func AppendDecorators(decorators ...decor.Decorator) BarOption {
|
||||
return func(s *bState) {
|
||||
s.addDecorators(&s.aDecorators, decorators...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrependDecorators let you inject decorators to the bar's left side.
|
||||
func PrependDecorators(decorators ...decor.Decorator) BarOption {
|
||||
return func(s *bState) {
|
||||
s.addDecorators(&s.pDecorators, decorators...)
|
||||
}
|
||||
}
|
||||
|
||||
// BarID sets bar id.
|
||||
func BarID(id int) BarOption {
|
||||
return func(s *bState) {
|
||||
s.id = id
|
||||
}
|
||||
}
|
||||
|
||||
// BarWidth sets bar width independent of the container.
|
||||
func BarWidth(width int) BarOption {
|
||||
return func(s *bState) {
|
||||
s.reqWidth = width
|
||||
}
|
||||
}
|
||||
|
||||
// BarQueueAfter queues this (being constructed) bar to relplace
|
||||
// runningBar after it has been completed.
|
||||
func BarQueueAfter(runningBar *Bar) BarOption {
|
||||
if runningBar == nil {
|
||||
return nil
|
||||
}
|
||||
return func(s *bState) {
|
||||
s.runningBar = runningBar
|
||||
}
|
||||
}
|
||||
|
||||
// BarRemoveOnComplete removes both bar's filler and its decorators
|
||||
// on complete event.
|
||||
func BarRemoveOnComplete() BarOption {
|
||||
return func(s *bState) {
|
||||
s.dropOnComplete = true
|
||||
}
|
||||
}
|
||||
|
||||
// BarFillerClearOnComplete clears bar's filler on complete event.
|
||||
// It's shortcut for BarFillerOnComplete("").
|
||||
func BarFillerClearOnComplete() BarOption {
|
||||
return BarFillerOnComplete("")
|
||||
}
|
||||
|
||||
// BarFillerOnComplete replaces bar's filler with message, on complete event.
|
||||
func BarFillerOnComplete(message string) BarOption {
|
||||
return BarFillerMiddleware(func(base BarFiller) BarFiller {
|
||||
return BarFillerFunc(func(w io.Writer, reqWidth int, st decor.Statistics) {
|
||||
if st.Completed {
|
||||
io.WriteString(w, message)
|
||||
} else {
|
||||
base.Fill(w, reqWidth, st)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// BarFillerMiddleware provides a way to augment the underlying BarFiller.
|
||||
func BarFillerMiddleware(middle func(BarFiller) BarFiller) BarOption {
|
||||
return func(s *bState) {
|
||||
s.middleware = middle
|
||||
}
|
||||
}
|
||||
|
||||
// BarPriority sets bar's priority. Zero is highest priority, i.e. bar
|
||||
// will be on top. If `BarReplaceOnComplete` option is supplied, this
|
||||
// option is ignored.
|
||||
func BarPriority(priority int) BarOption {
|
||||
return func(s *bState) {
|
||||
s.priority = priority
|
||||
}
|
||||
}
|
||||
|
||||
// BarExtender provides a way to extend bar to the next new line.
|
||||
func BarExtender(filler BarFiller) BarOption {
|
||||
if filler == nil {
|
||||
return nil
|
||||
}
|
||||
return func(s *bState) {
|
||||
s.extender = makeExtenderFunc(filler)
|
||||
}
|
||||
}
|
||||
|
||||
func makeExtenderFunc(filler BarFiller) extenderFunc {
|
||||
buf := new(bytes.Buffer)
|
||||
return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) {
|
||||
filler.Fill(buf, reqWidth, st)
|
||||
return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// BarFillerTrim removes leading and trailing space around the underlying BarFiller.
|
||||
func BarFillerTrim() BarOption {
|
||||
return func(s *bState) {
|
||||
s.trimSpace = true
|
||||
}
|
||||
}
|
||||
|
||||
// BarNoPop disables bar pop out of container. Effective when
|
||||
// PopCompletedMode of container is enabled.
|
||||
func BarNoPop() BarOption {
|
||||
return func(s *bState) {
|
||||
s.noPop = true
|
||||
}
|
||||
}
|
||||
|
||||
// BarOptional will invoke provided option only when pick is true.
|
||||
func BarOptional(option BarOption, pick bool) BarOption {
|
||||
return BarOptOn(option, internal.Predicate(pick))
|
||||
}
|
||||
|
||||
// BarOptOn will invoke provided option only when higher order predicate
|
||||
// evaluates to true.
|
||||
func BarOptOn(option BarOption, predicate func() bool) BarOption {
|
||||
if predicate() {
|
||||
return option
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package mpb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vbauerster/mpb/v6/internal"
|
||||
)
|
||||
|
||||
// ContainerOption is a func option to alter default behavior of a bar
|
||||
// container. Container term refers to a Progress struct which can
|
||||
// hold one or more Bars.
|
||||
type ContainerOption func(*pState)
|
||||
|
||||
// WithWaitGroup provides means to have a single joint point. If
|
||||
// *sync.WaitGroup is provided, you can safely call just p.Wait()
|
||||
// without calling Wait() on provided *sync.WaitGroup. Makes sense
|
||||
// when there are more than one bar to render.
|
||||
func WithWaitGroup(wg *sync.WaitGroup) ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.uwg = wg
|
||||
}
|
||||
}
|
||||
|
||||
// WithWidth sets container width. If not set it defaults to terminal
|
||||
// width. A bar added to the container will inherit its width, unless
|
||||
// overridden by `func BarWidth(int) BarOption`.
|
||||
func WithWidth(width int) ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.reqWidth = width
|
||||
}
|
||||
}
|
||||
|
||||
// WithRefreshRate overrides default 120ms refresh rate.
|
||||
func WithRefreshRate(d time.Duration) ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.rr = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithManualRefresh disables internal auto refresh time.Ticker.
|
||||
// Refresh will occur upon receive value from provided ch.
|
||||
func WithManualRefresh(ch <-chan interface{}) ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.externalRefresh = ch
|
||||
}
|
||||
}
|
||||
|
||||
// WithRenderDelay delays rendering. By default rendering starts as
|
||||
// soon as bar is added, with this option it's possible to delay
|
||||
// rendering process by keeping provided chan unclosed. In other words
|
||||
// rendering will start as soon as provided chan is closed.
|
||||
func WithRenderDelay(ch <-chan struct{}) ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.renderDelay = ch
|
||||
}
|
||||
}
|
||||
|
||||
// WithShutdownNotifier provided chanel will be closed, after all bars
|
||||
// have been rendered.
|
||||
func WithShutdownNotifier(ch chan struct{}) ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.shutdownNotifier = ch
|
||||
}
|
||||
}
|
||||
|
||||
// WithOutput overrides default os.Stdout output. Setting it to nil
|
||||
// will effectively disable auto refresh rate and discard any output,
|
||||
// useful if you want to disable progress bars with little overhead.
|
||||
func WithOutput(w io.Writer) ContainerOption {
|
||||
return func(s *pState) {
|
||||
if w == nil {
|
||||
s.output = ioutil.Discard
|
||||
s.outputDiscarded = true
|
||||
return
|
||||
}
|
||||
s.output = w
|
||||
}
|
||||
}
|
||||
|
||||
// WithDebugOutput sets debug output.
|
||||
func WithDebugOutput(w io.Writer) ContainerOption {
|
||||
if w == nil {
|
||||
return nil
|
||||
}
|
||||
return func(s *pState) {
|
||||
s.debugOut = w
|
||||
}
|
||||
}
|
||||
|
||||
// PopCompletedMode will pop and stop rendering completed bars.
|
||||
func PopCompletedMode() ContainerOption {
|
||||
return func(s *pState) {
|
||||
s.popCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerOptional will invoke provided option only when pick is true.
|
||||
func ContainerOptional(option ContainerOption, pick bool) ContainerOption {
|
||||
return ContainerOptOn(option, internal.Predicate(pick))
|
||||
}
|
||||
|
||||
// ContainerOptOn will invoke provided option only when higher order
|
||||
// predicate evaluates to true.
|
||||
func ContainerOptOn(option ContainerOption, predicate func() bool) ContainerOption {
|
||||
if predicate() {
|
||||
return option
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
// Package cwriter is a console writer abstraction for the underlying OS.
|
||||
package cwriter
|
|
@ -0,0 +1,7 @@
|
|||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package cwriter
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
|
@ -0,0 +1,7 @@
|
|||
// +build aix linux
|
||||
|
||||
package cwriter
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
|
@ -0,0 +1,7 @@
|
|||
// +build solaris
|
||||
|
||||
package cwriter
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETA
|
|
@ -0,0 +1,84 @@
|
|||
package cwriter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ErrNotTTY not a TeleTYpewriter error.
|
||||
var ErrNotTTY = errors.New("not a terminal")
|
||||
|
||||
// http://ascii-table.com/ansi-escape-sequences.php
|
||||
const (
|
||||
escOpen = "\x1b["
|
||||
cuuAndEd = "A\x1b[J"
|
||||
)
|
||||
|
||||
// Writer is a buffered the writer that updates the terminal. The
|
||||
// contents of writer will be flushed when Flush is called.
|
||||
type Writer struct {
|
||||
out io.Writer
|
||||
buf bytes.Buffer
|
||||
lineCount int
|
||||
fd int
|
||||
isTerminal bool
|
||||
}
|
||||
|
||||
// New returns a new Writer with defaults.
|
||||
func New(out io.Writer) *Writer {
|
||||
w := &Writer{out: out}
|
||||
if f, ok := out.(*os.File); ok {
|
||||
w.fd = int(f.Fd())
|
||||
w.isTerminal = IsTerminal(w.fd)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Flush flushes the underlying buffer.
|
||||
func (w *Writer) Flush(lineCount int) (err error) {
|
||||
// some terminals interpret 'cursor up 0' as 'cursor up 1'
|
||||
if w.lineCount > 0 {
|
||||
err = w.clearLines()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
w.lineCount = lineCount
|
||||
_, err = w.buf.WriteTo(w.out)
|
||||
return
|
||||
}
|
||||
|
||||
// Write appends the contents of p to the underlying buffer.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
return w.buf.Write(p)
|
||||
}
|
||||
|
||||
// WriteString writes string to the underlying buffer.
|
||||
func (w *Writer) WriteString(s string) (n int, err error) {
|
||||
return w.buf.WriteString(s)
|
||||
}
|
||||
|
||||
// ReadFrom reads from the provided io.Reader and writes to the
|
||||
// underlying buffer.
|
||||
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
return w.buf.ReadFrom(r)
|
||||
}
|
||||
|
||||
// GetWidth returns width of underlying terminal.
|
||||
func (w *Writer) GetWidth() (int, error) {
|
||||
if !w.isTerminal {
|
||||
return -1, ErrNotTTY
|
||||
}
|
||||
tw, _, err := GetSize(w.fd)
|
||||
return tw, err
|
||||
}
|
||||
|
||||
func (w *Writer) ansiCuuAndEd() (err error) {
|
||||
buf := make([]byte, 8)
|
||||
buf = strconv.AppendInt(buf[:copy(buf, escOpen)], int64(w.lineCount), 10)
|
||||
_, err = w.out.Write(append(buf, cuuAndEd...))
|
||||
return
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// +build !windows
|
||||
|
||||
package cwriter
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (w *Writer) clearLines() error {
|
||||
return w.ansiCuuAndEd()
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// +build windows
|
||||
|
||||
package cwriter
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
)
|
||||
|
||||
func (w *Writer) clearLines() error {
|
||||
if !w.isTerminal {
|
||||
// hope it's cygwin or similar
|
||||
return w.ansiCuuAndEd()
|
||||
}
|
||||
|
||||
var info windows.ConsoleScreenBufferInfo
|
||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(w.fd), &info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info.CursorPosition.Y -= int16(w.lineCount)
|
||||
if info.CursorPosition.Y < 0 {
|
||||
info.CursorPosition.Y = 0
|
||||
}
|
||||
_, _, _ = procSetConsoleCursorPosition.Call(
|
||||
uintptr(w.fd),
|
||||
uintptr(uint32(uint16(info.CursorPosition.Y))<<16|uint32(uint16(info.CursorPosition.X))),
|
||||
)
|
||||
|
||||
// clear the lines
|
||||
cursor := &windows.Coord{
|
||||
X: info.Window.Left,
|
||||
Y: info.CursorPosition.Y,
|
||||
}
|
||||
count := uint32(info.Size.X) * uint32(w.lineCount)
|
||||
_, _, _ = procFillConsoleOutputCharacter.Call(
|
||||
uintptr(w.fd),
|
||||
uintptr(' '),
|
||||
uintptr(count),
|
||||
*(*uintptr)(unsafe.Pointer(cursor)),
|
||||
uintptr(unsafe.Pointer(new(uint32))),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSize returns the visible dimensions of the given terminal.
|
||||
//
|
||||
// These dimensions don't include any scrollback buffer height.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
var info windows.ConsoleScreenBufferInfo
|
||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// terminal.GetSize from crypto/ssh adds "+ 1" to both width and height:
|
||||
// https://go.googlesource.com/crypto/+/refs/heads/release-branch.go1.14/ssh/terminal/util_windows.go#75
|
||||
// but looks like this is a root cause of issue #66, so removing both "+ 1" have fixed it.
|
||||
return int(info.Window.Right - info.Window.Left), int(info.Window.Bottom - info.Window.Top), nil
|
||||
}
|
||||
|
||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
var st uint32
|
||||
err := windows.GetConsoleMode(windows.Handle(fd), &st)
|
||||
return err == nil
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package decor
|
||||
|
||||
// Any decorator displays text, that can be changed during decorator's
|
||||
// lifetime via provided DecorFunc.
|
||||
//
|
||||
// `fn` DecorFunc callback
|
||||
//
|
||||
// `wcc` optional WC config
|
||||
//
|
||||
func Any(fn DecorFunc, wcc ...WC) Decorator {
|
||||
return &any{initWC(wcc...), fn}
|
||||
}
|
||||
|
||||
type any struct {
|
||||
WC
|
||||
fn DecorFunc
|
||||
}
|
||||
|
||||
func (d *any) Decor(s Statistics) string {
|
||||
return d.FormatMsg(d.fn(s))
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package decor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
UnitKiB
|
||||
UnitKB
|
||||
)
|
||||
|
||||
// CountersNoUnit is a wrapper around Counters with no unit param.
|
||||
func CountersNoUnit(pairFmt string, wcc ...WC) Decorator {
|
||||
return Counters(0, pairFmt, wcc...)
|
||||
}
|
||||
|
||||
// CountersKibiByte is a wrapper around Counters with predefined unit
|
||||
// UnitKiB (bytes/1024).
|
||||
func CountersKibiByte(pairFmt string, wcc ...WC) Decorator {
|
||||
return Counters(UnitKiB, pairFmt, wcc...)
|
||||
}
|
||||
|
||||
// CountersKiloByte is a wrapper around Counters with predefined unit
|
||||
// UnitKB (bytes/1000).
|
||||
func CountersKiloByte(pairFmt string, wcc ...WC) Decorator {
|
||||
return Counters(UnitKB, pairFmt, wcc...)
|
||||
}
|
||||
|
||||
// Counters decorator with dynamic unit measure adjustment.
|
||||
//
|
||||
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
|
||||
//
|
||||
// `pairFmt` printf compatible verbs for current and total pair
|
||||
//
|
||||
// `wcc` optional WC config
|
||||
//
|
||||
// pairFmt example if unit=UnitKB:
|
||||
//
|
||||
// pairFmt="%.1f / %.1f" output: "1.0MB / 12.0MB"
|
||||
// pairFmt="% .1f / % .1f" output: "1.0 MB / 12.0 MB"
|
||||
// pairFmt="%d / %d" output: "1MB / 12MB"
|
||||
// pairFmt="% d / % d" output: "1 MB / 12 MB"
|
||||
//
|
||||
func Counters(unit int, pairFmt string, wcc ...WC) Decorator {
|
||||
producer := func(unit int, pairFmt string) DecorFunc {
|
||||
if pairFmt == "" {
|
||||
pairFmt = "%d / %d"
|
||||
} else if strings.Count(pairFmt, "%") != 2 {
|
||||
panic("expected pairFmt with exactly 2 verbs")
|
||||
}
|
||||
switch unit {
|
||||
case UnitKiB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(pairFmt, SizeB1024(s.Current), SizeB1024(s.Total))
|
||||
}
|
||||
case UnitKB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(pairFmt, SizeB1000(s.Current), SizeB1000(s.Total))
|
||||
}
|
||||
default:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(pairFmt, s.Current, s.Total)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Any(producer(unit, pairFmt), wcc...)
|
||||
}
|
||||
|
||||
// TotalNoUnit is a wrapper around Total with no unit param.
|
||||
func TotalNoUnit(format string, wcc ...WC) Decorator {
|
||||
return Total(0, format, wcc...)
|
||||
}
|
||||
|
||||
// TotalKibiByte is a wrapper around Total with predefined unit
|
||||
// UnitKiB (bytes/1024).
|
||||
func TotalKibiByte(format string, wcc ...WC) Decorator {
|
||||
return Total(UnitKiB, format, wcc...)
|
||||
}
|
||||
|
||||
// TotalKiloByte is a wrapper around Total with predefined unit
|
||||
// UnitKB (bytes/1000).
|
||||
func TotalKiloByte(format string, wcc ...WC) Decorator {
|
||||
return Total(UnitKB, format, wcc...)
|
||||
}
|
||||
|
||||
// Total decorator with dynamic unit measure adjustment.
|
||||
//
|
||||
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
|
||||
//
|
||||
// `format` printf compatible verb for Total
|
||||
//
|
||||
// `wcc` optional WC config
|
||||
//
|
||||
// format example if unit=UnitKiB:
|
||||
//
|
||||
// format="%.1f" output: "12.0MiB"
|
||||
// format="% .1f" output: "12.0 MiB"
|
||||
// format="%d" output: "12MiB"
|
||||
// format="% d" output: "12 MiB"
|
||||
//
|
||||
func Total(unit int, format string, wcc ...WC) Decorator {
|
||||
producer := func(unit int, format string) DecorFunc {
|
||||
if format == "" {
|
||||
format = "%d"
|
||||
} else if strings.Count(format, "%") != 1 {
|
||||
panic("expected format with exactly 1 verb")
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case UnitKiB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, SizeB1024(s.Total))
|
||||
}
|
||||
case UnitKB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, SizeB1000(s.Total))
|
||||
}
|
||||
default:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, s.Total)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Any(producer(unit, format), wcc...)
|
||||
}
|
||||
|
||||
// CurrentNoUnit is a wrapper around Current with no unit param.
|
||||
func CurrentNoUnit(format string, wcc ...WC) Decorator {
|
||||
return Current(0, format, wcc...)
|
||||
}
|
||||
|
||||
// CurrentKibiByte is a wrapper around Current with predefined unit
|
||||
// UnitKiB (bytes/1024).
|
||||
func CurrentKibiByte(format string, wcc ...WC) Decorator {
|
||||
return Current(UnitKiB, format, wcc...)
|
||||
}
|
||||
|
||||
// CurrentKiloByte is a wrapper around Current with predefined unit
|
||||
// UnitKB (bytes/1000).
|
||||
func CurrentKiloByte(format string, wcc ...WC) Decorator {
|
||||
return Current(UnitKB, format, wcc...)
|
||||
}
|
||||
|
||||
// Current decorator with dynamic unit measure adjustment.
|
||||
//
|
||||
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
|
||||
//
|
||||
// `format` printf compatible verb for Current
|
||||
//
|
||||
// `wcc` optional WC config
|
||||
//
|
||||
// format example if unit=UnitKiB:
|
||||
//
|
||||
// format="%.1f" output: "12.0MiB"
|
||||
// format="% .1f" output: "12.0 MiB"
|
||||
// format="%d" output: "12MiB"
|
||||
// format="% d" output: "12 MiB"
|
||||
//
|
||||
func Current(unit int, format string, wcc ...WC) Decorator {
|
||||
producer := func(unit int, format string) DecorFunc {
|
||||
if format == "" {
|
||||
format = "%d"
|
||||
} else if strings.Count(format, "%") != 1 {
|
||||
panic("expected format with exactly 1 verb")
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case UnitKiB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, SizeB1024(s.Current))
|
||||
}
|
||||
case UnitKB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, SizeB1000(s.Current))
|
||||
}
|
||||
default:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, s.Current)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Any(producer(unit, format), wcc...)
|
||||
}
|
||||
|
||||
// InvertedCurrentNoUnit is a wrapper around InvertedCurrent with no unit param.
|
||||
func InvertedCurrentNoUnit(format string, wcc ...WC) Decorator {
|
||||
return InvertedCurrent(0, format, wcc...)
|
||||
}
|
||||
|
||||
// InvertedCurrentKibiByte is a wrapper around InvertedCurrent with predefined unit
|
||||
// UnitKiB (bytes/1024).
|
||||
func InvertedCurrentKibiByte(format string, wcc ...WC) Decorator {
|
||||
return InvertedCurrent(UnitKiB, format, wcc...)
|
||||
}
|
||||
|
||||
// InvertedCurrentKiloByte is a wrapper around InvertedCurrent with predefined unit
|
||||
// UnitKB (bytes/1000).
|
||||
func InvertedCurrentKiloByte(format string, wcc ...WC) Decorator {
|
||||
return InvertedCurrent(UnitKB, format, wcc...)
|
||||
}
|
||||
|
||||
// InvertedCurrent decorator with dynamic unit measure adjustment.
|
||||
//
|
||||
// `unit` one of [0|UnitKiB|UnitKB] zero for no unit
|
||||
//
|
||||
// `format` printf compatible verb for InvertedCurrent
|
||||
//
|
||||
// `wcc` optional WC config
|
||||
//
|
||||
// format example if unit=UnitKiB:
|
||||
//
|
||||
// format="%.1f" output: "12.0MiB"
|
||||
// format="% .1f" output: "12.0 MiB"
|
||||
// format="%d" output: "12MiB"
|
||||
// format="% d" output: "12 MiB"
|
||||
//
|
||||
func InvertedCurrent(unit int, format string, wcc ...WC) Decorator {
|
||||
producer := func(unit int, format string) DecorFunc {
|
||||
if format == "" {
|
||||
format = "%d"
|
||||
} else if strings.Count(format, "%") != 1 {
|
||||
panic("expected format with exactly 1 verb")
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case UnitKiB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, SizeB1024(s.Total-s.Current))
|
||||
}
|
||||
case UnitKB:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, SizeB1000(s.Total-s.Current))
|
||||
}
|
||||
default:
|
||||
return func(s Statistics) string {
|
||||
return fmt.Sprintf(format, s.Total-s.Current)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Any(producer(unit, format), wcc...)
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package decor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
const (
|
||||
// DidentRight bit specifies identation direction.
|
||||
// |foo |b | With DidentRight
|
||||
// | foo| b| Without DidentRight
|
||||
DidentRight = 1 << iota
|
||||
|
||||
// DextraSpace bit adds extra space, makes sense with DSyncWidth only.
|
||||
// When DidentRight bit set, the space will be added to the right,
|
||||
// otherwise to the left.
|
||||
DextraSpace
|
||||
|
||||
// DSyncWidth bit enables same column width synchronization.
|
||||
// Effective with multiple bars only.
|
||||
DSyncWidth
|
||||
|
||||
// DSyncWidthR is shortcut for DSyncWidth|DidentRight
|
||||
DSyncWidthR = DSyncWidth | DidentRight
|
||||
|
||||
// DSyncSpace is shortcut for DSyncWidth|DextraSpace
|
||||
DSyncSpace = DSyncWidth | DextraSpace
|
||||
|
||||
// DSyncSpaceR is shortcut for DSyncWidth|DextraSpace|DidentRight
|
||||
DSyncSpaceR = DSyncWidth | DextraSpace | DidentRight
|
||||
)
|
||||
|
||||
// TimeStyle enum.
|
||||
type TimeStyle int
|
||||
|
||||
// TimeStyle kinds.
|
||||
const (
|
||||
ET_STYLE_GO TimeStyle = iota
|
||||
ET_STYLE_HHMMSS
|
||||
ET_STYLE_HHMM
|
||||
ET_STYLE_MMSS
|
||||
)
|
||||
|
||||
// Statistics consists of progress related statistics, that Decorator
|
||||
// may need.
|
||||
type Statistics struct {
|
||||
ID int
|
||||
AvailableWidth int
|
||||
Total int64
|
||||
Current int64
|
||||
Refill int64
|
||||
Completed bool
|
||||
}
|
||||
|
||||
// Decorator interface.
|
||||
// Most of the time there is no need to implement this interface
|
||||
// manually, as decor package already provides a wide range of decorators
|
||||
// which implement this interface. If however built-in decorators don't
|
||||
// meet your needs, you're free to implement your own one by implementing
|
||||
// this particular interface. The easy way to go is to convert a
|
||||
// `DecorFunc` into a `Decorator` interface by using provided
|
||||
// `func Any(DecorFunc, ...WC) Decorator`.
|
||||
type Decorator interface {
|
||||
Configurator
|
||||
Synchronizer
|
||||
Decor(Statistics) string
|
||||
}
|
||||
|
||||
// DecorFunc func type.
|
||||
// To be used with `func Any`(DecorFunc, ...WC) Decorator`.
|
||||
type DecorFunc func(Statistics) string
|
||||
|
||||
// Synchronizer interface.
|
||||
// All decorators implement this interface implicitly. Its Sync
|
||||
// method exposes width sync channel, if DSyncWidth bit is set.
|
||||
type Synchronizer interface {
|
||||
Sync() (chan int, bool)
|
||||
}
|
||||
|
||||
// Configurator interface.
|
||||
type Configurator interface {
|
||||
GetConf() WC
|
||||
SetConf(WC)
|
||||
}
|
||||
|
||||
// Wrapper interface.
|
||||
// If you're implementing custom Decorator by wrapping a built-in one,
|
||||
// it is necessary to implement this interface to retain functionality
|
||||
// of built-in Decorator.
|
||||
type Wrapper interface {
|
||||
Base() Decorator
|
||||
}
|
||||
|
||||
// EwmaDecorator interface.
|
||||
// EWMA based decorators should implement this one.
|
||||
type EwmaDecorator interface {
|
||||
EwmaUpdate(int64, time.Duration)
|
||||
}
|
||||
|
||||
// AverageDecorator interface.
|
||||
// Average decorators should implement this interface to provide start
|
||||
// time adjustment facility, for resume-able tasks.
|
||||
type AverageDecorator interface {
|
||||
AverageAdjust(time.Time)
|
||||
}
|
||||
|
||||
// ShutdownListener interface.
|
||||
// If decorator needs to be notified once upon bar shutdown event, so
|
||||
// this is the right interface to implement.
|
||||
type ShutdownListener interface {
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// Global convenience instances of WC with sync width bit set.
|
||||
// To be used with multiple bars only, i.e. not effective for single bar usage.
|
||||
var (
|
||||
WCSyncWidth = WC{C: DSyncWidth}
|
||||
WCSyncWidthR = WC{C: DSyncWidthR}
|
||||
WCSyncSpace = WC{C: DSyncSpace}
|
||||
WCSyncSpaceR = WC{C: DSyncSpaceR}
|
||||
)
|
||||
|
||||
// WC is a struct with two public fields W and C, both of int type.
|
||||
// W represents width and C represents bit set of width related config.
|
||||
// A decorator should embed WC, to enable width synchronization.
|
||||
type WC struct {
|
||||
W int
|
||||
C int
|
||||
fill func(s string, w int) string
|
||||
wsync chan int
|
||||
}
|
||||
|
||||
// FormatMsg formats final message according to WC.W and WC.C.
|
||||
// Should be called by any Decorator implementation.
|
||||
func (wc *WC) FormatMsg(msg string) string {
|
||||
pureWidth := runewidth.StringWidth(msg)
|
||||
stripWidth := runewidth.StringWidth(stripansi.Strip(msg))
|
||||
maxCell := wc.W
|
||||
if (wc.C & DSyncWidth) != 0 {
|
||||
cellCount := stripWidth
|
||||
if (wc.C & DextraSpace) != 0 {
|
||||
cellCount++
|
||||
}
|
||||
wc.wsync <- cellCount
|
||||
maxCell = <-wc.wsync
|
||||
}
|
||||
return wc.fill(msg, maxCell+(pureWidth-stripWidth))
|
||||
}
|
||||
|
||||
// Init initializes width related config.
|
||||
func (wc *WC) Init() WC {
|
||||
wc.fill = runewidth.FillLeft
|
||||
if (wc.C & DidentRight) != 0 {
|
||||
wc.fill = runewidth.FillRight
|
||||
}
|
||||
if (wc.C & DSyncWidth) != 0 {
|
||||
// it's deliberate choice to override wsync on each Init() call,
|
||||
// this way globals like WCSyncSpace can be reused
|
||||
wc.wsync = make(chan int)
|
||||
}
|
||||
return *wc
|
||||
}
|
||||
|
||||
// Sync is implementation of Synchronizer interface.
|
||||
func (wc *WC) Sync() (chan int, bool) {
|
||||
if (wc.C&DSyncWidth) != 0 && wc.wsync == nil {
|
||||
panic(fmt.Sprintf("%T is not initialized", wc))
|
||||
}
|
||||
return wc.wsync, (wc.C & DSyncWidth) != 0
|
||||
}
|
||||
|
||||
// GetConf is implementation of Configurator interface.
|
||||
func (wc *WC) GetConf() WC {
|
||||
return *wc
|
||||
}
|
||||
|
||||
// SetConf is implementation of Configurator interface.
|
||||
func (wc *WC) SetConf(conf WC) {
|
||||
*wc = conf.Init()
|
||||
}
|
||||
|
||||
func initWC(wcc ...WC) WC {
|
||||
var wc WC
|
||||
for _, nwc := range wcc {
|
||||
wc = nwc
|
||||
}
|
||||
return wc.Init()
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Package decor provides common decorators for "github.com/vbauerster/mpb/v6" module.
|
||||
/*
|
||||
Some decorators returned by this package might have a closure state. It is ok to use
|
||||
decorators concurrently, unless you share the same decorator among multiple
|
||||
*mpb.Bar instances. To avoid data races, create new decorator per *mpb.Bar instance.
|
||||
|
||||
Don't:
|
||||
|
||||
p := mpb.New()
|
||||
name := decor.Name("bar")
|
||||
p.AddBar(100, mpb.AppendDecorators(name))
|
||||
p.AddBar(100, mpb.AppendDecorators(name))
|
||||
|
||||
Do:
|
||||
|
||||
p := mpb.New()
|
||||
p.AddBar(100, mpb.AppendDecorators(decor.Name("bar1")))
|
||||
p.AddBar(100, mpb.AppendDecorators(decor.Name("bar2")))
|
||||
*/
|
||||
package decor
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue