// RunRestart is the handler for 'scw restart' func RunRestart(ctx CommandContext, args RestartArgs) error { if args.Wait && args.Timeout > 0 { go func() { time.Sleep(time.Duration(args.Timeout*1000) * time.Millisecond) // FIXME: avoid use of fatalf logrus.Fatalf("Operation timed out") }() } cr := make(chan string) go restartIdentifiers(ctx, args.Wait, args.Servers, cr) done := false hasError := false for !done { select { case uuid, more := <-cr: if !more { done = true break } if len(uuid) > 0 { fmt.Fprintln(ctx.Stdout, uuid) } else { hasError = true } } } if hasError { return fmt.Errorf("at least 1 server failed to restart") } return nil }
func main() { ec, err := cli.Start(os.Args[1:], nil) if err != nil { logrus.Fatalf("%s", err) } os.Exit(ec) }
// GetBootscriptID returns exactly one bootscript matching or dies func (s *ScalewayAPI) GetBootscriptID(needle string) string { // Parses optional type prefix, i.e: "bootscript:name" -> "name" _, needle = parseNeedle(needle) bootscripts, err := s.ResolveBootscript(needle) if err != nil { log.Fatalf("Unable to resolve bootscript %s: %s", needle, err) } if len(bootscripts) == 1 { return bootscripts[0].Identifier } if len(bootscripts) == 0 { log.Fatalf("No such bootscript: %s", needle) } showResolverResults(needle, bootscripts) os.Exit(1) return "" }
// GetSnapshotID returns exactly one snapshot matching or dies func (s *ScalewayAPI) GetSnapshotID(needle string) string { // Parses optional type prefix, i.e: "snapshot:name" -> "name" _, needle = parseNeedle(needle) snapshots, err := s.ResolveSnapshot(needle) if err != nil { log.Fatalf("Unable to resolve snapshot %s: %s", needle, err) } if len(snapshots) == 1 { return snapshots[0].Identifier } if len(snapshots) == 0 { log.Fatalf("No such snapshot: %s", needle) } showResolverResults(needle, snapshots) os.Exit(1) return "" }
// GetImageID returns exactly one image matching or dies func (s *ScalewayAPI) GetImageID(needle string, exitIfMissing bool) string { // Parses optional type prefix, i.e: "image:name" -> "name" _, needle = parseNeedle(needle) images, err := s.ResolveImage(needle) if err != nil { log.Fatalf("Unable to resolve image %s: %s", needle, err) } if len(images) == 1 { return images[0].Identifier } if len(images) == 0 { if exitIfMissing { log.Fatalf("No such image: %s", needle) } else { return "" } } showResolverResults(needle, images) os.Exit(1) return "" }
func promptUser(prompt string, output *string, echo bool) { // FIXME: should use stdin/stdout from command context fmt.Fprintf(os.Stdout, prompt) os.Stdout.Sync() if !echo { b, err := terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { logrus.Fatalf("Unable to prompt for password: %s", err) } *output = string(b) fmt.Fprintf(os.Stdout, "\n") } else { reader := bufio.NewReader(os.Stdin) *output, _ = reader.ReadString('\n') } }
// 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 }
func runHelp(cmd *Command, rawArgs []string) error { if waitHelp { return cmd.PrintUsage() } if len(rawArgs) > 1 { return cmd.PrintShortUsage() } if len(rawArgs) == 1 { name := rawArgs[0] for _, command := range Commands { if command.Name() == name { return command.PrintUsage() } } logrus.Fatalf("Unknown help topic `%s`. Run 'scw help'.", name) } else { t := template.New("top") template.Must(t.Parse(helpTemplate)) ctx := cmd.GetContext(rawArgs) return t.Execute(ctx.Stdout, Commands) } return nil }
func runPatch(cmd *Command, args []string) error { if patchHelp { return cmd.PrintUsage() } if len(args) != 2 { return cmd.PrintShortUsage() } // Parsing FIELD=VALUE updateParts := strings.SplitN(args[1], "=", 2) if len(updateParts) != 2 { return cmd.PrintShortUsage() } fieldName := updateParts[0] newValue := updateParts[1] changes := 0 ident := api.GetIdentifier(cmd.API, args[0]) switch ident.Type { case api.IdentifierServer: currentServer, err := cmd.API.GetServer(ident.Identifier) if err != nil { log.Fatalf("Cannot get server %s: %v", ident.Identifier, err) } var payload api.ScalewayServerPatchDefinition switch fieldName { case "state_detail": log.Debugf("%s=%s => %s=%s", fieldName, currentServer.StateDetail, fieldName, newValue) if currentServer.StateDetail != newValue { changes++ payload.StateDetail = &newValue } case "name": log.Warnf("To rename a server, Use 'scw rename'") log.Debugf("%s=%s => %s=%s", fieldName, currentServer.StateDetail, fieldName, newValue) if currentServer.Name != newValue { changes++ payload.Name = &newValue } case "bootscript": log.Debugf("%s=%s => %s=%s", fieldName, currentServer.Bootscript.Identifier, fieldName, newValue) if currentServer.Bootscript.Identifier != newValue { changes++ payload.Bootscript.Identifier = newValue } case "security_group": log.Debugf("%s=%s => %s=%s", fieldName, currentServer.SecurityGroup.Identifier, fieldName, newValue) if currentServer.SecurityGroup.Identifier != newValue { changes++ payload.SecurityGroup.Identifier = newValue } case "tags": newTags := strings.Split(newValue, " ") log.Debugf("%s=%s => %s=%s", fieldName, currentServer.Tags, fieldName, newTags) // fixme test equality with reflect.DeepEqual ? changes++ payload.Tags = &newTags default: log.Fatalf("'_patch server %s=' not implemented", fieldName) } // FIXME: volumes, tags, dynamic_ip_required if changes > 0 { log.Debugf("updating server: %d change(s)", changes) err = cmd.API.PatchServer(ident.Identifier, payload) } else { log.Debugf("no changes, not updating server") } if err != nil { log.Fatalf("Cannot update server: %v", err) } default: log.Fatalf("_patch not implemented for this kind of object") } fmt.Println(ident.Identifier) return nil }
// RunImages is the handler for 'scw images' func RunImages(ctx CommandContext, args ImagesArgs) error { wg := sync.WaitGroup{} chEntries := make(chan api.ScalewayImageInterface) var entries = []api.ScalewayImageInterface{} filterType := args.Filters["type"] // FIXME: remove log.Fatalf in routines if filterType == "" || filterType == "image" { wg.Add(1) go func() { defer wg.Done() images, err := ctx.API.GetImages() if err != nil { logrus.Fatalf("unable to fetch images from the Scaleway API: %v", err) } for _, val := range *images { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "image", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Public: val.Public, Tag: "latest", VirtualSize: float64(val.RootVolume.Size), Organization: val.Organization, } } }() } if args.All || filterType != "" { if filterType == "" || filterType == "snapshot" { wg.Add(1) go func() { defer wg.Done() snapshots, err := ctx.API.GetSnapshots() if err != nil { logrus.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) } for _, val := range *snapshots { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "snapshot", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Tag: "<snapshot>", VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, } } }() } if filterType == "" || filterType == "bootscript" { wg.Add(1) go func() { defer wg.Done() bootscripts, err := ctx.API.GetBootscripts() if err != nil { logrus.Fatalf("unable to fetch bootscripts from the Scaleway API: %v", err) } for _, val := range *bootscripts { chEntries <- api.ScalewayImageInterface{ Type: "bootscript", Identifier: val.Identifier, Name: val.Title, Tag: "<bootscript>", Public: false, } } }() } if filterType == "" || filterType == "volume" { wg.Add(1) go func() { defer wg.Done() volumes, err := ctx.API.GetVolumes() if err != nil { logrus.Fatalf("unable to fetch volumes from the Scaleway API: %v", err) } for _, val := range *volumes { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) } chEntries <- api.ScalewayImageInterface{ Type: "volume", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Tag: "<volume>", VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, } } }() } } go func() { wg.Wait() close(chEntries) }() done := false for { select { case entry, ok := <-chEntries: if !ok { done = true break } entries = append(entries, entry) } if done { break } } for key, value := range args.Filters { switch key { case "organization", "type", "name", "public": continue default: logrus.Warnf("Unknown filter: '%s=%s'", key, value) } } w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush() if !args.Quiet { fmt.Fprintf(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE\n") } sort.Sort(api.ByCreationDate(entries)) for _, image := range entries { for key, value := range args.Filters { switch key { case "type": if value != image.Type { goto skipimage } case "organization": switch value { case "me": value = ctx.API.Organization case "official-distribs": value = "a283af0b-d13e-42e1-a43f-855ffbf281ab" case "official-apps": value = "c3884e19-7a3e-4b69-9db8-50e7f902aafc" } if image.Organization != value { goto skipimage } case "name": if fuzzy.RankMatch(strings.ToLower(value), strings.ToLower(image.Name)) == -1 { goto skipimage } case "public": if (value == "true" && !image.Public) || (value == "false" && image.Public) { goto skipimage } } } if args.Quiet { fmt.Fprintf(ctx.Stdout, "%s\n", image.Identifier) } else { tag := image.Tag shortID := utils.TruncIf(image.Identifier, 8, !args.NoTrunc) name := utils.Wordify(image.Name) if !image.Public && image.Type == "image" { name = "user/" + name } shortName := utils.TruncIf(name, 25, !args.NoTrunc) var creationDate, virtualSize string if image.CreationDate.IsZero() { creationDate = "n/a" } else { creationDate = units.HumanDuration(time.Now().UTC().Sub(image.CreationDate)) } if image.VirtualSize == 0 { virtualSize = "n/a" } else { virtualSize = units.HumanSize(image.VirtualSize) } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", shortName, tag, shortID, creationDate, virtualSize) } skipimage: continue } return nil }
func runCompletion(cmd *Command, args []string) error { if completionHelp { return cmd.PrintUsage() } if len(args) != 1 { return cmd.PrintShortUsage() } category := args[0] elements := []string{} switch category { case "servers-all": for identifier, name := range cmd.API.Cache.Servers { elements = append(elements, identifier, wordifyName(name, "server")) } case "servers-names": for _, name := range cmd.API.Cache.Servers { elements = append(elements, wordifyName(name, "server")) } case "images-all": for identifier, name := range cmd.API.Cache.Images { elements = append(elements, identifier, wordifyName(name, "image")) } case "images-names": for _, name := range cmd.API.Cache.Images { elements = append(elements, wordifyName(name, "image")) } case "volumes-all": for identifier, name := range cmd.API.Cache.Volumes { elements = append(elements, identifier, wordifyName(name, "volume")) } case "volumes-names": for _, name := range cmd.API.Cache.Volumes { elements = append(elements, wordifyName(name, "volume")) } case "snapshots-all": for identifier, name := range cmd.API.Cache.Snapshots { elements = append(elements, identifier, wordifyName(name, "snapshot")) } case "snapshots-names": for _, name := range cmd.API.Cache.Snapshots { elements = append(elements, wordifyName(name, "snapshot")) } case "bootscripts-all": for identifier, name := range cmd.API.Cache.Bootscripts { elements = append(elements, identifier, wordifyName(name, "bootscript")) } case "bootscripts-names": for _, name := range cmd.API.Cache.Bootscripts { elements = append(elements, wordifyName(name, "bootscript")) } default: logrus.Fatalf("Unhandled category of completion: %s", category) } sort.Strings(elements) fmt.Println(strings.Join(utils.RemoveDuplicates(elements), "\n")) return nil }