Example #1
0
func runInfo(cmd *types.Command, args []string) {
	if infoHelp {
		cmd.PrintUsage()
	}
	if len(args) != 0 {
		cmd.PrintShortUsage()
	}

	// FIXME: fmt.Printf("Servers: %s\n", "quantity")
	// FIXME: fmt.Printf("Images: %s\n", "quantity")
	fmt.Printf("Debug mode (client): %v\n", os.Getenv("DEBUG") != "")

	fmt.Printf("Organization: %s\n", cmd.API.Organization)
	// FIXME: add partially-masked token
	fmt.Printf("API Endpoint: %s\n", os.Getenv("scaleway_api_endpoint"))
	configPath, _ := utils.GetConfigFilePath()
	fmt.Printf("RC file: %s\n", configPath)
	fmt.Printf("User: %s\n", os.Getenv("USER"))
	fmt.Printf("CPUs: %d\n", runtime.NumCPU())
	hostname, _ := os.Hostname()
	fmt.Printf("Hostname: %s\n", hostname)
	cliPath, _ := osext.Executable()
	fmt.Printf("CLI Path: %s\n", cliPath)

	fmt.Printf("Cache: %s\n", cmd.API.Cache.Path)
	fmt.Printf("  Servers: %d\n", cmd.API.Cache.GetNbServers())
	fmt.Printf("  Images: %d\n", cmd.API.Cache.GetNbImages())
	fmt.Printf("  Snapshots: %d\n", cmd.API.Cache.GetNbSnapshots())
	fmt.Printf("  Volumes: %d\n", cmd.API.Cache.GetNbVolumes())
	fmt.Printf("  Bootscripts: %d\n", cmd.API.Cache.GetNbBootscripts())
}
Example #2
0
func runCompletion(cmd *types.Command, args []string) {
	if completionHelp {
		cmd.PrintUsage()
	}
	if len(args) != 1 {
		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:
		log.Fatalf("Unhandled category of completion: %s", category)
	}

	sort.Strings(elements)
	fmt.Println(strings.Join(utils.RemoveDuplicates(elements), "\n"))
}
Example #3
0
func runCommit(cmd *types.Command, args []string) {
	if commitHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	serverID := cmd.API.GetServerID(args[0])
	server, err := cmd.API.GetServer(serverID)
	if err != nil {
		log.Fatalf("Cannot fetch server: %v", err)
	}
	var volume = server.Volumes[fmt.Sprintf("%d", commitVolume)]
	var name string
	if len(args) > 1 {
		name = args[1]
	} else {
		name = volume.Name + "-snapshot"
	}
	snapshot, err := cmd.API.PostSnapshot(volume.Identifier, name)
	if err != nil {
		log.Fatalf("Cannot create snapshot: %v", err)
	}
	fmt.Println(snapshot)
}
Example #4
0
func runRestart(cmd *types.Command, args []string) {
	if restartHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	hasError := false
	for _, needle := range args {
		server := cmd.API.GetServerID(needle)
		err := cmd.API.PostServerAction(server, "reboot")
		if err != nil {
			if err.Error() != "server is being stopped or rebooted" {
				log.Errorf("failed to restart server %s: %s", server, err)
				hasError = true
			}
		} else {
			fmt.Println(needle)
		}
		if hasError {
			os.Exit(1)
		}
	}
}
Example #5
0
func runRun(cmd *types.Command, args []string) {
	if runHelpFlag {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}
	if runAttachFlag && len(args) > 1 {
		log.Fatalf("Cannot use '--attach' and 'COMMAND [ARG...]' at the same time. See 'scw run --help'")
	}

	// create IMAGE
	log.Debugf("Creating a new server")
	serverID, err := api.CreateServer(cmd.API, args[0], runCreateName, runCreateBootscript, runCreateEnv, runCreateVolume)
	if err != nil {
		log.Fatalf("Failed to create server: %v", err)
	}
	log.Debugf("Created server: %s", serverID)

	// start SERVER
	log.Debugf("Starting server")
	err = api.StartServer(cmd.API, serverID, false)
	if err != nil {
		log.Fatalf("Failed to start server %s: %v", serverID, err)
	}
	log.Debugf("Server is booting")

	if runAttachFlag {
		// Attach to server serial
		log.Debugf("Attaching to server console")
		err = utils.AttachToSerial(serverID, cmd.API.Token, true)
		if err != nil {
			log.Fatalf("Cannot attach to server serial: %v", err)
		}
	} else {
		// waiting for server to be ready
		log.Debugf("Waiting for server to be ready")
		// We wait for 30 seconds, which is the minimal amount of time needed by a server to boot
		time.Sleep(30 * time.Second)
		server, err := api.WaitForServerReady(cmd.API, serverID)
		if err != nil {
			log.Fatalf("Cannot get access to server %s: %v", serverID, err)
		}
		log.Debugf("Server is ready: %s", server.PublicAddress.IP)

		// exec -w SERVER COMMAND ARGS...
		log.Debugf("Executing command")
		if len(args) < 2 {
			err = utils.SSHExec(server.PublicAddress.IP, []string{"if [ -x /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi"}, false)
		} else {
			err = utils.SSHExec(server.PublicAddress.IP, args[1:], false)
		}
		if err != nil {
			log.Debugf("Command execution failed: %v", err)
			os.Exit(1)
		}
		log.Debugf("Command successfuly executed")
	}
}
Example #6
0
func runInspect(cmd *types.Command, args []string) {
	if inspectHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	res := "["
	nbInspected := 0
	ci := make(chan api.ScalewayResolvedIdentifier)
	cj := make(chan interface{})
	go api.ResolveIdentifiers(cmd.API, args, ci)
	go api.InspectIdentifiers(cmd.API, ci, cj)
	for {
		data, open := <-cj
		if !open {
			break
		}
		if inspectFormat == "" {
			dataB, err := json.MarshalIndent(data, "", "  ")
			if err == nil {
				if nbInspected != 0 {
					res += ",\n"
				}
				res += string(dataB)
				nbInspected++
			}
		} else {
			tmpl, err := template.New("").Funcs(api.FuncMap).Parse(inspectFormat)
			if err != nil {
				log.Fatalf("Format parsing error: %v", err)
			}

			err = tmpl.Execute(os.Stdout, data)
			if err != nil {
				log.Fatalf("Format execution error: %v", err)
			}
			fmt.Fprint(os.Stdout, "\n")
			nbInspected++
		}
	}
	res += "]"

	if inspectFormat == "" {
		if os.Getenv("SCW_SENSITIVE") != "1" {
			res = cmd.API.HideApiCredentials(res)
		}
		fmt.Println(res)
	}

	if len(args) != nbInspected {
		os.Exit(1)
	}
}
Example #7
0
func runLogin(cmd *types.Command, args []string) {
	if loginHelp {
		cmd.PrintUsage()
	}
	if len(args) != 0 {
		cmd.PrintShortUsage()
	}

	if len(organization) == 0 {
		fmt.Println("You can get your credentials on https://cloud.scaleway.com/#/credentials")
		promptUser("Organization (access key): ", &organization, true)
	}
	if len(token) == 0 {
		promptUser("Token: ", &token, false)
	}

	cfg := &api.Config{
		APIEndPoint:  "https://account.scaleway.com/",
		Organization: strings.Trim(organization, "\n"),
		Token:        strings.Trim(token, "\n"),
	}

	api, err := api.NewScalewayAPI(cfg.APIEndPoint, cfg.Organization, cfg.Token)
	if err != nil {
		log.Fatalf("Unable to create ScalewayAPI: %s", err)
	}
	err = api.CheckCredentials()
	if err != nil {
		log.Fatalf("Unable to contact ScalewayAPI: %s", err)
	}

	scwrcPath, err := utils.GetConfigFilePath()
	if err != nil {
		log.Fatalf("Unable to get scwrc config file path: %s", err)
	}
	scwrc, err := os.OpenFile(scwrcPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
	if err != nil {
		log.Fatalf("Unable to create scwrc config file: %s", err)
	}
	defer scwrc.Close()
	encoder := json.NewEncoder(scwrc)
	cfg.APIEndPoint = "https://api.scaleway.com/"
	err = encoder.Encode(cfg)
	if err != nil {
		log.Fatalf("Unable to encode scw config file: %s", err)
	}
}
Example #8
0
func runStart(cmd *types.Command, args []string) {
	if startHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	hasError := false
	errChan := make(chan error)
	successChan := make(chan bool)
	remainingItems := len(args)

	for i := range args {
		needle := args[i]
		go api.StartServerOnce(cmd.API, needle, startW, successChan, errChan)
	}

	if startTimeout > 0 {
		go func() {
			time.Sleep(time.Duration(startTimeout*1000) * time.Millisecond)
			log.Fatalf("Operation timed out")
		}()
	}

	for {
		select {
		case _ = <-successChan:
			remainingItems--
		case err := <-errChan:
			log.Errorf(fmt.Sprintf("%s", err))
			remainingItems--
			hasError = true
		}

		if remainingItems == 0 {
			break
		}
	}
	if hasError {
		os.Exit(1)
	}
}
Example #9
0
func runTag(cmd *types.Command, args []string) {
	if tagHelp {
		cmd.PrintUsage()
	}
	if len(args) != 2 {
		cmd.PrintShortUsage()
	}

	snapshotID := cmd.API.GetSnapshotID(args[0])
	snapshot, err := cmd.API.GetSnapshot(snapshotID)
	if err != nil {
		log.Fatalf("Cannot fetch snapshot: %v", err)
	}

	image, err := cmd.API.PostImage(snapshot.Identifier, args[1])
	if err != nil {
		log.Fatalf("Cannot create image: %v", err)
	}
	fmt.Println(image)
}
Example #10
0
func runRename(cmd *types.Command, args []string) {
	if renameHelp {
		cmd.PrintUsage()
	}
	if len(args) != 2 {
		cmd.PrintShortUsage()
	}

	serverID := cmd.API.GetServerID(args[0])

	var server api.ScalewayServerPatchDefinition
	server.Name = &args[1]

	err := cmd.API.PatchServer(serverID, server)
	if err != nil {
		log.Fatalf("Cannot rename server: %v", err)
	} else {
		cmd.API.Cache.InsertServer(serverID, *server.Name)
	}
}
Example #11
0
func runPort(cmd *types.Command, args []string) {
	if portHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	serverID := cmd.API.GetServerID(args[0])
	server, err := cmd.API.GetServer(serverID)
	if err != nil {
		log.Fatalf("Failed to get server information for %s: %v", serverID, err)
	}

	command := []string{"netstat -lutn 2>/dev/null | grep LISTEN"}
	err = utils.SSHExec(server.PublicAddress.IP, command, true)
	if err != nil {
		log.Fatalf("Command execution failed: %v", err)
	}
}
Example #12
0
func runExec(cmd *types.Command, args []string) {
	if execHelp {
		cmd.PrintUsage()
	}
	if len(args) < 2 {
		cmd.PrintShortUsage()
	}

	serverID := cmd.API.GetServerID(args[0])

	var server *api.ScalewayServer
	var err error
	if execW {
		// --wait
		server, err = api.WaitForServerReady(cmd.API, serverID)
		if err != nil {
			log.Fatalf("Failed to wait for server to be ready, %v", err)
		}
	} else {
		// no --wait
		server, err = cmd.API.GetServer(serverID)
		if err != nil {
			log.Fatalf("Failed to get server information for %s: %v", serverID, err)
		}
	}

	if execTimeout > 0 {
		go func() {
			time.Sleep(time.Duration(execTimeout*1000) * time.Millisecond)
			log.Fatalf("Operation timed out")
		}()
	}

	err = utils.SSHExec(server.PublicAddress.IP, args[1:], !execW)
	if err != nil {
		log.Fatalf("%v", err)
		os.Exit(1)
	}
	log.Debugf("Command successfuly executed")
}
Example #13
0
func runPatch(cmd *types.Command, args []string) {
	if patchHelp {
		cmd.PrintUsage()
	}
	if len(args) != 2 {
		cmd.PrintShortUsage()
	}

	// Parsing FIELD=VALUE
	updateParts := strings.Split(args[1], "=")
	if len(updateParts) != 2 {
		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
			}
		default:
			log.Fatalf("'_patch server %s=' not implemented", fieldName)
		}

		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 rename server: %v", err)
		}
	default:
		log.Fatalf("_patch not implemented for this kind of object")
	}
	fmt.Println(ident.Identifier)
}
Example #14
0
func runCp(cmd *types.Command, args []string) {
	if cpHelp {
		cmd.PrintUsage()
	}
	if len(args) != 2 {
		cmd.PrintShortUsage()
	}

	if strings.Count(args[0], ":") > 1 || strings.Count(args[1], ":") > 1 {
		log.Fatalf("usage: scw %s", cmd.UsageLine)
	}

	sourceStream, err := TarFromSource(cmd.API, args[0])
	if err != nil {
		log.Fatalf("Cannot tar from source '%s': %v", args[0], err)
	}

	err = UntarToDest(cmd.API, sourceStream, args[1])
	if err != nil {
		log.Fatalf("Cannot untar to destination '%s': %v", args[1], err)
	}
}
Example #15
0
func runLogs(cmd *types.Command, args []string) {
	if logsHelp {
		cmd.PrintUsage()
	}
	if len(args) != 1 {
		cmd.PrintShortUsage()
	}

	serverID := cmd.API.GetServerID(args[0])
	server, err := cmd.API.GetServer(serverID)
	if err != nil {
		log.Fatalf("Failed to get server information for %s: %v", serverID, err)
	}

	// FIXME: switch to serial history when API is ready

	command := []string{"dmesg"}
	err = utils.SSHExec(server.PublicAddress.IP, command, true)
	if err != nil {
		log.Fatalf("Command execution failed: %v", err)
	}
}
Example #16
0
func runRm(cmd *types.Command, args []string) {
	if rmHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	hasError := false
	for _, needle := range args {
		server := cmd.API.GetServerID(needle)
		err := cmd.API.DeleteServer(server)
		if err != nil {
			log.Errorf("failed to delete server %s: %s", server, err)
			hasError = true
		} else {
			fmt.Println(needle)
		}
	}
	if hasError {
		os.Exit(1)
	}
}
Example #17
0
func runWait(cmd *types.Command, args []string) {
	if waitHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	hasError := false
	for _, needle := range args {
		serverIdentifier := cmd.API.GetServerID(needle)

		_, err := api.WaitForServerStopped(cmd.API, serverIdentifier)
		if err != nil {
			log.Errorf("failed to wait for server %s: %v", serverIdentifier, err)
			hasError = true
		}
	}

	if hasError {
		os.Exit(1)
	}
}
Example #18
0
func runTop(cmd *types.Command, args []string) {
	if topHelp {
		cmd.PrintUsage()
	}
	if len(args) != 2 {
		cmd.PrintShortUsage()
	}

	serverID := cmd.API.GetServerID(args[0])
	command := "ps"
	server, err := cmd.API.GetServer(serverID)
	if err != nil {
		log.Fatalf("Failed to get server information for %s: %v", serverID, err)
	}

	execCmd := append(utils.NewSSHExecCmd(server.PublicAddress.IP, true, []string{command}))

	log.Debugf("Executing: ssh %s", strings.Join(execCmd, " "))
	out, err := exec.Command("ssh", execCmd...).CombinedOutput()
	fmt.Printf("%s", out)
	if err != nil {
		log.Fatal(err)
	}
}
Example #19
0
func runHistory(cmd *types.Command, args []string) {
	if historyHelp {
		cmd.PrintUsage()
	}
	if len(args) != 1 {
		cmd.PrintShortUsage()
	}

	imageID := cmd.API.GetImageID(args[0], true)
	image, err := cmd.API.GetImage(imageID)
	if err != nil {
		log.Fatalf("Cannot get image %s: %v", imageID, err)
	}

	if imagesQ {
		fmt.Println(imageID)
		return
	}

	w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
	defer w.Flush()
	fmt.Fprintf(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\n")

	identifier := utils.TruncIf(image.Identifier, 8, !historyNoTrunc)

	creationDate, err := time.Parse("2006-01-02T15:04:05.000000+00:00", image.CreationDate)
	if err != nil {
		log.Fatalf("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, !historyNoTrunc)
	size := units.HumanSize(float64(image.RootVolume.Size))

	fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", identifier, creationDateStr, volumeName, size)
}
Example #20
0
func runHelp(cmd *types.Command, args []string) {
	if waitHelp {
		cmd.PrintUsage()
	}
	if len(args) > 1 {
		cmd.PrintShortUsage()
	}

	if len(args) == 1 {
		name := args[0]
		for _, command := range Commands {
			if command.Name() == name {
				command.PrintUsage()
			}
		}
		log.Fatalf("Unknown help topic `%s`.  Run 'scw help'.", name)
	} else {
		t := template.New("top")
		template.Must(t.Parse(helpTemplate))
		if err := t.Execute(os.Stdout, Commands); err != nil {
			panic(err)
		}
	}
}
Example #21
0
func runSearch(cmd *types.Command, args []string) {
	if searchHelp {
		cmd.PrintUsage()
	}
	if len(args) != 1 {
		cmd.PrintShortUsage()
	}

	w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
	defer w.Flush()
	fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")

	var entries = []api.ScalewayImageInterface{}

	images, err := cmd.API.GetImages()
	if err != nil {
		log.Fatalf("unable to fetch images from the Scaleway API: %v", err)
	}
	for _, val := range *images {
		entries = append(entries, api.ScalewayImageInterface{
			Type:   "image",
			Name:   val.Name,
			Public: val.Public,
		})
	}

	snapshots, err := cmd.API.GetSnapshots()
	if err != nil {
		log.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err)
	}
	for _, val := range *snapshots {
		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, !searchNoTrunc)

		// 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, !searchNoTrunc)

		// 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, "")
	}
}
Example #22
0
func runImages(cmd *types.Command, args []string) {
	if imagesHelp {
		cmd.PrintUsage()
	}
	if len(args) != 0 {
		cmd.PrintShortUsage()
	}

	wg := sync.WaitGroup{}
	chEntries := make(chan api.ScalewayImageInterface)
	var entries = []api.ScalewayImageInterface{}

	wg.Add(1)
	go func() {
		defer wg.Done()
		images, err := cmd.API.GetImages()
		if err != nil {
			log.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 {
				log.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),
			}
		}
	}()

	if imagesA {
		wg.Add(1)
		go func() {
			defer wg.Done()
			snapshots, err := cmd.API.GetSnapshots()
			if err != nil {
				log.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 {
					log.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,
				}
			}
		}()

		wg.Add(1)
		go func() {
			defer wg.Done()
			bootscripts, err := cmd.API.GetBootscripts()
			if err != nil {
				log.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,
				}
			}
		}()

		wg.Add(1)
		go func() {
			defer wg.Done()
			volumes, err := cmd.API.GetVolumes()
			if err != nil {
				log.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 {
					log.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,
				}
			}
		}()
	}

	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
		}
	}

	w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
	defer w.Flush()
	if !imagesQ {
		fmt.Fprintf(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE\n")
	}
	sort.Sort(api.ByCreationDate(entries))
	for _, image := range entries {
		if imagesQ {
			fmt.Fprintf(w, "%s\n", image.Identifier)
		} else {
			tag := image.Tag
			shortID := utils.TruncIf(image.Identifier, 8, !imagesNoTrunc)
			name := utils.Wordify(image.Name)
			if !image.Public && image.Type == "image" {
				name = "user/" + name
			}
			shortName := utils.TruncIf(name, 25, !imagesNoTrunc)
			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)
		}
	}
}