// restartIdentifiers resolves server IDs, restarts, and waits for them to be ready (-w) func restartIdentifiers(ctx CommandContext, wait bool, servers []string, cr chan string) { var wg sync.WaitGroup for _, needle := range servers { wg.Add(1) go func(needle string) { defer wg.Done() server := ctx.API.GetServerID(needle) res := server err := ctx.API.PostServerAction(server, "reboot") if err != nil { if err.Error() != "server is being stopped or rebooted" { logrus.Errorf("failed to restart server %s: %s", server, err) } res = "" } else { if wait { // FIXME: handle gateway api.WaitForServerReady(ctx.API, server, "") } } cr <- res }(needle) } wg.Wait() close(cr) }
// GetTotalUsedFds Returns the number of used File Descriptors by // reading it via /proc filesystem. func GetTotalUsedFds() int { if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) } else { return len(fds) } return -1 }
// DeleteServerSafe tries to delete a server using multiple ways func (a *ScalewayAPI) DeleteServerSafe(serverID string) error { // FIXME: also delete attached volumes and ip address err := a.DeleteServer(serverID) if err == nil { logrus.Infof("Server '%s' successfuly deleted", serverID) return nil } err = a.PostServerAction(serverID, "terminate") if err == nil { logrus.Infof("Server '%s' successfuly terminated", serverID) return nil } logrus.Errorf("Failed to delete server %s", serverID) logrus.Errorf("Try to run 'scw rm %s' later", serverID) return err }
func showResolverResults(needle string, results ScalewayResolverResults) error { log.Errorf("Too many candidates for %s (%d)", needle, len(results)) w := tabwriter.NewWriter(os.Stderr, 20, 1, 3, ' ', 0) defer w.Flush() sort.Sort(results) for _, result := range results { fmt.Fprintf(w, "- %s\t%s\t%s\n", result.TruncIdentifier(), result.CodeName(), result.Name) } return nil }
// DeleteServerSafe tries to delete a server using multiple ways func (a *ScalewayAPI) DeleteServerSafe(serverID string) error { // FIXME: also delete attached volumes and ip address // FIXME: call delete and stop -t in parallel to speed up process err := a.DeleteServer(serverID) if err == nil { logrus.Infof("Server '%s' successfuly deleted", serverID) return nil } err = a.PostServerAction(serverID, "terminate") if err == nil { logrus.Infof("Server '%s' successfuly terminated", serverID) return nil } // FIXME: retry in a loop until timeout or Control+C logrus.Errorf("Failed to delete server %s", serverID) logrus.Errorf("Try to run 'scw rm -f %s' later", serverID) return err }
// uploadSSHKeys uploads an SSH Key func uploadSSHKeys(apiConnection *api.ScalewayAPI, newKey string) { user, err := apiConnection.GetUser() if err != nil { logrus.Errorf("Unable to contact ScalewayAPI: %s", err) } else { user.SSHPublicKeys = append(user.SSHPublicKeys, api.ScalewayKeyDefinition{Key: strings.Trim(newKey, "\n")}) SSHKeys := api.ScalewayUserPatchSSHKeyDefinition{ SSHPublicKeys: user.SSHPublicKeys, } userID, err := apiConnection.GetUserID() if err != nil { logrus.Errorf("Unable to get userID: %s", err) } else { if err = apiConnection.PatchUserSSHKey(userID, SSHKeys); err != nil { logrus.Errorf("Unable to patch SSHkey: %v", err) } } } }
// RunRmi is the handler for 'scw rmi' func RunRmi(ctx CommandContext, args RmiArgs) error { hasError := false for _, needle := range args.Images { image := ctx.API.GetImageID(needle, true) err := ctx.API.DeleteImage(image) if err != nil { logrus.Errorf("failed to delete image %s: %s", image, err) hasError = true } else { fmt.Fprintln(ctx.Stdout, needle) } } if hasError { return fmt.Errorf("at least 1 image failed to be removed") } return nil }
// RunRm is the handler for 'scw rm' func RunRm(ctx CommandContext, args RmArgs) error { hasError := false for _, needle := range args.Servers { server := ctx.API.GetServerID(needle) err := ctx.API.DeleteServer(server) if err != nil { logrus.Errorf("failed to delete server %s: %s", server, err) hasError = true } else { fmt.Fprintln(ctx.Stdout, needle) } } if hasError { return fmt.Errorf("at least 1 server failed to be removed") } return nil }
// RunWait is the handler for 'scw wait' func RunWait(ctx CommandContext, args WaitArgs) error { hasError := false for _, needle := range args.Servers { serverIdentifier := ctx.API.GetServerID(needle) _, err := api.WaitForServerStopped(ctx.API, serverIdentifier) if err != nil { logrus.Errorf("failed to wait for server %s: %v", serverIdentifier, err) hasError = true } } if hasError { return fmt.Errorf("at least 1 server failed to be stopped") } return nil }
// RunRestart is the handler for 'scw restart' func RunRestart(ctx CommandContext, args RestartArgs) error { hasError := false for _, needle := range args.Servers { server := ctx.API.GetServerID(needle) err := ctx.API.PostServerAction(server, "reboot") if err != nil { if err.Error() != "server is being stopped or rebooted" { logrus.Errorf("failed to restart server %s: %s", server, err) hasError = true } } else { fmt.Fprintln(ctx.Stdout, needle) } if hasError { return fmt.Errorf("at least 1 server failed to restart") } } return nil }
// GetIdentifier returns a an identifier if the resolved needles only match one element, else, it exists the program func GetIdentifier(api *ScalewayAPI, needle string) *ScalewayResolverResult { idents := ResolveIdentifier(api, needle) if len(idents) == 1 { return &idents[0] } if len(idents) == 0 { log.Fatalf("No such identifier: %s", needle) } log.Errorf("Too many candidates for %s (%d)", needle, len(idents)) sort.Sort(idents) for _, identifier := range idents { // FIXME: also print the name fmt.Fprintf(os.Stderr, "- %s\n", identifier.Identifier) } os.Exit(1) return nil }
// RunStart is the handler for 'scw start' func RunStart(ctx CommandContext, args StartArgs) error { hasError := false errChan := make(chan error) successChan := make(chan bool) remainingItems := len(args.Servers) for _, needle := range args.Servers { go api.StartServerOnce(ctx.API, needle, args.Wait, successChan, errChan) } if args.Timeout > 0 { go func() { time.Sleep(time.Duration(args.Timeout*1000) * time.Millisecond) // FIXME: avoid use of fatalf logrus.Fatalf("Operation timed out") }() } for { select { case _ = <-successChan: remainingItems-- case err := <-errChan: logrus.Errorf(fmt.Sprintf("%s", err)) remainingItems-- hasError = true } if remainingItems == 0 { break } } if hasError { return fmt.Errorf("at least 1 server failed to start") } return nil }
// RunStop is the handler for 'scw stop' func RunStop(ctx CommandContext, args StopArgs) error { // FIXME: parallelize stop when stopping multiple servers hasError := false for _, needle := range args.Servers { serverID := ctx.API.GetServerID(needle) action := "poweroff" if args.Terminate { action = "terminate" } err := ctx.API.PostServerAction(serverID, action) if err != nil { if err.Error() != "server should be running" && err.Error() != "server is being stopped or rebooted" { logrus.Warningf("failed to stop server %s: %s", serverID, err) hasError = true } } else { if args.Wait { // We wait for 10 seconds which is the minimal amount of time needed for a server to stop time.Sleep(10 * time.Second) _, err = api.WaitForServerStopped(ctx.API, serverID) if err != nil { logrus.Errorf("failed to wait for server %s: %v", serverID, err) hasError = true } } if args.Terminate { ctx.API.Cache.RemoveServer(serverID) } fmt.Fprintln(ctx.Stdout, needle) } } if hasError { return fmt.Errorf("at least 1 server failed to be stopped") } return nil }
// RunLogin is the handler for 'scw login' func RunLogin(ctx CommandContext, args LoginArgs) error { if args.Organization == "" || args.Token == "" { var err error args.Organization, args.Token, err = connectAPI() if err != nil { return err } } cfg := &config.Config{ ComputeAPI: api.ComputeAPI, AccountAPI: api.AccountAPI, Organization: strings.Trim(args.Organization, "\n"), Token: strings.Trim(args.Token, "\n"), } apiConnection, err := api.NewScalewayAPI(cfg.ComputeAPI, cfg.AccountAPI, cfg.Organization, cfg.Token) if err != nil { return fmt.Errorf("Unable to create ScalewayAPI: %s", err) } err = apiConnection.CheckCredentials() if err != nil { return fmt.Errorf("Unable to contact ScalewayAPI: %s", err) } if !args.SkipSSHKey { if err := selectKey(&args); err != nil { logrus.Errorf("Unable to select a key: %v", err) } else { if args.SSHKey != "" { uploadSSHKeys(apiConnection, args.SSHKey) } } } return cfg.Save() }
// InspectIdentifiers inspects identifiers concurrently func InspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj chan InspectIdentifierResult) { var wg sync.WaitGroup for { idents, ok := <-ci if !ok { break } if len(idents.Identifiers) != 1 { if len(idents.Identifiers) == 0 { log.Errorf("Unable to resolve identifier %s", idents.Needle) } else { showResolverResults(idents.Needle, idents.Identifiers) } } else { ident := idents.Identifiers[0] wg.Add(1) go func() { if ident.Type == IdentifierServer { server, err := api.GetServer(ident.Identifier) if err == nil { cj <- InspectIdentifierResult{ Type: ident.Type, Object: server, } } } else if ident.Type == IdentifierImage { image, err := api.GetImage(ident.Identifier) if err == nil { cj <- InspectIdentifierResult{ Type: ident.Type, Object: image, } } } else if ident.Type == IdentifierSnapshot { snap, err := api.GetSnapshot(ident.Identifier) if err == nil { cj <- InspectIdentifierResult{ Type: ident.Type, Object: snap, } } } else if ident.Type == IdentifierVolume { volume, err := api.GetVolume(ident.Identifier) if err == nil { cj <- InspectIdentifierResult{ Type: ident.Type, Object: volume, } } } else if ident.Type == IdentifierBootscript { bootscript, err := api.GetBootscript(ident.Identifier) if err == nil { cj <- InspectIdentifierResult{ Type: ident.Type, Object: bootscript, } } } wg.Done() }() } } wg.Wait() close(cj) }
// RunInspect is the handler for 'scw inspect' func RunInspect(ctx CommandContext, args InspectArgs) error { nbInspected := 0 ci := make(chan api.ScalewayResolvedIdentifier) cj := make(chan api.InspectIdentifierResult) go api.ResolveIdentifiers(ctx.API, args.Identifiers, ci) go api.InspectIdentifiers(ctx.API, ci, cj) if args.Browser { // --browser will open links in the browser for { data, isOpen := <-cj if !isOpen { break } switch data.Type { case api.IdentifierServer: err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/servers/%s", data.Object.(*api.ScalewayServer).Identifier)) if err != nil { return fmt.Errorf("cannot open browser: %v", err) } nbInspected++ case api.IdentifierImage: err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/images/%s", data.Object.(*api.ScalewayImage).Identifier)) if err != nil { return fmt.Errorf("cannot open browser: %v", err) } nbInspected++ case api.IdentifierVolume: err := open.Start(fmt.Sprintf("https://cloud.scaleway.com/#/volumes/%s", data.Object.(*api.ScalewayVolume).Identifier)) if err != nil { return fmt.Errorf("cannot open browser: %v", err) } nbInspected++ case api.IdentifierSnapshot: logrus.Errorf("Cannot use '--browser' option for snapshots") case api.IdentifierBootscript: logrus.Errorf("Cannot use '--browser' option for bootscripts") } } } else { // without --browser option, inspect will print object info to the terminal res := "[" for { data, isOpen := <-cj if !isOpen { break } if args.Format == "" { dataB, err := json.MarshalIndent(data.Object, "", " ") if err == nil { if nbInspected != 0 { res += ",\n" } res += string(dataB) nbInspected++ } } else { tmpl, err := template.New("").Funcs(api.FuncMap).Parse(args.Format) if err != nil { return fmt.Errorf("format parsing error: %v", err) } err = tmpl.Execute(ctx.Stdout, data.Object) if err != nil { return fmt.Errorf("format execution error: %v", err) } fmt.Fprint(ctx.Stdout, "\n") nbInspected++ } } res += "]" if args.Format == "" { if ctx.Getenv("SCW_SENSITIVE") != "1" { res = ctx.API.HideAPICredentials(res) } fmt.Fprintln(ctx.Stdout, res) } } if len(args.Identifiers) != nbInspected { return fmt.Errorf("at least 1 item failed to be inspected") } return nil }