github.com/scaleway/scaleway-cli@v1.11.1/pkg/commands/run.go (about) 1 // Copyright (C) 2015 Scaleway. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE.md file. 4 5 package commands 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/Sirupsen/logrus" 16 "github.com/scaleway/scaleway-cli/pkg/api" 17 "github.com/scaleway/scaleway-cli/pkg/config" 18 "github.com/scaleway/scaleway-cli/pkg/utils" 19 ) 20 21 // RunArgs are flags for the `Run` function 22 type RunArgs struct { 23 Bootscript string 24 Command []string 25 Gateway string 26 Image string 27 Name string 28 IP string 29 Tags []string 30 Volumes []string 31 Userdata string 32 CommercialType string 33 State string 34 SSHUser string 35 Timeout int64 36 SSHPort int 37 AutoRemove bool 38 TmpSSHKey bool 39 ShowBoot bool 40 Detach bool 41 Attach bool 42 IPV6 bool 43 } 44 45 // AddSSHKeyToTags adds the ssh key in the tags 46 func AddSSHKeyToTags(ctx CommandContext, tags *[]string, image string) error { 47 home, err := config.GetHomeDir() 48 if err != nil { 49 return fmt.Errorf("unable to find your home %v", err) 50 } 51 idRsa := filepath.Join(home, ".ssh", "id_rsa") 52 if _, errStat := os.Stat(idRsa); errStat != nil { 53 if os.IsNotExist(errStat) { 54 logrus.Warnln("Unable to find your ~/.ssh/id_rsa") 55 logrus.Warnln("Run 'ssh-keygen -t rsa'") 56 return nil 57 } 58 } 59 idRsa = strings.Join([]string{idRsa, ".pub"}, "") 60 data, err := ioutil.ReadFile(idRsa) 61 if err != nil { 62 return fmt.Errorf("failed to read %v", err) 63 } 64 data[7] = '_' 65 for i := range data { 66 if data[i] == ' ' { 67 data = data[:i] 68 break 69 } 70 } 71 *tags = append(*tags, strings.Join([]string{"AUTHORIZED_KEY", string(data[:])}, "=")) 72 return nil 73 } 74 75 func addUserData(ctx CommandContext, userdatas []string, serverID string) { 76 for i := range userdatas { 77 keyValue := strings.Split(userdatas[i], "=") 78 if len(keyValue) != 2 { 79 logrus.Warn("Bad format: ", userdatas[i]) 80 continue 81 } 82 var data []byte 83 var err error 84 85 // Set userdata 86 if keyValue[1][0] == '@' { 87 data, err = ioutil.ReadFile(keyValue[1][1:]) 88 if err != nil { 89 logrus.Warn("ReadFile: ", err) 90 continue 91 } 92 } else { 93 data = []byte(keyValue[1]) 94 } 95 if err = ctx.API.PatchUserdata(serverID, keyValue[0], data, false); err != nil { 96 logrus.Warn("PatchUserdata: ", err) 97 continue 98 } 99 } 100 } 101 102 func runShowBoot(ctx CommandContext, args RunArgs, serverID, region string, closeTimeout chan struct{}, timeoutExit chan struct{}) error { 103 // Attach to server serial 104 logrus.Info("Attaching to server console ...") 105 gottycli, done, err := utils.AttachToSerial(serverID, ctx.API.Token, ctx.API.ResolveTTYUrl()) 106 if err != nil { 107 close(closeTimeout) 108 return fmt.Errorf("cannot attach to server serial: %v", err) 109 } 110 utils.Quiet(true) 111 notif, gateway, err := waitSSHConnection(ctx, args, serverID) 112 if err != nil { 113 close(closeTimeout) 114 gottycli.ExitLoop() 115 <-done 116 return err 117 } 118 select { 119 case <-timeoutExit: 120 gottycli.ExitLoop() 121 <-done 122 utils.Quiet(false) 123 return fmt.Errorf("Operation timed out") 124 case sshConnection := <-notif: 125 close(closeTimeout) 126 gottycli.ExitLoop() 127 <-done 128 utils.Quiet(false) 129 if sshConnection.err != nil { 130 return sshConnection.err 131 } 132 if fingerprints := ctx.API.GetSSHFingerprintFromServer(serverID); len(fingerprints) > 0 { 133 for i := range fingerprints { 134 fmt.Fprintf(ctx.Stdout, "%s\n", fingerprints[i]) 135 } 136 } 137 server := sshConnection.server 138 logrus.Info("Connecting to server ...") 139 if err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.SSHUser, args.SSHPort, []string{}, false, gateway); err != nil { 140 return fmt.Errorf("Connection to server failed: %v", err) 141 } 142 } 143 return nil 144 } 145 146 // Run is the handler for 'scw run' 147 func Run(ctx CommandContext, args RunArgs) error { 148 if args.Gateway == "" { 149 args.Gateway = ctx.Getenv("SCW_GATEWAY") 150 } 151 152 if args.TmpSSHKey { 153 err := AddSSHKeyToTags(ctx, &args.Tags, args.Image) 154 if err != nil { 155 return err 156 } 157 } 158 env := strings.Join(args.Tags, " ") 159 volume := strings.Join(args.Volumes, " ") 160 161 // create IMAGE 162 logrus.Info("Server creation ...") 163 config := api.ConfigCreateServer{ 164 ImageName: args.Image, 165 Name: args.Name, 166 Bootscript: args.Bootscript, 167 Env: env, 168 AdditionalVolumes: volume, 169 DynamicIPRequired: false, 170 IP: args.IP, 171 CommercialType: args.CommercialType, 172 EnableIPV6: args.IPV6, 173 } 174 if args.IP == "dynamic" || (args.IP == "" && args.Gateway == "") { 175 config.DynamicIPRequired = true 176 config.IP = "" 177 } else if args.IP == "none" || args.IP == "no" || (args.IP == "" && args.Gateway != "") { 178 config.IP = "" 179 } 180 serverID, err := api.CreateServer(ctx.API, &config) 181 if err != nil { 182 return fmt.Errorf("failed to create server: %v", err) 183 } 184 logrus.Infof("Server created: %s", serverID) 185 logrus.Debugf("PublicDNS %s", serverID+api.URLPublicDNS) 186 logrus.Debugf("PrivateDNS %s", serverID+api.URLPrivateDNS) 187 188 if args.AutoRemove { 189 defer ctx.API.DeleteServerForce(serverID) 190 } 191 192 // start SERVER 193 logrus.Info("Server start requested ...") 194 if err = api.StartServer(ctx.API, serverID, false); err != nil { 195 return fmt.Errorf("failed to start server %s: %v", serverID, err) 196 } 197 logrus.Info("Server is starting, this may take up to a minute ...") 198 199 if args.Userdata != "" { 200 addUserData(ctx, strings.Split(args.Userdata, " "), serverID) 201 } 202 // Sync cache on disk 203 ctx.API.Sync() 204 205 if args.Detach { 206 fmt.Fprintln(ctx.Stdout, serverID) 207 return nil 208 } 209 210 closeTimeout := make(chan struct{}) 211 timeoutExit := make(chan struct{}) 212 213 if args.Timeout > 0 { 214 go func() { 215 select { 216 case <-time.After(time.Duration(args.Timeout) * time.Second): 217 close(timeoutExit) 218 case <-closeTimeout: 219 break 220 } 221 }() 222 } 223 if args.State != "" { 224 go func() { 225 for { 226 server, err := ctx.API.GetServer(serverID) 227 if err != nil { 228 logrus.Errorf("%s", err) 229 return 230 } 231 if server.StateDetail == "kernel-started" { 232 err = ctx.API.PatchServer(serverID, api.ScalewayServerPatchDefinition{ 233 StateDetail: &args.State, 234 }) 235 if err != nil { 236 logrus.Errorf("%s", err) 237 } 238 return 239 } 240 time.Sleep(1 * time.Second) 241 } 242 }() 243 } 244 if args.ShowBoot { 245 return runShowBoot(ctx, args, serverID, ctx.API.Region, closeTimeout, timeoutExit) 246 } else if args.Attach { 247 // Attach to server serial 248 logrus.Info("Attaching to server console ...") 249 gottycli, done, err := utils.AttachToSerial(serverID, ctx.API.Token, ctx.API.ResolveTTYUrl()) 250 close(closeTimeout) 251 if err != nil { 252 return fmt.Errorf("cannot attach to server serial: %v", err) 253 } 254 <-done 255 gottycli.Close() 256 } else { 257 notif, gateway, err := waitSSHConnection(ctx, args, serverID) 258 if err != nil { 259 close(closeTimeout) 260 return err 261 } 262 select { 263 case <-timeoutExit: 264 return fmt.Errorf("Operation timed out") 265 case sshConnection := <-notif: 266 close(closeTimeout) 267 if sshConnection.err != nil { 268 return sshConnection.err 269 } 270 if fingerprints := ctx.API.GetSSHFingerprintFromServer(serverID); len(fingerprints) > 0 { 271 for i := range fingerprints { 272 fmt.Fprintf(ctx.Stdout, "%s\n", fingerprints[i]) 273 } 274 } 275 server := sshConnection.server 276 // exec -w SERVER COMMAND ARGS... 277 if len(args.Command) < 1 { 278 logrus.Info("Connecting to server ...") 279 if err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.SSHUser, args.SSHPort, []string{}, false, gateway); err != nil { 280 return fmt.Errorf("Connection to server failed: %v", err) 281 } 282 } else { 283 logrus.Infof("Executing command: %s ...", args.Command) 284 if err = utils.SSHExec(server.PublicAddress.IP, server.PrivateIP, args.SSHUser, args.SSHPort, args.Command, false, gateway); err != nil { 285 return fmt.Errorf("command execution failed: %v", err) 286 } 287 logrus.Info("Command successfully executed") 288 } 289 } 290 } 291 return nil 292 } 293 294 type notifSSHConnection struct { 295 server *api.ScalewayServer 296 err error 297 } 298 299 func waitSSHConnection(ctx CommandContext, args RunArgs, serverID string) (chan notifSSHConnection, string, error) { 300 notif := make(chan notifSSHConnection) 301 // Resolve gateway 302 gateway, err := api.ResolveGateway(ctx.API, args.Gateway) 303 if err != nil { 304 return nil, "", fmt.Errorf("cannot resolve Gateway '%s': %v", args.Gateway, err) 305 } 306 307 // waiting for server to be ready 308 logrus.Debug("Waiting for server to be ready") 309 // We wait for 30 seconds, which is the minimal amount of time needed by a server to boot 310 go func() { 311 server, err := api.WaitForServerReady(ctx.API, serverID, gateway) 312 if err != nil { 313 notif <- notifSSHConnection{ 314 err: fmt.Errorf("cannot get access to server %s: %v", serverID, err), 315 } 316 return 317 } 318 logrus.Debugf("SSH server is available: %s:22", server.PublicAddress.IP) 319 logrus.Info("Server is ready !") 320 notif <- notifSSHConnection{ 321 server: server, 322 } 323 }() 324 return notif, gateway, nil 325 }