mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-21 10:09:07 +00:00
Split cli into moby and linuxkit
moby just does the simple `build` cases, while `linuxkit` does `push` and `run`. Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
parent
4e1317d213
commit
8968335e59
@ -1,474 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
"golang.org/x/oauth2/google"
|
|
||||||
"google.golang.org/api/compute/v1"
|
|
||||||
"google.golang.org/api/googleapi"
|
|
||||||
"google.golang.org/api/storage/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const pollingInterval = 500 * time.Millisecond
|
|
||||||
const timeout = 300
|
|
||||||
|
|
||||||
// GCPClient contains state required for communication with GCP
|
|
||||||
type GCPClient struct {
|
|
||||||
client *http.Client
|
|
||||||
compute *compute.Service
|
|
||||||
storage *storage.Service
|
|
||||||
projectName string
|
|
||||||
fileName string
|
|
||||||
privKey *rsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGCPClient creates a new GCP client
|
|
||||||
func NewGCPClient(keys, projectName string) (*GCPClient, error) {
|
|
||||||
log.Debugf("Connecting to GCP")
|
|
||||||
ctx := context.Background()
|
|
||||||
var client *GCPClient
|
|
||||||
if keys != "" {
|
|
||||||
log.Debugf("Using Keys %s", keys)
|
|
||||||
f, err := os.Open(keys)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonKey, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := google.JWTConfigFromJSON(jsonKey,
|
|
||||||
storage.DevstorageReadWriteScope,
|
|
||||||
compute.ComputeScope,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client = &GCPClient{
|
|
||||||
client: config.Client(ctx),
|
|
||||||
projectName: projectName,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debugf("Using Application Default crednetials")
|
|
||||||
gc, err := google.DefaultClient(
|
|
||||||
ctx,
|
|
||||||
storage.DevstorageReadWriteScope,
|
|
||||||
compute.ComputeScope,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client = &GCPClient{
|
|
||||||
client: gc,
|
|
||||||
projectName: projectName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
client.compute, err = compute.New(client.client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client.storage, err = storage.New(client.client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Generating SSH Keypair")
|
|
||||||
client.privKey, err = rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadFile uploads a file to Google Storage
|
|
||||||
func (g GCPClient) UploadFile(src, dst, bucketName string, public bool) error {
|
|
||||||
log.Infof("Uploading file %s to Google Storage as %s", src, dst)
|
|
||||||
f, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
objectCall := g.storage.Objects.Insert(bucketName, &storage.Object{Name: dst}).Media(f)
|
|
||||||
|
|
||||||
if public {
|
|
||||||
objectCall.PredefinedAcl("publicRead")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = objectCall.Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("Upload Complete!")
|
|
||||||
fmt.Println("gs://" + bucketName + "/" + dst)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateImage creates a GCP image using the a source from Google Storage
|
|
||||||
func (g GCPClient) CreateImage(name, storageURL, family string, replace bool) error {
|
|
||||||
if replace {
|
|
||||||
if err := g.DeleteImage(name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Creating image: %s", name)
|
|
||||||
imgObj := &compute.Image{
|
|
||||||
RawDisk: &compute.ImageRawDisk{
|
|
||||||
Source: storageURL,
|
|
||||||
},
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if family != "" {
|
|
||||||
imgObj.Family = family
|
|
||||||
}
|
|
||||||
|
|
||||||
op, err := g.compute.Images.Insert(g.projectName, imgObj).Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.pollOperationStatus(op.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("Image %s created", name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteImage deletes and image
|
|
||||||
func (g GCPClient) DeleteImage(name string) error {
|
|
||||||
var notFound bool
|
|
||||||
op, err := g.compute.Images.Delete(g.projectName, name).Do()
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(*googleapi.Error); !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err.(*googleapi.Error).Code != 404 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notFound = true
|
|
||||||
}
|
|
||||||
if !notFound {
|
|
||||||
log.Infof("Deleting existing image...")
|
|
||||||
if err := g.pollOperationStatus(op.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("Image %s deleted", name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInstance creates and starts an instance on GCP
|
|
||||||
func (g GCPClient) CreateInstance(name, image, zone, machineType string, diskSize int, replace bool) error {
|
|
||||||
if replace {
|
|
||||||
if err := g.DeleteInstance(name, zone, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Creating instance %s from image %s (type: %s in %s)", name, image, machineType, zone)
|
|
||||||
|
|
||||||
enabled := new(string)
|
|
||||||
*enabled = "1"
|
|
||||||
|
|
||||||
k, err := ssh.NewPublicKey(g.privKey.Public())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sshKey := new(string)
|
|
||||||
*sshKey = fmt.Sprintf("moby:%s moby", string(ssh.MarshalAuthorizedKey(k)))
|
|
||||||
|
|
||||||
diskName := name + "-systemdisk"
|
|
||||||
diskOp, err := g.compute.Disks.Insert(g.projectName, zone, &compute.Disk{Name: diskName, SizeGb: int64(diskSize)}).Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := g.pollZoneOperationStatus(diskOp.Name, zone); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceObj := &compute.Instance{
|
|
||||||
MachineType: fmt.Sprintf("zones/%s/machineTypes/%s", zone, machineType),
|
|
||||||
Name: name,
|
|
||||||
Disks: []*compute.AttachedDisk{
|
|
||||||
{
|
|
||||||
AutoDelete: true,
|
|
||||||
Boot: true,
|
|
||||||
InitializeParams: &compute.AttachedDiskInitializeParams{
|
|
||||||
SourceImage: fmt.Sprintf("global/images/%s", image),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
AutoDelete: true,
|
|
||||||
Boot: false,
|
|
||||||
Source: fmt.Sprintf("zones/%s/disks/%s", zone, diskName),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetworkInterfaces: []*compute.NetworkInterface{
|
|
||||||
{
|
|
||||||
Network: "global/networks/default",
|
|
||||||
AccessConfigs: []*compute.AccessConfig{
|
|
||||||
{
|
|
||||||
Type: "ONE_TO_ONE_NAT",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Metadata: &compute.Metadata{
|
|
||||||
Items: []*compute.MetadataItems{
|
|
||||||
{
|
|
||||||
Key: "serial-port-enable",
|
|
||||||
Value: enabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "ssh-keys",
|
|
||||||
Value: sshKey,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't wait for operation to complete!
|
|
||||||
// A headstart is needed as by the time we've polled for this event to be
|
|
||||||
// completed, the instance may have already terminated
|
|
||||||
_, err = g.compute.Instances.Insert(g.projectName, zone, instanceObj).Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("Instance created")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteInstance removes an instance
|
|
||||||
func (g GCPClient) DeleteInstance(instance, zone string, wait bool) error {
|
|
||||||
var notFound bool
|
|
||||||
op, err := g.compute.Instances.Delete(g.projectName, zone, instance).Do()
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(*googleapi.Error); !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err.(*googleapi.Error).Code != 404 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
notFound = true
|
|
||||||
}
|
|
||||||
if !notFound && wait {
|
|
||||||
log.Infof("Deleting existing instance...")
|
|
||||||
if err := g.pollZoneOperationStatus(op.Name, zone); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("Instance %s deleted", instance)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInstanceSerialOutput streams the serial output of an instance
|
|
||||||
func (g GCPClient) GetInstanceSerialOutput(instance, zone string) error {
|
|
||||||
log.Infof("Getting serial port output for instance %s", instance)
|
|
||||||
var next int64
|
|
||||||
for {
|
|
||||||
res, err := g.compute.Instances.GetSerialPortOutput(g.projectName, zone, instance).Start(next).Do()
|
|
||||||
if err != nil {
|
|
||||||
if err.(*googleapi.Error).Code == 400 {
|
|
||||||
// Instance may not be ready yet...
|
|
||||||
time.Sleep(pollingInterval)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err.(*googleapi.Error).Code == 503 {
|
|
||||||
// Timeout received when the instance has terminated
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(res.Contents)
|
|
||||||
next = res.Next
|
|
||||||
// When the instance has been stopped, Start and Next will both be 0
|
|
||||||
if res.Start > 0 && next == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectToInstanceSerialPort uses SSH to connect to the serial port of the instance
|
|
||||||
func (g GCPClient) ConnectToInstanceSerialPort(instance, zone string) error {
|
|
||||||
log.Infof("Connecting to serial port of instance %s", instance)
|
|
||||||
gPubKeyURL := "https://cloud-certs.storage.googleapis.com/google-cloud-serialport-host-key.pub"
|
|
||||||
resp, err := http.Get(gPubKeyURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gPubKey, _, _, _, err := ssh.ParseAuthorizedKey(body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := ssh.NewSignerFromKey(g.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
User: fmt.Sprintf("%s.%s.%s.moby", g.projectName, zone, instance),
|
|
||||||
Auth: []ssh.AuthMethod{
|
|
||||||
ssh.PublicKeys(signer),
|
|
||||||
},
|
|
||||||
HostKeyCallback: ssh.FixedHostKey(gPubKey),
|
|
||||||
Timeout: 5 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
var conn *ssh.Client
|
|
||||||
// Retry connection as VM may not be ready yet
|
|
||||||
for i := 0; i < timeout; i++ {
|
|
||||||
conn, err = ssh.Dial("tcp", "ssh-serialport.googleapis.com:9600", config)
|
|
||||||
if err != nil {
|
|
||||||
time.Sleep(pollingInterval)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if conn == nil {
|
|
||||||
return fmt.Errorf(err.Error())
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
session, err := conn.NewSession()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
stdin, err := session.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to setup stdin for session: %v", err)
|
|
||||||
}
|
|
||||||
go io.Copy(stdin, os.Stdin)
|
|
||||||
|
|
||||||
stdout, err := session.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to setup stdout for session: %v", err)
|
|
||||||
}
|
|
||||||
go io.Copy(os.Stdout, stdout)
|
|
||||||
|
|
||||||
stderr, err := session.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to setup stderr for session: %v", err)
|
|
||||||
}
|
|
||||||
go io.Copy(os.Stderr, stderr)
|
|
||||||
/*
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
exit := make(chan bool, 1)
|
|
||||||
signal.Notify(c)
|
|
||||||
go func(exit <-chan bool, c <-chan os.Signal) {
|
|
||||||
select {
|
|
||||||
case <-exit:
|
|
||||||
return
|
|
||||||
case s := <-c:
|
|
||||||
switch s {
|
|
||||||
// CTRL+C
|
|
||||||
case os.Interrupt:
|
|
||||||
session.Signal(ssh.SIGINT)
|
|
||||||
// CTRL+\
|
|
||||||
case os.Kill:
|
|
||||||
session.Signal(ssh.SIGQUIT)
|
|
||||||
default:
|
|
||||||
log.Debugf("Received signal %s but not forwarding to ssh", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(exit, c)
|
|
||||||
*/
|
|
||||||
var termWidth, termHeight int
|
|
||||||
fd := os.Stdin.Fd()
|
|
||||||
|
|
||||||
if term.IsTerminal(fd) {
|
|
||||||
oldState, err := term.MakeRaw(fd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer term.RestoreTerminal(fd, oldState)
|
|
||||||
|
|
||||||
winsize, err := term.GetWinsize(fd)
|
|
||||||
if err != nil {
|
|
||||||
termWidth = 80
|
|
||||||
termHeight = 24
|
|
||||||
} else {
|
|
||||||
termWidth = int(winsize.Width)
|
|
||||||
termHeight = int(winsize.Height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = session.RequestPty("xterm", termHeight, termWidth, ssh.TerminalModes{
|
|
||||||
ssh.ECHO: 1,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = session.Shell(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.Wait()
|
|
||||||
//exit <- true
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GCPClient) pollOperationStatus(operationName string) error {
|
|
||||||
for i := 0; i < timeout; i++ {
|
|
||||||
operation, err := g.compute.GlobalOperations.Get(g.projectName, operationName).Do()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error fetching operation status: %v", err)
|
|
||||||
}
|
|
||||||
if operation.Error != nil {
|
|
||||||
return fmt.Errorf("error running operation: %v", operation.Error)
|
|
||||||
}
|
|
||||||
if operation.Status == "DONE" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(pollingInterval)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("timeout waiting for operation to finish")
|
|
||||||
|
|
||||||
}
|
|
||||||
func (g *GCPClient) pollZoneOperationStatus(operationName, zone string) error {
|
|
||||||
for i := 0; i < timeout; i++ {
|
|
||||||
operation, err := g.compute.ZoneOperations.Get(g.projectName, zone, operationName).Do()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error fetching operation status: %v", err)
|
|
||||||
}
|
|
||||||
if operation.Error != nil {
|
|
||||||
return fmt.Errorf("error running operation: %v", operation.Error)
|
|
||||||
}
|
|
||||||
if operation.Status == "DONE" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(pollingInterval)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("timeout waiting for operation to finish")
|
|
||||||
}
|
|
@ -42,8 +42,6 @@ func main() {
|
|||||||
fmt.Printf("USAGE: %s [options] COMMAND\n\n", filepath.Base(os.Args[0]))
|
fmt.Printf("USAGE: %s [options] COMMAND\n\n", filepath.Base(os.Args[0]))
|
||||||
fmt.Printf("Commands:\n")
|
fmt.Printf("Commands:\n")
|
||||||
fmt.Printf(" build Build a Moby image from a YAML file\n")
|
fmt.Printf(" build Build a Moby image from a YAML file\n")
|
||||||
fmt.Printf(" push Push a VM image to a cloud or image store\n")
|
|
||||||
fmt.Printf(" run Run a VM image on a local hypervisor or remote cloud\n")
|
|
||||||
fmt.Printf(" version Print version information\n")
|
fmt.Printf(" version Print version information\n")
|
||||||
fmt.Printf(" help Print this message\n")
|
fmt.Printf(" help Print this message\n")
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
@ -82,10 +80,6 @@ func main() {
|
|||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "build":
|
case "build":
|
||||||
build(args[1:])
|
build(args[1:])
|
||||||
case "push":
|
|
||||||
push(args[1:])
|
|
||||||
case "run":
|
|
||||||
run(args[1:])
|
|
||||||
case "version":
|
case "version":
|
||||||
version()
|
version()
|
||||||
case "help":
|
case "help":
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func pushUsage() {
|
|
||||||
fmt.Printf("USAGE: %s push [backend] [options] [prefix]\n\n", os.Args[0])
|
|
||||||
|
|
||||||
fmt.Printf("'backend' specifies the push backend.\n")
|
|
||||||
fmt.Printf("Supported backends are\n")
|
|
||||||
fmt.Printf(" gcp\n")
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Printf("'options' are the backend specific options.\n")
|
|
||||||
fmt.Printf("See 'moby push [backend] --help' for details.\n\n")
|
|
||||||
fmt.Printf("'prefix' specifies the path to the VM image.\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func push(args []string) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
runUsage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch args[0] {
|
|
||||||
case "help", "-h", "-help", "--help":
|
|
||||||
pushUsage()
|
|
||||||
os.Exit(0)
|
|
||||||
case "gcp":
|
|
||||||
pushGcp(args[1:])
|
|
||||||
default:
|
|
||||||
log.Errorf("No 'push' backend specified.")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Process the run arguments and execute run
|
|
||||||
func pushGcp(args []string) {
|
|
||||||
gcpCmd := flag.NewFlagSet("gcp", flag.ExitOnError)
|
|
||||||
gcpCmd.Usage = func() {
|
|
||||||
fmt.Printf("USAGE: %s push gcp [options] [name]\n\n", os.Args[0])
|
|
||||||
fmt.Printf("'name' specifies the full path of an image file which will be uploaded\n")
|
|
||||||
fmt.Printf("Options:\n\n")
|
|
||||||
gcpCmd.PrintDefaults()
|
|
||||||
}
|
|
||||||
keysFlag := gcpCmd.String("keys", "", "Path to Service Account JSON key file")
|
|
||||||
projectFlag := gcpCmd.String("project", "", "GCP Project Name")
|
|
||||||
bucketFlag := gcpCmd.String("bucket", "", "GS Bucket to upload to. *Required*")
|
|
||||||
publicFlag := gcpCmd.Bool("public", false, "Select if file on GS should be public. *Optional*")
|
|
||||||
familyFlag := gcpCmd.String("family", "", "GCP Image Family. A group of images where the family name points to the most recent image. *Optional*")
|
|
||||||
nameFlag := gcpCmd.String("img-name", "", "Overrides the Name used to identify the file in Google Storage and Image. Defaults to [name]")
|
|
||||||
|
|
||||||
if err := gcpCmd.Parse(args); err != nil {
|
|
||||||
log.Fatal("Unable to parse args")
|
|
||||||
}
|
|
||||||
|
|
||||||
remArgs := gcpCmd.Args()
|
|
||||||
if len(remArgs) == 0 {
|
|
||||||
fmt.Printf("Please specify the prefix to the image to push\n")
|
|
||||||
gcpCmd.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
prefix := remArgs[0]
|
|
||||||
|
|
||||||
keys := getStringValue(keysVar, *keysFlag, "")
|
|
||||||
project := getStringValue(projectVar, *projectFlag, "")
|
|
||||||
bucket := getStringValue(bucketVar, *bucketFlag, "")
|
|
||||||
public := getBoolValue(publicVar, *publicFlag)
|
|
||||||
family := getStringValue(familyVar, *familyFlag, "")
|
|
||||||
name := getStringValue(nameVar, *nameFlag, "")
|
|
||||||
|
|
||||||
client, err := NewGCPClient(keys, project)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to connect to GCP")
|
|
||||||
}
|
|
||||||
|
|
||||||
suffix := ".img.tar.gz"
|
|
||||||
src := prefix
|
|
||||||
if strings.HasSuffix(prefix, suffix) {
|
|
||||||
prefix = prefix[:len(prefix)-len(suffix)]
|
|
||||||
} else {
|
|
||||||
src = prefix + suffix
|
|
||||||
}
|
|
||||||
if name != "" {
|
|
||||||
prefix = name
|
|
||||||
}
|
|
||||||
if bucket == "" {
|
|
||||||
log.Fatalf("No bucket specified. Please provide one using the -bucket flag")
|
|
||||||
}
|
|
||||||
err = client.UploadFile(src, prefix+suffix, bucket, public)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error copying to Google Storage: %v", err)
|
|
||||||
}
|
|
||||||
err = client.CreateImage(prefix, "https://storage.googleapis.com/"+bucket+"/"+prefix+".img.tar.gz", family, true)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error creating Google Compute Image: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func runUsage() {
|
|
||||||
fmt.Printf("USAGE: %s run [backend] [options] [prefix]\n\n", os.Args[0])
|
|
||||||
|
|
||||||
fmt.Printf("'backend' specifies the run backend.\n")
|
|
||||||
fmt.Printf("If not specified the platform specific default will be used\n")
|
|
||||||
fmt.Printf("Supported backends are (default platform in brackets):\n")
|
|
||||||
fmt.Printf(" gcp\n")
|
|
||||||
fmt.Printf(" hyperkit [macOS]\n")
|
|
||||||
fmt.Printf(" qemu [linux]\n")
|
|
||||||
fmt.Printf(" vmware\n")
|
|
||||||
fmt.Printf(" packet\n")
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Printf("'options' are the backend specific options.\n")
|
|
||||||
fmt.Printf("See 'moby run [backend] --help' for details.\n\n")
|
|
||||||
fmt.Printf("'prefix' specifies the path to the VM image.\n")
|
|
||||||
fmt.Printf("It defaults to './moby'.\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(args []string) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
runUsage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch args[0] {
|
|
||||||
case "help", "-h", "-help", "--help":
|
|
||||||
runUsage()
|
|
||||||
os.Exit(0)
|
|
||||||
case "hyperkit":
|
|
||||||
runHyperKit(args[1:])
|
|
||||||
case "vmware":
|
|
||||||
runVMware(args[1:])
|
|
||||||
case "gcp":
|
|
||||||
runGcp(args[1:])
|
|
||||||
case "qemu":
|
|
||||||
runQemu(args[1:])
|
|
||||||
case "packet":
|
|
||||||
runPacket(args[1:])
|
|
||||||
default:
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin":
|
|
||||||
runHyperKit(args)
|
|
||||||
case "linux":
|
|
||||||
runQemu(args)
|
|
||||||
default:
|
|
||||||
log.Errorf("There currently is no default 'run' backend for your platform.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultZone = "europe-west1-d"
|
|
||||||
defaultMachine = "g1-small"
|
|
||||||
defaultDiskSize = 1
|
|
||||||
// Environment variables. Some are non-standard
|
|
||||||
zoneVar = "CLOUDSDK_COMPUTE_ZONE"
|
|
||||||
machineVar = "CLOUDSDK_COMPUTE_MACHINE" // non-standard
|
|
||||||
keysVar = "CLOUDSDK_COMPUTE_KEYS" // non-standard
|
|
||||||
projectVar = "CLOUDSDK_CORE_PROJECT"
|
|
||||||
bucketVar = "CLOUDSDK_IMAGE_BUCKET" // non-standard
|
|
||||||
familyVar = "CLOUDSDK_IMAGE_FAMILY" // non-standard
|
|
||||||
publicVar = "CLOUDSDK_IMAGE_PUBLIC" // non-standard
|
|
||||||
nameVar = "CLOUDSDK_IMAGE_NAME" // non-standard
|
|
||||||
diskSizeVar = "CLOUDSDK_DISK_SIZE" // non-standard
|
|
||||||
)
|
|
||||||
|
|
||||||
// Process the run arguments and execute run
|
|
||||||
func runGcp(args []string) {
|
|
||||||
gcpCmd := flag.NewFlagSet("gcp", flag.ExitOnError)
|
|
||||||
gcpCmd.Usage = func() {
|
|
||||||
fmt.Printf("USAGE: %s run gcp [options] [name]\n\n", os.Args[0])
|
|
||||||
fmt.Printf("'name' specifies either the name of an already uploaded\n")
|
|
||||||
fmt.Printf("GCP image or the full path to a image file which will be\n")
|
|
||||||
fmt.Printf("uploaded before it is run.\n\n")
|
|
||||||
fmt.Printf("Options:\n\n")
|
|
||||||
gcpCmd.PrintDefaults()
|
|
||||||
}
|
|
||||||
zoneFlag := gcpCmd.String("zone", defaultZone, "GCP Zone")
|
|
||||||
machineFlag := gcpCmd.String("machine", defaultMachine, "GCP Machine Type")
|
|
||||||
keysFlag := gcpCmd.String("keys", "", "Path to Service Account JSON key file")
|
|
||||||
projectFlag := gcpCmd.String("project", "", "GCP Project Name")
|
|
||||||
diskSizeFlag := gcpCmd.Int("disk-size", 0, "Size of system disk in GB")
|
|
||||||
skipCleanup := gcpCmd.Bool("skip-cleanup", false, "Don't remove images or VMs")
|
|
||||||
|
|
||||||
if err := gcpCmd.Parse(args); err != nil {
|
|
||||||
log.Fatal("Unable to parse args")
|
|
||||||
}
|
|
||||||
|
|
||||||
remArgs := gcpCmd.Args()
|
|
||||||
if len(remArgs) == 0 {
|
|
||||||
fmt.Printf("Please specify the name of the image to boot\n")
|
|
||||||
gcpCmd.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
name := remArgs[0]
|
|
||||||
|
|
||||||
zone := getStringValue(zoneVar, *zoneFlag, defaultZone)
|
|
||||||
machine := getStringValue(machineVar, *machineFlag, defaultMachine)
|
|
||||||
keys := getStringValue(keysVar, *keysFlag, "")
|
|
||||||
project := getStringValue(projectVar, *projectFlag, "")
|
|
||||||
diskSize := getIntValue(diskSizeVar, *diskSizeFlag, defaultDiskSize)
|
|
||||||
|
|
||||||
client, err := NewGCPClient(keys, project)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to connect to GCP")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.CreateInstance(name, name, zone, machine, diskSize, true); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = client.ConnectToInstanceSerialPort(name, zone); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*skipCleanup {
|
|
||||||
if err = client.DeleteInstance(name, zone, true); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/moby/hyperkit/go"
|
|
||||||
"github.com/rneugeba/iso9660wrap"
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Process the run arguments and execute run
|
|
||||||
func runHyperKit(args []string) {
|
|
||||||
hyperkitCmd := flag.NewFlagSet("hyperkit", flag.ExitOnError)
|
|
||||||
hyperkitCmd.Usage = func() {
|
|
||||||
fmt.Printf("USAGE: %s run hyperkit [options] prefix\n\n", os.Args[0])
|
|
||||||
fmt.Printf("'prefix' specifies the path to the VM image.\n")
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Printf("Options:\n")
|
|
||||||
hyperkitCmd.PrintDefaults()
|
|
||||||
}
|
|
||||||
hyperkitPath := hyperkitCmd.String("hyperkit", "", "Path to hyperkit binary (if not in default location)")
|
|
||||||
cpus := hyperkitCmd.Int("cpus", 1, "Number of CPUs")
|
|
||||||
mem := hyperkitCmd.Int("mem", 1024, "Amount of memory in MB")
|
|
||||||
diskSz := hyperkitCmd.Int("disk-size", 0, "Size of Disk in MB")
|
|
||||||
disk := hyperkitCmd.String("disk", "", "Path to disk image to used")
|
|
||||||
data := hyperkitCmd.String("data", "", "Metadata to pass to VM (either a path to a file or a string)")
|
|
||||||
ipStr := hyperkitCmd.String("ip", "", "IP address for the VM")
|
|
||||||
|
|
||||||
if err := hyperkitCmd.Parse(args); err != nil {
|
|
||||||
log.Fatal("Unable to parse args")
|
|
||||||
}
|
|
||||||
remArgs := hyperkitCmd.Args()
|
|
||||||
if len(remArgs) == 0 {
|
|
||||||
fmt.Println("Please specify the prefix to the image to boot\n")
|
|
||||||
hyperkitCmd.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
prefix := remArgs[0]
|
|
||||||
|
|
||||||
isoPath := ""
|
|
||||||
if *data != "" {
|
|
||||||
var d []byte
|
|
||||||
if _, err := os.Stat(*data); os.IsNotExist(err) {
|
|
||||||
d = []byte(*data)
|
|
||||||
} else {
|
|
||||||
d, err = ioutil.ReadFile(*data)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Cannot read user data: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isoPath = prefix + "-data.iso"
|
|
||||||
outfh, err := os.OpenFile(isoPath, os.O_CREATE|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Cannot create user data ISO: %v", err)
|
|
||||||
}
|
|
||||||
err = iso9660wrap.WriteBuffer(outfh, d, "config")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Cannot write user data ISO: %v", err)
|
|
||||||
}
|
|
||||||
if err = outfh.Close(); err != nil {
|
|
||||||
log.Fatalf("Cannot close output ISO: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vpnKitKey := ""
|
|
||||||
if *ipStr != "" {
|
|
||||||
// If an IP address was requested construct a "special" UUID
|
|
||||||
// for the VM.
|
|
||||||
if ip := net.ParseIP(*ipStr); len(ip) > 0 {
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
uuid[12] = ip.To4()[0]
|
|
||||||
uuid[13] = ip.To4()[1]
|
|
||||||
uuid[14] = ip.To4()[2]
|
|
||||||
uuid[15] = ip.To4()[3]
|
|
||||||
vpnKitKey = fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate new UUID, otherwise /sys/class/dmi/id/product_uuid is identical on all VMs
|
|
||||||
vmUUID := uuid.NewV4().String()
|
|
||||||
|
|
||||||
// Run
|
|
||||||
cmdline, err := ioutil.ReadFile(prefix + "-cmdline")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Cannot open cmdline file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *diskSz != 0 && *disk == "" {
|
|
||||||
*disk = prefix + "-disk.img"
|
|
||||||
}
|
|
||||||
|
|
||||||
h, err := hyperkit.New(*hyperkitPath, "auto", "")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Error creating hyperkit: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Kernel = prefix + "-bzImage"
|
|
||||||
h.Initrd = prefix + "-initrd.img"
|
|
||||||
h.VPNKitKey = vpnKitKey
|
|
||||||
h.UUID = vmUUID
|
|
||||||
h.DiskImage = *disk
|
|
||||||
h.ISOImage = isoPath
|
|
||||||
h.CPUs = *cpus
|
|
||||||
h.Memory = *mem
|
|
||||||
h.DiskSize = *diskSz
|
|
||||||
|
|
||||||
err = h.Run(string(cmdline))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Cannot run hyperkit: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/packethost/packngo"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
packetDefaultZone = "ams1"
|
|
||||||
packetDefaultMachine = "baremetal_0"
|
|
||||||
packetDefaultHostname = "moby"
|
|
||||||
packetBaseURL = "PACKET_BASE_URL"
|
|
||||||
packetZoneVar = "PACKET_ZONE"
|
|
||||||
packetMachineVar = "PACKET_MACHINE"
|
|
||||||
packetAPIKeyVar = "PACKET_API_KEY"
|
|
||||||
packetProjectIDVar = "PACKET_PROJECT_ID"
|
|
||||||
packetHostnameVar = "PACKET_HOSTNAME"
|
|
||||||
packetNameVar = "PACKET_NAME"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateHTTPURL does a sanity check that a URL returns a 200 or 300 response
|
|
||||||
func ValidateHTTPURL(url string) {
|
|
||||||
log.Printf("Validating URL: %s", url)
|
|
||||||
resp, err := http.Head(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode >= 400 {
|
|
||||||
log.Fatal("Got a non 200- or 300- HTTP response code: %s", resp)
|
|
||||||
}
|
|
||||||
log.Printf("OK: %d response code", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the run arguments and execute run
|
|
||||||
func runPacket(args []string) {
|
|
||||||
packetCmd := flag.NewFlagSet("packet", flag.ExitOnError)
|
|
||||||
packetCmd.Usage = func() {
|
|
||||||
fmt.Printf("USAGE: %s run packet [options] [name]\n\n", os.Args[0])
|
|
||||||
fmt.Printf("Options:\n\n")
|
|
||||||
packetCmd.PrintDefaults()
|
|
||||||
}
|
|
||||||
baseURLFlag := packetCmd.String("base-url", "", "Base URL that the kernel and initrd are served from.")
|
|
||||||
zoneFlag := packetCmd.String("zone", packetDefaultZone, "Packet Zone")
|
|
||||||
machineFlag := packetCmd.String("machine", packetDefaultMachine, "Packet Machine Type")
|
|
||||||
apiKeyFlag := packetCmd.String("api-key", "", "Packet API key")
|
|
||||||
projectFlag := packetCmd.String("project-id", "", "Packet Project ID")
|
|
||||||
hostNameFlag := packetCmd.String("hostname", packetDefaultHostname, "Hostname of new instance")
|
|
||||||
nameFlag := packetCmd.String("img-name", "", "Overrides the prefix used to identify the files. Defaults to [name]")
|
|
||||||
if err := packetCmd.Parse(args); err != nil {
|
|
||||||
log.Fatal("Unable to parse args")
|
|
||||||
}
|
|
||||||
|
|
||||||
remArgs := packetCmd.Args()
|
|
||||||
prefix := "packet"
|
|
||||||
if len(remArgs) > 0 {
|
|
||||||
prefix = remArgs[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
url := getStringValue(packetBaseURL, *baseURLFlag, "")
|
|
||||||
if url == "" {
|
|
||||||
log.Fatal("Need to specify a value for --base-url where the images are hosted. This URL should contain <url>/%s-bzImage and <url>/%s-initrd.img")
|
|
||||||
}
|
|
||||||
facility := getStringValue(packetZoneVar, *zoneFlag, "")
|
|
||||||
plan := getStringValue(packetMachineVar, *machineFlag, defaultMachine)
|
|
||||||
apiKey := getStringValue(packetAPIKeyVar, *apiKeyFlag, "")
|
|
||||||
if apiKey == "" {
|
|
||||||
log.Fatal("Must specify a Packet.net API key with --api-key")
|
|
||||||
}
|
|
||||||
projectID := getStringValue(packetProjectIDVar, *projectFlag, "")
|
|
||||||
if projectID == "" {
|
|
||||||
log.Fatal("Must specify a Packet.net Project ID with --project-id")
|
|
||||||
}
|
|
||||||
hostname := getStringValue(packetHostnameVar, *hostNameFlag, "")
|
|
||||||
name := getStringValue(packetNameVar, *nameFlag, prefix)
|
|
||||||
osType := "custom_ipxe"
|
|
||||||
billing := "hourly"
|
|
||||||
userData := fmt.Sprintf("#!ipxe\n\ndhcp\nset base-url %s\nset kernel-params ip=dhcp nomodeset ro serial console=ttyS1,115200\nkernel ${base-url}/%s-bzImage ${kernel-params}\ninitrd ${base-url}/%s-initrd.img\nboot", url, name, name)
|
|
||||||
log.Debugf("Using userData of:\n%s\n", userData)
|
|
||||||
initrdURL := fmt.Sprintf("%s/%s-initrd.img", url, name)
|
|
||||||
kernelURL := fmt.Sprintf("%s/%s-bzImage", url, name)
|
|
||||||
ValidateHTTPURL(kernelURL)
|
|
||||||
ValidateHTTPURL(initrdURL)
|
|
||||||
client := packngo.NewClient("", apiKey, nil)
|
|
||||||
tags := []string{}
|
|
||||||
req := packngo.DeviceCreateRequest{
|
|
||||||
HostName: hostname,
|
|
||||||
Plan: plan,
|
|
||||||
Facility: facility,
|
|
||||||
OS: osType,
|
|
||||||
BillingCycle: billing,
|
|
||||||
ProjectID: projectID,
|
|
||||||
UserData: userData,
|
|
||||||
Tags: tags,
|
|
||||||
}
|
|
||||||
d, _, err := client.Devices.Create(&req)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
b, err := json.MarshalIndent(d, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// log response json if in verbose mode
|
|
||||||
log.Debugf("%s\n", string(b))
|
|
||||||
// TODO: poll events api for bringup (requires extpacknogo)
|
|
||||||
// TODO: connect to serial console (requires API extension to get SSH URI)
|
|
||||||
// TODO: add ssh keys via API registered keys
|
|
||||||
}
|
|
@ -1,285 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QemuImg is the version of qemu container
|
|
||||||
const QemuImg = "linuxkit/qemu:17f052263d63c8a2b641ad91c589edcbb8a18c82"
|
|
||||||
|
|
||||||
// QemuConfig contains the config for Qemu
|
|
||||||
type QemuConfig struct {
|
|
||||||
Prefix string
|
|
||||||
ISO bool
|
|
||||||
UEFI bool
|
|
||||||
Kernel bool
|
|
||||||
GUI bool
|
|
||||||
DiskPath string
|
|
||||||
DiskSize string
|
|
||||||
FWPath string
|
|
||||||
Arch string
|
|
||||||
CPUs string
|
|
||||||
Memory string
|
|
||||||
KVM bool
|
|
||||||
Containerized bool
|
|
||||||
QemuBinPath string
|
|
||||||
QemuImgPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func runQemu(args []string) {
|
|
||||||
qemuFlags := flag.NewFlagSet("qemu", flag.ExitOnError)
|
|
||||||
qemuFlags.Usage = func() {
|
|
||||||
fmt.Printf("USAGE: %s run qemu [options] prefix\n\n", os.Args[0])
|
|
||||||
fmt.Printf("'prefix' specifies the path to the VM image.\n")
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Printf("Options:\n")
|
|
||||||
qemuFlags.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine Flags
|
|
||||||
qemuGUI := qemuFlags.Bool("gui", false, "Set qemu to use video output instead of stdio")
|
|
||||||
qemuUEFI := qemuFlags.Bool("uefi", false, "Set UEFI boot from 'prefix'-efi.iso")
|
|
||||||
qemuIso := qemuFlags.Bool("iso", false, "Set Legacy BIOS boot from 'prefix'.iso")
|
|
||||||
qemuKernel := qemuFlags.Bool("kernel", true, "Set boot using 'prefix'-bzImage/-initrd/-cmdline")
|
|
||||||
|
|
||||||
// Paths and settings for Disks and UEFI firware
|
|
||||||
qemuDiskPath := qemuFlags.String("diskpath", "", "Path to disk image to use")
|
|
||||||
qemuDiskSize := qemuFlags.String("disksize", "", "Size of disk to create, only created if it doesn't exist")
|
|
||||||
qemuFWPath := qemuFlags.String("fwpath", "/usr/share/ovmf/bios.bin", "Path to OVMF firmware for UEFI boot")
|
|
||||||
|
|
||||||
// VM configuration
|
|
||||||
qemuArch := qemuFlags.String("arch", "x86_64", "Type of architecture to use, e.g. x86_64, aarch64")
|
|
||||||
qemuCPUs := qemuFlags.String("cpus", "1", "Number of CPUs")
|
|
||||||
qemuMem := qemuFlags.String("mem", "1024", "Amount of memory in MB")
|
|
||||||
|
|
||||||
if err := qemuFlags.Parse(args); err != nil {
|
|
||||||
log.Fatal("Unable to parse args")
|
|
||||||
}
|
|
||||||
remArgs := qemuFlags.Args()
|
|
||||||
|
|
||||||
if len(remArgs) == 0 {
|
|
||||||
fmt.Println("Please specify the prefix to the image to boot")
|
|
||||||
qemuFlags.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
prefix := remArgs[0]
|
|
||||||
|
|
||||||
// Print warning if conflicting UEFI and ISO flags are set
|
|
||||||
if *qemuUEFI && *qemuIso {
|
|
||||||
log.Warnf("Both -iso and -uefi have been used")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := QemuConfig{
|
|
||||||
Prefix: prefix,
|
|
||||||
ISO: *qemuIso,
|
|
||||||
UEFI: *qemuUEFI,
|
|
||||||
Kernel: *qemuKernel,
|
|
||||||
GUI: *qemuGUI,
|
|
||||||
DiskPath: *qemuDiskPath,
|
|
||||||
DiskSize: *qemuDiskSize,
|
|
||||||
FWPath: *qemuFWPath,
|
|
||||||
Arch: *qemuArch,
|
|
||||||
CPUs: *qemuCPUs,
|
|
||||||
Memory: *qemuMem,
|
|
||||||
}
|
|
||||||
|
|
||||||
config, qemuArgs := buildQemuCmdline(config)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if config.Containerized {
|
|
||||||
err = runQemuContainer(config, qemuArgs)
|
|
||||||
} else {
|
|
||||||
err = runQemuLocal(config, qemuArgs)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runQemuLocal(config QemuConfig, args []string) error {
|
|
||||||
if config.DiskPath != "" {
|
|
||||||
// If disk doesn't exist then create one
|
|
||||||
if _, err := os.Stat(config.DiskPath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Infof("Creating new qemu disk [%s]", config.DiskPath)
|
|
||||||
qemuImgCmd := exec.Command(config.QemuImgPath, "create", "-f", "qcow2", config.DiskPath, config.DiskSize)
|
|
||||||
log.Debugf("%v\n", qemuImgCmd.Args)
|
|
||||||
if err := qemuImgCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("Error creating disk [%s]: %s", config.DiskPath, err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Infof("Using existing disk [%s]", config.DiskPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for OVMF firmware before running
|
|
||||||
if config.UEFI {
|
|
||||||
if _, err := os.Stat(config.FWPath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("File [%s] does not exist, please ensure OVMF is installed", config.FWPath)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qemuCmd := exec.Command(config.QemuBinPath, args...)
|
|
||||||
// If verbosity is enabled print out the full path/arguments
|
|
||||||
log.Debugf("%v\n", qemuCmd.Args)
|
|
||||||
|
|
||||||
// If we're not using a separate window then link the execution to stdin/out
|
|
||||||
if config.GUI != true {
|
|
||||||
qemuCmd.Stdin = os.Stdin
|
|
||||||
qemuCmd.Stdout = os.Stdout
|
|
||||||
qemuCmd.Stderr = os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
return qemuCmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runQemuContainer(config QemuConfig, args []string) error {
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerArgs := []string{"run", "-i", "--rm", "-v", fmt.Sprintf("%s:%s", wd, "/tmp"), "-w", "/tmp"}
|
|
||||||
|
|
||||||
if config.KVM {
|
|
||||||
dockerArgs = append(dockerArgs, "--device", "/dev/kvm")
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerPath, err := exec.LookPath("docker")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to find docker in the $PATH")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.DiskPath != "" {
|
|
||||||
// If disk doesn't exist then create one
|
|
||||||
if _, err = os.Stat(config.DiskPath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Infof("Creating new qemu disk [%s]", config.DiskPath)
|
|
||||||
imgArgs := append(dockerArgs, QemuImg, "qemu-img", "create", "-f", "qcow2", config.DiskPath, config.DiskSize)
|
|
||||||
qemuImgCmd := exec.Command(dockerPath, imgArgs...)
|
|
||||||
log.Debugf("%v\n", qemuImgCmd.Args)
|
|
||||||
if err = qemuImgCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("Error creating disk [%s]: %s", config.DiskPath, err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Infof("Using existing disk [%s]", config.DiskPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qemuArgs := append(dockerArgs, QemuImg, "qemu-system-"+config.Arch)
|
|
||||||
qemuArgs = append(qemuArgs, args...)
|
|
||||||
qemuCmd := exec.Command(dockerPath, qemuArgs...)
|
|
||||||
// If verbosity is enabled print out the full path/arguments
|
|
||||||
log.Debugf("%v\n", qemuCmd.Args)
|
|
||||||
|
|
||||||
// GUI mode not currently supported in a container. Although it could be in future.
|
|
||||||
if config.GUI == true {
|
|
||||||
return fmt.Errorf("GUI mode is only supported when running locally, not in a container")
|
|
||||||
}
|
|
||||||
|
|
||||||
qemuCmd.Stdin = os.Stdin
|
|
||||||
qemuCmd.Stdout = os.Stdout
|
|
||||||
qemuCmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
return qemuCmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildQemuCmdline(config QemuConfig) (QemuConfig, []string) {
|
|
||||||
// Before building qemu arguments, check if qemu is in the PATH or fallback to containerized
|
|
||||||
qemuBinPath := "qemu-system-" + config.Arch
|
|
||||||
qemuImgPath := "qemu-img"
|
|
||||||
|
|
||||||
var err error
|
|
||||||
config.QemuBinPath, err = exec.LookPath(qemuBinPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Infof("Unable to find %s within the $PATH. Using a container", qemuBinPath)
|
|
||||||
config.Containerized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
config.QemuImgPath, err = exec.LookPath(qemuImgPath)
|
|
||||||
if err != nil {
|
|
||||||
// No need to show the error message twice
|
|
||||||
if !config.Containerized {
|
|
||||||
log.Infof("Unable to find %s within the $PATH. Using a container", qemuImgPath)
|
|
||||||
config.Containerized = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through the flags and build arguments
|
|
||||||
var qemuArgs []string
|
|
||||||
qemuArgs = append(qemuArgs, "-device", "virtio-rng-pci")
|
|
||||||
qemuArgs = append(qemuArgs, "-smp", config.CPUs)
|
|
||||||
qemuArgs = append(qemuArgs, "-m", config.Memory)
|
|
||||||
|
|
||||||
// Look for kvm device and enable for qemu if it exists
|
|
||||||
if _, err = os.Stat("/dev/kvm"); os.IsNotExist(err) {
|
|
||||||
qemuArgs = append(qemuArgs, "-machine", "q35")
|
|
||||||
} else {
|
|
||||||
config.KVM = true
|
|
||||||
qemuArgs = append(qemuArgs, "-enable-kvm")
|
|
||||||
qemuArgs = append(qemuArgs, "-machine", "q35,accel=kvm:tcg")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.DiskPath != "" {
|
|
||||||
qemuArgs = append(qemuArgs, "-drive", "file="+config.DiskPath+",format=qcow2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check flags for iso/uefi boot and if so disable kernel boot
|
|
||||||
if config.ISO {
|
|
||||||
config.Kernel = false
|
|
||||||
qemuIsoPath := buildPath(config.Prefix, ".iso")
|
|
||||||
qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.UEFI {
|
|
||||||
config.Kernel = false
|
|
||||||
qemuIsoPath := buildPath(config.Prefix, "-efi.iso")
|
|
||||||
qemuArgs = append(qemuArgs, "-pflash", config.FWPath)
|
|
||||||
qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
|
|
||||||
qemuArgs = append(qemuArgs, "-boot", "d")
|
|
||||||
}
|
|
||||||
|
|
||||||
// build kernel boot config from bzImage/initrd/cmdline
|
|
||||||
if config.Kernel {
|
|
||||||
qemuKernelPath := buildPath(config.Prefix, "-bzImage")
|
|
||||||
qemuInitrdPath := buildPath(config.Prefix, "-initrd.img")
|
|
||||||
qemuArgs = append(qemuArgs, "-kernel", qemuKernelPath)
|
|
||||||
qemuArgs = append(qemuArgs, "-initrd", qemuInitrdPath)
|
|
||||||
consoleString, err := ioutil.ReadFile(config.Prefix + "-cmdline")
|
|
||||||
if err != nil {
|
|
||||||
log.Infof(" %s\n defaulting to console output", err.Error())
|
|
||||||
qemuArgs = append(qemuArgs, "-append", "console=ttyS0 console=tty0 page_poison=1")
|
|
||||||
} else {
|
|
||||||
qemuArgs = append(qemuArgs, "-append", string(consoleString))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.GUI != true {
|
|
||||||
qemuArgs = append(qemuArgs, "-nographic")
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, qemuArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildPath(prefix string, postfix string) string {
|
|
||||||
path := prefix + postfix
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
log.Fatalf("File [%s] does not exist in current directory", path)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
@ -1,165 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
//Version 12 relates to Fusion 8 and WS 12
|
|
||||||
//virtualHW.version = "12"
|
|
||||||
|
|
||||||
const vmxHW string = `config.version = "8"
|
|
||||||
virtualHW.version = "12"
|
|
||||||
vmci0.present = "TRUE"
|
|
||||||
floppy0.present = "FALSE"
|
|
||||||
displayName = "%s"
|
|
||||||
numvcpus = "%d"
|
|
||||||
memsize = "%d"
|
|
||||||
`
|
|
||||||
|
|
||||||
const vmxDisk string = `scsi0.present = "TRUE"
|
|
||||||
scsi0.sharedBus = "none"
|
|
||||||
scsi0.virtualDev = "lsilogic"
|
|
||||||
scsi0:0.present = "TRUE"
|
|
||||||
scsi0:0.fileName = "%s"
|
|
||||||
scsi0:0.deviceType = "scsi-hardDisk"
|
|
||||||
`
|
|
||||||
|
|
||||||
const vmxCdrom string = `ide1:0.present = "TRUE"
|
|
||||||
ide1:0.fileName = "%s"
|
|
||||||
ide1:0.deviceType = "cdrom-image"
|
|
||||||
`
|
|
||||||
|
|
||||||
const vmxPCI string = `pciBridge0.present = "TRUE"
|
|
||||||
pciBridge4.present = "TRUE"
|
|
||||||
pciBridge4.virtualDev = "pcieRootPort"
|
|
||||||
pciBridge4.functions = "8"
|
|
||||||
pciBridge5.present = "TRUE"
|
|
||||||
pciBridge5.virtualDev = "pcieRootPort"
|
|
||||||
pciBridge5.functions = "8"
|
|
||||||
pciBridge6.present = "TRUE"
|
|
||||||
pciBridge6.virtualDev = "pcieRootPort"
|
|
||||||
pciBridge6.functions = "8"
|
|
||||||
pciBridge7.present = "TRUE"
|
|
||||||
pciBridge7.virtualDev = "pcieRootPort"
|
|
||||||
pciBridge7.functions = "8"
|
|
||||||
ethernet0.pciSlotNumber = "32"
|
|
||||||
ethernet0.present = "TRUE"
|
|
||||||
ethernet0.virtualDev = "e1000"
|
|
||||||
ethernet0.networkName = "Inside"
|
|
||||||
ethernet0.generatedAddressOffset = "0"
|
|
||||||
guestOS = "other3xlinux-64"
|
|
||||||
`
|
|
||||||
|
|
||||||
func runVMware(args []string) {
|
|
||||||
vmwareArgs := flag.NewFlagSet("vmware", flag.ExitOnError)
|
|
||||||
vmwareArgs.Usage = func() {
|
|
||||||
fmt.Printf("USAGE: %s run vmware [options] prefix\n\n", os.Args[0])
|
|
||||||
fmt.Printf("'prefix' specifies the path to the VM image.\n")
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Printf("Options:\n")
|
|
||||||
vmwareArgs.PrintDefaults()
|
|
||||||
}
|
|
||||||
runCPUs := vmwareArgs.Int("cpus", 1, "Number of CPUs")
|
|
||||||
runMem := vmwareArgs.Int("mem", 1024, "Amount of memory in MB")
|
|
||||||
runDisk := vmwareArgs.String("disk", "", "Path to disk image to use")
|
|
||||||
|
|
||||||
if err := vmwareArgs.Parse(args); err != nil {
|
|
||||||
log.Fatal("Unable to parse args")
|
|
||||||
}
|
|
||||||
remArgs := vmwareArgs.Args()
|
|
||||||
|
|
||||||
if len(remArgs) == 0 {
|
|
||||||
fmt.Println("Please specify the prefix to the image to boot")
|
|
||||||
vmwareArgs.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
prefix := remArgs[0]
|
|
||||||
|
|
||||||
// Build the contents of the VMWare .vmx file
|
|
||||||
vmx := buildVMX(*runCPUs, *runMem, *runDisk, prefix)
|
|
||||||
|
|
||||||
if vmx == "" {
|
|
||||||
log.Fatalf("VMware .vmx file could not be generated, please confirm inputs")
|
|
||||||
}
|
|
||||||
|
|
||||||
var path string
|
|
||||||
var vmrunArgs []string
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
path = "C:\\Program\\ files\\VMware Workstation\\vmrun.exe"
|
|
||||||
vmrunArgs = []string{"-T", "ws", "start", prefix + ".vmx"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
path = "/Applications/VMware Fusion.app/Contents/Library/vmrun"
|
|
||||||
vmrunArgs = []string{"-T", "fusion", "start", prefix + ".vmx"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
path = "vmrun"
|
|
||||||
fullVMrunPath, err := exec.LookPath(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to find %s within the $PATH", path)
|
|
||||||
}
|
|
||||||
path = fullVMrunPath
|
|
||||||
vmrunArgs = []string{"-T", "ws", "start", prefix + ".vmx"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check executables exist before attempting to execute
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
log.Fatalf("ERROR VMware exectuables can not be found, ensure software is installed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the .vmx file
|
|
||||||
err := ioutil.WriteFile(prefix+".vmx", []byte(vmx), 0644)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error writing .vmx file")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(path, vmrunArgs...)
|
|
||||||
out, err := cmd.Output()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error starting vmrun")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check there is output to push to logging
|
|
||||||
if len(out) > 0 {
|
|
||||||
log.Info(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildVMX(cpus int, mem int, diskPath string, prefix string) string {
|
|
||||||
// CD-ROM can be added for use in a further release
|
|
||||||
cdromPath := ""
|
|
||||||
|
|
||||||
var returnString string
|
|
||||||
|
|
||||||
returnString += fmt.Sprintf(vmxHW, prefix, cpus, mem)
|
|
||||||
|
|
||||||
if cdromPath != "" {
|
|
||||||
returnString += fmt.Sprintf(vmxCdrom, cdromPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diskPath != "" {
|
|
||||||
returnString += fmt.Sprintf(vmxDisk, diskPath)
|
|
||||||
} else {
|
|
||||||
vmdkPath := prefix + ".vmdk"
|
|
||||||
if _, err := os.Stat(vmdkPath); os.IsNotExist(err) {
|
|
||||||
log.Fatalf("File [%s] does not exist in current directory", vmdkPath)
|
|
||||||
} else {
|
|
||||||
returnString += fmt.Sprintf(vmxDisk, vmdkPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
returnString += vmxPCI
|
|
||||||
return returnString
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getStringValue(envKey string, flagVal string, defaultVal string) string {
|
|
||||||
var res string
|
|
||||||
|
|
||||||
// If defined, take the env variable
|
|
||||||
if _, ok := os.LookupEnv(envKey); ok {
|
|
||||||
res = os.Getenv(envKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a flag is specified, this value takes precedence
|
|
||||||
// Ignore cases where the flag carries the default value
|
|
||||||
if flagVal != "" && flagVal != defaultVal {
|
|
||||||
res = flagVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we still don't have a value, use the default
|
|
||||||
if res == "" {
|
|
||||||
res = defaultVal
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIntValue(envKey string, flagVal int, defaultVal int) int {
|
|
||||||
var res int
|
|
||||||
|
|
||||||
// If defined, take the env variable
|
|
||||||
if _, ok := os.LookupEnv(envKey); ok {
|
|
||||||
var err error
|
|
||||||
res, err = strconv.Atoi(os.Getenv(envKey))
|
|
||||||
if err != nil {
|
|
||||||
res = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a flag is specified, this value takes precedence
|
|
||||||
// Ignore cases where the flag carries the default value
|
|
||||||
if flagVal > 0 {
|
|
||||||
res = flagVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we still don't have a value, use the default
|
|
||||||
if res == 0 {
|
|
||||||
res = defaultVal
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBoolValue(envKey string, flagVal bool) bool {
|
|
||||||
var res bool
|
|
||||||
|
|
||||||
// If defined, take the env variable
|
|
||||||
if _, ok := os.LookupEnv(envKey); ok {
|
|
||||||
switch os.Getenv(envKey) {
|
|
||||||
case "":
|
|
||||||
res = false
|
|
||||||
case "0":
|
|
||||||
res = false
|
|
||||||
case "false":
|
|
||||||
res = false
|
|
||||||
case "FALSE":
|
|
||||||
res = false
|
|
||||||
case "1":
|
|
||||||
res = true
|
|
||||||
default:
|
|
||||||
// catches "true", "TRUE" or anything else
|
|
||||||
res = true
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a flag is specified, this value takes precedence
|
|
||||||
if res != flagVal {
|
|
||||||
res = flagVal
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user