1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
16 "git.curoverse.com/arvados.git/lib/cloud"
17 "git.curoverse.com/arvados.git/lib/config"
18 "git.curoverse.com/arvados.git/lib/dispatchcloud"
19 "git.curoverse.com/arvados.git/sdk/go/arvados"
20 "git.curoverse.com/arvados.git/sdk/go/ctxlog"
21 "golang.org/x/crypto/ssh"
28 func (command) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
32 fmt.Fprintf(stderr, "%s\n", err)
36 flags := flag.NewFlagSet("", flag.ContinueOnError)
37 flags.SetOutput(stderr)
38 configFile := flags.String("config", arvados.DefaultConfigFile, "Site configuration `file`")
39 instanceSetID := flags.String("instance-set-id", defaultInstanceSetID(), "InstanceSetID tag `value` to use on the test instance")
40 imageID := flags.String("image-id", "", "Image ID to use when creating the test instance (if empty, use cluster config)")
41 instanceType := flags.String("instance-type", "", "Instance type to create (if empty, use cheapest type in config)")
42 destroyExisting := flags.Bool("destroy-existing", false, "Destroy any existing instances tagged with our InstanceSetID, instead of erroring out")
43 shellCommand := flags.String("command", "", "Run an interactive shell command on the test instance when it boots")
44 pauseBeforeDestroy := flags.Bool("pause-before-destroy", false, "Prompt and wait before destroying the test instance")
45 err = flags.Parse(args)
46 if err == flag.ErrHelp {
49 } else if err != nil {
53 if len(flags.Args()) != 0 {
57 logger := ctxlog.New(stderr, "text", "info")
60 logger.WithError(err).Error("fatal")
61 // suppress output from the other error-printing func
64 logger.Info("exiting")
67 cfg, err := config.LoadFile(*configFile, logger)
71 cluster, err := cfg.GetCluster("")
75 key, err := ssh.ParsePrivateKey([]byte(cluster.Containers.DispatchPrivateKey))
77 err = fmt.Errorf("error parsing configured Containers.DispatchPrivateKey: %s", err)
80 driver, ok := dispatchcloud.Drivers[cluster.Containers.CloudVMs.Driver]
82 err = fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
86 *imageID = cluster.Containers.CloudVMs.ImageID
88 it, err := chooseInstanceType(cluster, *instanceType)
92 tags := cloud.SharedResourceTags(cluster.Containers.CloudVMs.ResourceTags)
93 tagKeyPrefix := cluster.Containers.CloudVMs.TagKeyPrefix
94 tags[tagKeyPrefix+"CloudTestPID"] = fmt.Sprintf("%d", os.Getpid())
98 TagKeyPrefix: tagKeyPrefix,
99 SetID: cloud.InstanceSetID(*instanceSetID),
100 DestroyExisting: *destroyExisting,
101 ProbeInterval: cluster.Containers.CloudVMs.ProbeInterval.Duration(),
102 SyncInterval: cluster.Containers.CloudVMs.SyncInterval.Duration(),
103 TimeoutBooting: cluster.Containers.CloudVMs.TimeoutBooting.Duration(),
105 DriverParameters: cluster.Containers.CloudVMs.DriverParameters,
106 ImageID: cloud.ImageID(*imageID),
109 SSHPort: cluster.Containers.CloudVMs.SSHPort,
110 BootProbeCommand: cluster.Containers.CloudVMs.BootProbeCommand,
111 ShellCommand: *shellCommand,
112 PauseBeforeDestroy: func() {
113 if *pauseBeforeDestroy {
114 logger.Info("waiting for operator to press Enter")
115 fmt.Fprint(stderr, "Press Enter to continue: ")
116 bufio.NewReader(stdin).ReadString('\n')
125 func defaultInstanceSetID() string {
127 if u, err := user.Current(); err == nil {
128 username = u.Username
130 hostname, _ := os.Hostname()
131 return fmt.Sprintf("cloudtest-%s@%s", username, hostname)
134 // Return the named instance type, or the cheapest type if name=="".
135 func chooseInstanceType(cluster *arvados.Cluster, name string) (arvados.InstanceType, error) {
136 if len(cluster.InstanceTypes) == 0 {
137 return arvados.InstanceType{}, errors.New("no instance types are configured")
138 } else if name == "" {
140 var best arvados.InstanceType
141 for _, it := range cluster.InstanceTypes {
142 if first || best.Price > it.Price {
148 } else if it, ok := cluster.InstanceTypes[name]; !ok {
149 return it, fmt.Errorf("requested instance type %q is not configured", name)