github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/cmd/run.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/sirupsen/logrus"
     9  	"github.com/spf13/cobra"
    10  
    11  	"bufio"
    12  	"net"
    13  
    14  	"github.com/emc-advanced-dev/pkg/errors"
    15  	"github.com/solo-io/unik/pkg/client"
    16  )
    17  
    18  var instanceName, imageName string
    19  var volumes, envPairs []string
    20  var instanceMemory, debugPort int
    21  
    22  var runCmd = &cobra.Command{
    23  	Use:   "run",
    24  	Short: "Run a unikernel instance from a compiled image",
    25  	Long: `Deploys a running instance from a unik-compiled unikernel disk image.
    26  The instance will be deployed on the provider the image was compiled for.
    27  e.g. if the image was compiled for virtualbox, unik will attempt to deploy
    28  the image on the configured virtualbox environment.
    29  
    30  'unik run' requires a unik-managed volume (see 'unik volumes' and 'unik create volume')
    31  to be attached and mounted to each mount point specified at image compilation time.
    32  This means that if the image was compiled with two mount points, /data1 and /data2,
    33  'unik run' requires 2 available volumes to be attached to the instance at runtime, which
    34  must be specified with the flags --vol SOME_VOLUME_NAME:/data1 --vol ANOTHER_VOLUME_NAME:/data2
    35  If no mount points are required for the image, volumes cannot be attached.
    36  
    37  environment variables can be set at runtime through the use of the -env flag.
    38  
    39  Example usage:
    40  	unik run --instanceName newInstance --imageName myImage --vol myVol:/mount1 --vol yourVol:/mount2 --env foo=bar --env another=one --instanceMemory 1234
    41  
    42  	# will create and run an instance of myImage on the provider environment myImage is compiled for
    43  	# instance will be named newInstance
    44  	# instance will attempt to mount unik-managed volume myVol to /mount1
    45  	# instance will attempt to mount unik-managed volume yourVol to /mount2
    46  	# instance will boot with env variable 'foo' set to 'bar'
    47  	# instance will boot with env variable 'another' set to 'one'
    48  	# instance will get 1234 MB of memory
    49  
    50  	# note that run must take exactly one --vol argument for each mount point defined in the image specification
    51  `,
    52  	Run: func(cmd *cobra.Command, args []string) {
    53  		if err := func() error {
    54  			if instanceName == "" {
    55  				return errors.New("--instanceName must be set", nil)
    56  			}
    57  			if imageName == "" {
    58  				return errors.New("--imageName must be set", nil)
    59  			}
    60  			if err := readClientConfig(); err != nil {
    61  				return err
    62  			}
    63  			if host == "" {
    64  				host = clientConfig.Host
    65  			}
    66  
    67  			mountPointsToVols := make(map[string]string)
    68  			for _, vol := range volumes {
    69  				pair := strings.SplitN(vol, ":", 2)
    70  				if len(pair) != 2 {
    71  					return errors.New(fmt.Sprintf("invalid format for vol flag: %s", vol), nil)
    72  				}
    73  				volId := pair[0]
    74  				mnt := pair[1]
    75  				mountPointsToVols[mnt] = volId
    76  			}
    77  
    78  			env := make(map[string]string)
    79  			for _, e := range envPairs {
    80  				pair := strings.Split(e, "=")
    81  				if len(pair) != 2 {
    82  					return errors.New(fmt.Sprintf("invalid format for env flag: %s", e), nil)
    83  				}
    84  				key := pair[0]
    85  				val := pair[1]
    86  				env[key] = val
    87  			}
    88  
    89  			logrus.WithFields(logrus.Fields{
    90  				"instanceName": instanceName,
    91  				"imageName":    imageName,
    92  				"env":          env,
    93  				"mounts":       mountPointsToVols,
    94  				"host":         host,
    95  			}).Infof("running unik run")
    96  			instance, err := client.UnikClient(host).Instances().Run(instanceName, imageName, mountPointsToVols, env, instanceMemory, noCleanup, debugMode)
    97  			if err != nil {
    98  				return errors.New("running image failed: %v", err)
    99  			}
   100  			printInstances(instance)
   101  			if debugMode {
   102  				logrus.Infof("attaching debugger to instance %s ...", instance.Name)
   103  				connectDebugger()
   104  			}
   105  			return nil
   106  		}(); err != nil {
   107  			logrus.Errorf("failed running instance: %v", err)
   108  			os.Exit(-1)
   109  		}
   110  	},
   111  }
   112  
   113  func init() {
   114  	RootCmd.AddCommand(runCmd)
   115  	runCmd.Flags().StringVar(&instanceName, "instanceName", "", "<string,required> name to give the instance. must be unique")
   116  	runCmd.Flags().StringVar(&imageName, "imageName", "", "<string,required> image to use")
   117  	runCmd.Flags().StringSliceVar(&envPairs, "env", []string{}, "<string,repeated> set any number of environment variables for the instance. must be in the format KEY=VALUE")
   118  	runCmd.Flags().StringSliceVar(&volumes, "vol", []string{}, `<string,repeated> each --vol flag specifies one volume id and the corresponding mount point to attach
   119  	to the instance at boot time. volumes must be attached to the instance for each mount point expected by the image.
   120  	run 'unik image <image_name>' to see the mount points required for the image.
   121  	specified in the format 'volume_id:mount_point'`)
   122  	runCmd.Flags().IntVar(&instanceMemory, "instanceMemory", 0, "<int, optional> amount of memory (in MB) to assign to the instance. if none is given, the provider default will be used")
   123  	runCmd.Flags().BoolVar(&noCleanup, "no-cleanup", false, "<bool, optional> for debugging; do not clean up artifacts for instances that fail to launch")
   124  	runCmd.Flags().BoolVar(&debugMode, "debug-mode", false, "<bool, optional> runs the instance in Debug mode so GDB can be attached. Currently only supported on QEMU provider")
   125  	runCmd.Flags().IntVar(&debugPort, "debug-port", 3001, "<int, optional> target port for debugger tcp connections. used in conjunction with --debug-mode")
   126  }
   127  
   128  func connectDebugger() {
   129  	addr := fmt.Sprintf("%v:%v", strings.Split(host, ":")[0], debugPort)
   130  	conn, err := net.Dial("tcp", addr)
   131  	if err != nil {
   132  		logrus.Errorf("failed to initiate tcp connection: %v", err)
   133  		os.Exit(-1)
   134  	}
   135  	if _, err := conn.Write([]byte("GET / HTTP/1.0\r\n\r\n")); err != nil {
   136  		logrus.Errorf("failed to initialize debgger connection: %v", err)
   137  		os.Exit(-1)
   138  	}
   139  
   140  	go func() {
   141  		reader := bufio.NewReader(conn)
   142  		for {
   143  			data, err := reader.ReadBytes('\n')
   144  			if err != nil {
   145  				logrus.Errorf("disconnected from debugger: %v", err)
   146  				os.Exit(0)
   147  			}
   148  			fmt.Print(string(data))
   149  		}
   150  	}()
   151  
   152  	reader := bufio.NewReader(os.Stdin)
   153  	logrus.Infof("Connected to %s", host)
   154  	for {
   155  		data, err := reader.ReadBytes('\n')
   156  		if err != nil {
   157  			logrus.Errorf("failed reading stdin: %v", err)
   158  			os.Exit(-1)
   159  		}
   160  		if _, err := conn.Write(data); err != nil {
   161  			logrus.Errorf("writing to tcp connection: %v", err)
   162  			os.Exit(-1)
   163  		}
   164  	}
   165  }