From 8968335e59e79eff91c18e3e24748e012dcab766 Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Mon, 24 Apr 2017 18:06:35 +0100 Subject: [PATCH] Split cli into moby and linuxkit moby just does the simple `build` cases, while `linuxkit` does `push` and `run`. Signed-off-by: Justin Cormack --- src/cmd/moby/gcp.go | 474 ----------------------------------- src/cmd/moby/main.go | 6 - src/cmd/moby/push.go | 37 --- src/cmd/moby/push_gcp.go | 73 ------ src/cmd/moby/run.go | 59 ----- src/cmd/moby/run_gcp.go | 81 ------ src/cmd/moby/run_hyperkit.go | 116 --------- src/cmd/moby/run_packet.go | 113 --------- src/cmd/moby/run_qemu.go | 285 --------------------- src/cmd/moby/run_vmware.go | 165 ------------ src/cmd/moby/util.go | 83 ------ 11 files changed, 1492 deletions(-) delete mode 100644 src/cmd/moby/gcp.go delete mode 100644 src/cmd/moby/push.go delete mode 100644 src/cmd/moby/push_gcp.go delete mode 100644 src/cmd/moby/run.go delete mode 100644 src/cmd/moby/run_gcp.go delete mode 100644 src/cmd/moby/run_hyperkit.go delete mode 100644 src/cmd/moby/run_packet.go delete mode 100644 src/cmd/moby/run_qemu.go delete mode 100644 src/cmd/moby/run_vmware.go delete mode 100644 src/cmd/moby/util.go diff --git a/src/cmd/moby/gcp.go b/src/cmd/moby/gcp.go deleted file mode 100644 index e7f4472a4..000000000 --- a/src/cmd/moby/gcp.go +++ /dev/null @@ -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") -} diff --git a/src/cmd/moby/main.go b/src/cmd/moby/main.go index faf7b09b2..33a262585 100644 --- a/src/cmd/moby/main.go +++ b/src/cmd/moby/main.go @@ -42,8 +42,6 @@ func main() { fmt.Printf("USAGE: %s [options] COMMAND\n\n", filepath.Base(os.Args[0])) fmt.Printf("Commands:\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(" help Print this message\n") fmt.Printf("\n") @@ -82,10 +80,6 @@ func main() { switch args[0] { case "build": build(args[1:]) - case "push": - push(args[1:]) - case "run": - run(args[1:]) case "version": version() case "help": diff --git a/src/cmd/moby/push.go b/src/cmd/moby/push.go deleted file mode 100644 index e168ba3af..000000000 --- a/src/cmd/moby/push.go +++ /dev/null @@ -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.") - } -} diff --git a/src/cmd/moby/push_gcp.go b/src/cmd/moby/push_gcp.go deleted file mode 100644 index 68818aa57..000000000 --- a/src/cmd/moby/push_gcp.go +++ /dev/null @@ -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) - } -} diff --git a/src/cmd/moby/run.go b/src/cmd/moby/run.go deleted file mode 100644 index d0564b81a..000000000 --- a/src/cmd/moby/run.go +++ /dev/null @@ -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.") - } - } -} diff --git a/src/cmd/moby/run_gcp.go b/src/cmd/moby/run_gcp.go deleted file mode 100644 index b29e8714e..000000000 --- a/src/cmd/moby/run_gcp.go +++ /dev/null @@ -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) - } - } -} diff --git a/src/cmd/moby/run_hyperkit.go b/src/cmd/moby/run_hyperkit.go deleted file mode 100644 index 3052c08aa..000000000 --- a/src/cmd/moby/run_hyperkit.go +++ /dev/null @@ -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) - } -} diff --git a/src/cmd/moby/run_packet.go b/src/cmd/moby/run_packet.go deleted file mode 100644 index 080b4821d..000000000 --- a/src/cmd/moby/run_packet.go +++ /dev/null @@ -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 /%s-bzImage and /%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 -} diff --git a/src/cmd/moby/run_qemu.go b/src/cmd/moby/run_qemu.go deleted file mode 100644 index c0d515890..000000000 --- a/src/cmd/moby/run_qemu.go +++ /dev/null @@ -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 -} diff --git a/src/cmd/moby/run_vmware.go b/src/cmd/moby/run_vmware.go deleted file mode 100644 index f34fd62a2..000000000 --- a/src/cmd/moby/run_vmware.go +++ /dev/null @@ -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 -} diff --git a/src/cmd/moby/util.go b/src/cmd/moby/util.go deleted file mode 100644 index a5717c07a..000000000 --- a/src/cmd/moby/util.go +++ /dev/null @@ -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 -}