// RunHistory is the handler for 'scw history' func RunHistory(ctx CommandContext, args HistoryArgs) error { imageID, err := ctx.API.GetImageID(args.Image) if err != nil { return err } image, err := ctx.API.GetImage(imageID.Identifier) if err != nil { return fmt.Errorf("cannot get image %s: %v", imageID.Identifier, err) } if args.Quiet { fmt.Fprintln(ctx.Stdout, imageID.Identifier) return nil } w := tabwriter.NewWriter(ctx.Stdout, 10, 1, 3, ' ', 0) defer w.Flush() fmt.Fprintf(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\n") identifier := utils.TruncIf(image.Identifier, 8, !args.NoTrunc) creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", image.CreationDate) if err != nil { return fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) } creationDateStr := units.HumanDuration(time.Now().UTC().Sub(creationDate)) volumeName := utils.TruncIf(image.RootVolume.Name, 25, !args.NoTrunc) size := units.HumanSize(float64(image.RootVolume.Size)) fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", identifier, creationDateStr, volumeName, size) return nil }
func runBilling(cmd *Command, rawArgs []string) error { if billingHelp { return cmd.PrintUsage() } if len(rawArgs) > 0 { return cmd.PrintShortUsage() } // cli parsing args := commands.PsArgs{ NoTrunc: billingNoTrunc, } ctx := cmd.GetContext(rawArgs) logrus.Warn("") logrus.Warn("Warning: 'scw _billing' is a work-in-progress price estimation tool") logrus.Warn("For real usage, visit https://cloud.scaleway.com/#/billing") logrus.Warn("") // table w := tabwriter.NewWriter(ctx.Stdout, 20, 1, 3, ' ', 0) defer w.Flush() fmt.Fprintf(w, "ID\tNAME\tSTARTED\tMONTH PRICE\n") // servers servers, err := cmd.API.GetServers(true, 0) if err != nil { return err } totalMonthPrice := new(big.Rat) for _, server := range *servers { if server.State != "running" { continue } commercialType := strings.ToLower(server.CommercialType) shortID := utils.TruncIf(server.Identifier, 8, !args.NoTrunc) shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !args.NoTrunc) modificationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.ModificationDate) modificationAgo := time.Now().UTC().Sub(modificationTime) shortModificationDate := units.HumanDuration(modificationAgo) usage := pricing.NewUsageByPath(fmt.Sprintf("/compute/%s/run", commercialType)) usage.SetStartEnd(modificationTime, time.Now().UTC()) totalMonthPrice = totalMonthPrice.Add(totalMonthPrice, usage.Total()) fmt.Fprintf(w, "server/%s/%s\t%s\t%s\t%s\n", commercialType, shortID, shortName, shortModificationDate, usage.TotalString()) } fmt.Fprintf(w, "TOTAL\t\t\t%s\n", pricing.PriceString(totalMonthPrice, "EUR")) 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 }
// RunSearch is the handler for 'scw search' func RunSearch(ctx CommandContext, args SearchArgs) error { // FIXME: parallelize API calls term := strings.ToLower(args.Term) w := tabwriter.NewWriter(ctx.Stdout, 10, 1, 3, ' ', 0) defer w.Flush() fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") var entries = []api.ScalewayImageInterface{} images, err := ctx.API.GetImages() if err != nil { return fmt.Errorf("unable to fetch images from the Scaleway API: %v", err) } for _, val := range *images { if fuzzy.Match(term, strings.ToLower(val.Name)) { entries = append(entries, api.ScalewayImageInterface{ Type: "image", Name: val.Name, Public: val.Public, }) } } snapshots, err := ctx.API.GetSnapshots() if err != nil { return fmt.Errorf("unable to fetch snapshots from the Scaleway API: %v", err) } for _, val := range *snapshots { if fuzzy.Match(term, strings.ToLower(val.Name)) { entries = append(entries, api.ScalewayImageInterface{ Type: "snapshot", Name: val.Name, Public: false, }) } } for _, image := range entries { // name field name := utils.TruncIf(utils.Wordify(image.Name), 45, !args.NoTrunc) // description field var description string switch image.Type { case "image": if image.Public { description = "public image" } else { description = "user image" } case "snapshot": description = "user snapshot" } description = utils.TruncIf(utils.Wordify(description), 45, !args.NoTrunc) // official field var official string if image.Public { official = "[OK]" } else { official = "" } fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\n", name, description, 0, official, "") } return nil }
// RunPs is the handler for 'scw ps' func RunPs(ctx CommandContext, args PsArgs) error { limit := args.NLast if args.Latest { limit = 1 } filterState := args.Filters["state"] // FIXME: if filter state is defined, try to optimize the query all := args.All || args.NLast > 0 || args.Latest || filterState != "" servers, err := ctx.API.GetServers(all, limit) if err != nil { return fmt.Errorf("Unable to fetch servers from the Scaleway API: %v", err) } for key, value := range args.Filters { switch key { case "state", "name", "tags", "image", "ip": 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, "SERVER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAME\n") } for _, server := range *servers { // filtering for key, value := range args.Filters { switch key { case "state": if value != server.State { goto skipServer } case "name": if fuzzy.RankMatch(strings.ToLower(value), strings.ToLower(server.Name)) == -1 { goto skipServer } case "tags": found := false for _, tag := range server.Tags { if tag == value { found = true continue } } if !found { goto skipServer } case "image": imageID := ctx.API.GetImageID(value, true) if imageID != server.Image.Identifier { goto skipServer } case "ip": if value != server.PublicAddress.IP { goto skipServer } } } if args.Quiet { fmt.Fprintf(w, "%s\n", server.Identifier) } else { shortID := utils.TruncIf(server.Identifier, 8, !args.NoTrunc) shortImage := utils.TruncIf(utils.Wordify(server.Image.Name), 25, !args.NoTrunc) shortName := utils.TruncIf(utils.Wordify(server.Name), 25, !args.NoTrunc) creationTime, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", server.CreationDate) shortCreationDate := units.HumanDuration(time.Now().UTC().Sub(creationTime)) port := server.PublicAddress.IP fmt.Fprintf(w, "%s\t%s\t\t%s\t%s\t%s\t%s\n", shortID, shortImage, shortCreationDate, server.State, port, shortName) } skipServer: continue } return nil }
// RunImages is the handler for 'scw images' func RunImages(ctx CommandContext, args ImagesArgs) error { wg := sync.WaitGroup{} chEntries := make(chan api.ScalewayImageInterface) errChan := make(chan error, 10) var entries = []api.ScalewayImageInterface{} filterType := args.Filters["type"] if filterType == "" || filterType == "image" { wg.Add(1) go func() { defer wg.Done() images, err := ctx.API.GetImages() if err != nil { errChan <- fmt.Errorf("unable to fetch images from the Scaleway API: %v", err) return } for _, val := range *images { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { errChan <- fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) return } archs := []string{} for _, version := range val.Versions { if val.CurrentPublicVersion == version.ID { for _, local := range version.LocalImages { archs = append(archs, local.Arch) } break } } chEntries <- api.ScalewayImageInterface{ Type: "image", CreationDate: creationDate, Identifier: val.CurrentPublicVersion, Name: val.Name, Tag: "latest", Organization: val.Organization.ID, Public: val.Public, // FIXME the region should not be hardcoded Region: "fr-1", Archs: archs, } } }() } if args.All || filterType != "" { if filterType == "" || filterType == "snapshot" { wg.Add(1) go func() { defer wg.Done() snapshots, err := ctx.API.GetSnapshots() if err != nil { errChan <- fmt.Errorf("unable to fetch snapshots from the Scaleway API: %v", err) return } for _, val := range *snapshots { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { errChan <- fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) return } chEntries <- api.ScalewayImageInterface{ Type: "snapshot", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Tag: "<snapshot>", VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, // FIXME the region should not be hardcoded Region: "fr-1", } } }() } if filterType == "" || filterType == "bootscript" { wg.Add(1) go func() { defer wg.Done() bootscripts, err := ctx.API.GetBootscripts() if err != nil { errChan <- fmt.Errorf("unable to fetch bootscripts from the Scaleway API: %v", err) return } for _, val := range *bootscripts { chEntries <- api.ScalewayImageInterface{ Type: "bootscript", Identifier: val.Identifier, Name: val.Title, Tag: "<bootscript>", Public: false, // FIXME the region should not be hardcoded Region: "fr-1", Archs: []string{val.Arch}, } } }() } if filterType == "" || filterType == "volume" { wg.Add(1) go func() { defer wg.Done() volumes, err := ctx.API.GetVolumes() if err != nil { errChan <- fmt.Errorf("unable to fetch volumes from the Scaleway API: %v", err) return } for _, val := range *volumes { creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", val.CreationDate) if err != nil { errChan <- fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) return } chEntries <- api.ScalewayImageInterface{ Type: "volume", CreationDate: creationDate, Identifier: val.Identifier, Name: val.Name, Tag: "<volume>", VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, // FIXME the region should not be hardcoded Region: "fr-1", } } }() } } go func() { wg.Wait() close(chEntries) }() for { if entry, ok := <-chEntries; !ok { break } else { entries = append(entries, entry) } } select { case err := <-errChan: return err default: 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\tREGION\tARCH\n") } sort.Sort(api.ByCreationDate(entries)) for _, image := range entries { if image.Identifier == "" { continue } 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 string if image.CreationDate.IsZero() { creationDate = "n/a" } else { creationDate = units.HumanDuration(time.Now().UTC().Sub(image.CreationDate)) } if len(image.Archs) == 0 { image.Archs = []string{"n/a"} } sort.Strings(image.Archs) fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%v\n", shortName, tag, shortID, creationDate, image.Region, image.Archs) } skipimage: continue } return nil }