diff --git a/README.md b/README.md index 8700cb4c25..a0669bcf12 100644 --- a/README.md +++ b/README.md @@ -1152,6 +1152,9 @@ $ scw inspect myserver | jq '.[0].public_ip.address' ### master (unreleased) +* Match bootscript/image with the good architecture ([#255](https://github.com/scaleway/scaleway-cli/issues/255)) +* Support of region/owner/arch in the cache file ([#255](https://github.com/scaleway/scaleway-cli/issues/255)) +* Remove some `fatal` and `Exit` * Use rfc4716 (openSSH) to generate the fingerprints ([#151](https://github.com/scaleway/scaleway-cli/issues/151)) * Switch from `Party` to `Godep` * create-image-from-http.sh: Support HTTP proxy ([#249](https://github.com/scaleway/scaleway-cli/issues/249)) diff --git a/pkg/api/api.go b/pkg/api/api.go index 4558a5d462..558911eb6d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -233,6 +233,14 @@ type ScalewayImage struct { // FIXME: extra_volumes } +// ScalewayImageIdentifier represents a Scaleway Image Identifier +type ScalewayImageIdentifier struct { + Identifier string + Arch string + Region string + Owner string +} + // ScalewayOneImage represents the response of a GET /images/UUID API call type ScalewayOneImage struct { Image ScalewayImage `json:"image,omitempty"` @@ -323,6 +331,12 @@ type ScalewayKernel struct { // ScalewayBootscript represents a Scaleway Bootscript type ScalewayBootscript struct { + // Arch is the architecture target of the bootscript + Arch string `json:"architecture,omitempty"` + + // Organization is the owner of the bootscript + Organization string `json:"organization,omitempty"` + // Identifier is a unique identifier for the bootscript Identifier string `json:"id,omitempty"` @@ -477,6 +491,9 @@ type ScalewayNewSecurityGroup struct { // ScalewayServer represents a Scaleway C1 server type ScalewayServer struct { + // Arch is the architecture target of the server + Arch string `json:"arch,omitempty"` + // Identifier is a unique identifier for the server Identifier string `json:"id,omitempty"` @@ -539,6 +556,7 @@ type ScalewayServer struct { // ScalewayServerPatchDefinition represents a Scaleway C1 server with nullable fields (for PATCH) type ScalewayServerPatchDefinition struct { + Arch *string `json:"arch,omitempty"` Name *string `json:"name,omitempty"` CreationDate *string `json:"creation_date,omitempty"` ModificationDate *string `json:"modification_date,omitempty"` @@ -1003,7 +1021,8 @@ func (s *ScalewayAPI) GetServers(all bool, limit int) (*[]ScalewayServer, error) return nil, err } for _, server := range servers.Servers { - s.Cache.InsertServer(server.Identifier, server.Name) + // FIXME region, arch, owner, title + s.Cache.InsertServer(server.Identifier, "fr-1", server.Arch, server.Organization, server.Name) } // FIXME: when API limit is ready, remove the following code if limit > 0 && limit < len(servers.Servers) { @@ -1030,7 +1049,8 @@ func (s *ScalewayAPI) GetServer(serverID string) (*ScalewayServer, error) { if err = json.Unmarshal(body, &oneServer); err != nil { return nil, err } - s.Cache.InsertServer(oneServer.Server.Identifier, oneServer.Server.Name) + // FIXME region, arch, owner, title + s.Cache.InsertServer(oneServer.Server.Identifier, "fr-1", oneServer.Server.Arch, oneServer.Server.Organization, oneServer.Server.Name) return &oneServer.Server, nil } @@ -1083,7 +1103,8 @@ func (s *ScalewayAPI) PostServer(definition ScalewayServerDefinition) (string, e if err = json.Unmarshal(body, &server); err != nil { return "", err } - s.Cache.InsertServer(server.Server.Identifier, server.Server.Name) + // FIXME region, arch, owner, title + s.Cache.InsertServer(server.Server.Identifier, "fr-1", server.Server.Arch, server.Server.Organization, server.Server.Name) return server.Server.Identifier, nil } @@ -1139,7 +1160,8 @@ func (s *ScalewayAPI) PostSnapshot(volumeID string, name string) (string, error) if err = json.Unmarshal(body, &snapshot); err != nil { return "", err } - s.Cache.InsertSnapshot(snapshot.Snapshot.Identifier, snapshot.Snapshot.Name) + // FIXME region, arch, owner, title + s.Cache.InsertSnapshot(snapshot.Snapshot.Identifier, "fr-1", "", snapshot.Snapshot.Organization, snapshot.Snapshot.Name) return snapshot.Snapshot.Identifier, nil } @@ -1170,7 +1192,8 @@ func (s *ScalewayAPI) PostImage(volumeID string, name string, bootscript string, if err = json.Unmarshal(body, &image); err != nil { return "", err } - s.Cache.InsertImage(image.Image.Identifier, image.Image.Name) + // FIXME region, arch, owner, title + s.Cache.InsertImage(image.Image.Identifier, "fr-1", image.Image.Arch, image.Image.Organization, image.Image.Name) return image.Image.Identifier, nil } @@ -1280,7 +1303,8 @@ func (s *ScalewayAPI) GetImages() (*[]ScalewayImage, error) { return nil, err } for _, image := range images.Images { - s.Cache.InsertImage(image.Identifier, image.Name) + // FIXME region, arch, owner, title + s.Cache.InsertImage(image.Identifier, "fr-1", image.Arch, image.Organization, image.Name) } return &images.Images, nil } @@ -1302,7 +1326,8 @@ func (s *ScalewayAPI) GetImage(imageID string) (*ScalewayImage, error) { if err = json.Unmarshal(body, &oneImage); err != nil { return nil, err } - s.Cache.InsertImage(oneImage.Image.Identifier, oneImage.Image.Name) + // FIXME region, arch, owner, title + s.Cache.InsertImage(oneImage.Image.Identifier, "fr-1", oneImage.Image.Arch, oneImage.Image.Organization, oneImage.Image.Name) return &oneImage.Image, nil } @@ -1343,7 +1368,8 @@ func (s *ScalewayAPI) GetSnapshots() (*[]ScalewaySnapshot, error) { return nil, err } for _, snapshot := range snapshots.Snapshots { - s.Cache.InsertSnapshot(snapshot.Identifier, snapshot.Name) + // FIXME region, arch, owner, title + s.Cache.InsertSnapshot(snapshot.Identifier, "fr-1", "", snapshot.Organization, snapshot.Name) } return &snapshots.Snapshots, nil } @@ -1365,7 +1391,8 @@ func (s *ScalewayAPI) GetSnapshot(snapshotID string) (*ScalewaySnapshot, error) if err = json.Unmarshal(body, &oneSnapshot); err != nil { return nil, err } - s.Cache.InsertSnapshot(oneSnapshot.Snapshot.Identifier, oneSnapshot.Snapshot.Name) + // FIXME region, arch, owner, title + s.Cache.InsertSnapshot(oneSnapshot.Snapshot.Identifier, "fr-1", "", oneSnapshot.Snapshot.Organization, oneSnapshot.Snapshot.Name) return &oneSnapshot.Snapshot, nil } @@ -1390,7 +1417,8 @@ func (s *ScalewayAPI) GetVolumes() (*[]ScalewayVolume, error) { return nil, err } for _, volume := range volumes.Volumes { - s.Cache.InsertVolume(volume.Identifier, volume.Name) + // FIXME region, arch, owner, title + s.Cache.InsertVolume(volume.Identifier, "fr-1", "", volume.Organization, volume.Name) } return &volumes.Volumes, nil } @@ -1412,7 +1440,8 @@ func (s *ScalewayAPI) GetVolume(volumeID string) (*ScalewayVolume, error) { if err = json.Unmarshal(body, &oneVolume); err != nil { return nil, err } - s.Cache.InsertVolume(oneVolume.Volume.Identifier, oneVolume.Volume.Name) + // FIXME region, arch, owner, title + s.Cache.InsertVolume(oneVolume.Volume.Identifier, "fr-1", "", oneVolume.Volume.Organization, oneVolume.Volume.Name) return &oneVolume.Volume, nil } @@ -1436,7 +1465,8 @@ func (s *ScalewayAPI) GetBootscripts() (*[]ScalewayBootscript, error) { return nil, err } for _, bootscript := range bootscripts.Bootscripts { - s.Cache.InsertBootscript(bootscript.Identifier, bootscript.Title) + // FIXME region, arch, owner, title + s.Cache.InsertBootscript(bootscript.Identifier, "fr-1", bootscript.Arch, bootscript.Organization, bootscript.Title) } return &bootscripts.Bootscripts, nil } @@ -1458,7 +1488,8 @@ func (s *ScalewayAPI) GetBootscript(bootscriptID string) (*ScalewayBootscript, e if err = json.Unmarshal(body, &oneBootscript); err != nil { return nil, err } - s.Cache.InsertBootscript(oneBootscript.Bootscript.Identifier, oneBootscript.Bootscript.Title) + // FIXME region, arch, owner, title + s.Cache.InsertBootscript(oneBootscript.Bootscript.Identifier, "fr-1", oneBootscript.Bootscript.Arch, oneBootscript.Bootscript.Organization, oneBootscript.Bootscript.Title) return &oneBootscript.Bootscript, nil } @@ -1736,82 +1767,81 @@ func (s *ScalewayAPI) GetDashboard() (*ScalewayDashboard, error) { } // GetServerID returns exactly one server matching or dies -func (s *ScalewayAPI) GetServerID(needle string) string { +func (s *ScalewayAPI) GetServerID(needle string) (string, error) { // Parses optional type prefix, i.e: "server:name" -> "name" _, needle = parseNeedle(needle) servers, err := s.ResolveServer(needle) if err != nil { - log.Fatalf("Unable to resolve server %s: %s", needle, err) + return "", fmt.Errorf("Unable to resolve server %s: %s", needle, err) } if len(servers) == 1 { - return servers[0].Identifier + return servers[0].Identifier, nil } if len(servers) == 0 { - log.Fatalf("No such server: %s", needle) + return "", fmt.Errorf("No such server: %s", needle) } - - showResolverResults(needle, servers) - os.Exit(1) - return "" + return "", showResolverResults(needle, servers) } func showResolverResults(needle string, results ScalewayResolverResults) error { - log.Errorf("Too many candidates for %s (%d)", needle, len(results)) - + arch := "" 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) + arch = result.Arch + if arch == "" { + arch = "n/a" + } + fmt.Fprintf(w, "- %s\t%s\t%s\t%s\n", result.TruncIdentifier(), result.CodeName(), result.Name, arch) } - return nil + return fmt.Errorf("Too many candidates for %s (%d)", needle, len(results)) } // GetSnapshotID returns exactly one snapshot matching or dies -func (s *ScalewayAPI) GetSnapshotID(needle string) string { +func (s *ScalewayAPI) GetSnapshotID(needle string) (string, error) { // 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) + return "", fmt.Errorf("Unable to resolve snapshot %s: %s", needle, err) } if len(snapshots) == 1 { - return snapshots[0].Identifier + return snapshots[0].Identifier, nil } if len(snapshots) == 0 { - log.Fatalf("No such snapshot: %s", needle) + return "", fmt.Errorf("No such snapshot: %s", needle) } - - showResolverResults(needle, snapshots) - os.Exit(1) - return "" + return "", showResolverResults(needle, snapshots) } // GetImageID returns exactly one image matching or dies -func (s *ScalewayAPI) GetImageID(needle string, exitIfMissing bool) string { +func (s *ScalewayAPI) GetImageID(needle string, exitIfMissing bool) (*ScalewayImageIdentifier, error) { // 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) + return nil, fmt.Errorf("Unable to resolve image %s: %s", needle, err) } if len(images) == 1 { - return images[0].Identifier + return &ScalewayImageIdentifier{ + Identifier: images[0].Identifier, + Arch: images[0].Arch, + // FIXME region, owner hardcoded + Region: "fr-1", + Owner: "", + }, nil } if len(images) == 0 { if exitIfMissing { - log.Fatalf("No such image: %s", needle) - } else { - return "" + return nil, fmt.Errorf("No such image: %s", needle) } + return nil, nil } - - showResolverResults(needle, images) - os.Exit(1) - return "" + return nil, showResolverResults(needle, images) } // GetSecurityGroups returns a ScalewaySecurityGroups @@ -2133,24 +2163,22 @@ func (s *ScalewayAPI) GetQuotas() (*ScalewayGetQuotas, error) { } // GetBootscriptID returns exactly one bootscript matching or dies -func (s *ScalewayAPI) GetBootscriptID(needle string) string { +func (s *ScalewayAPI) GetBootscriptID(needle, arch string) (string, error) { // 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) + return "", fmt.Errorf("Unable to resolve bootscript %s: %s", needle, err) } + bootscripts.FilterByArch(arch) if len(bootscripts) == 1 { - return bootscripts[0].Identifier + return bootscripts[0].Identifier, nil } if len(bootscripts) == 0 { - log.Fatalf("No such bootscript: %s", needle) + return "", fmt.Errorf("No such bootscript: %s", needle) } - - showResolverResults(needle, bootscripts) - os.Exit(1) - return "" + return "", showResolverResults(needle, bootscripts) } // HideAPICredentials removes API credentials from a string diff --git a/pkg/api/cache.go b/pkg/api/cache.go index 1bee03326c..18e905c799 100644 --- a/pkg/api/cache.go +++ b/pkg/api/cache.go @@ -19,22 +19,35 @@ import ( "github.com/renstrom/fuzzysearch/fuzzy" ) +const ( + // CacheRegion permits to access at the region field + CacheRegion = iota + // CacheArch permits to access at the arch field + CacheArch + // CacheOwner permits to access at the owner field + CacheOwner + // CacheTitle permits to access at the title field + CacheTitle + // CacheMaxfield is used to determine the size of array + CacheMaxfield +) + // ScalewayCache is used not to query the API to resolve full identifiers type ScalewayCache struct { // Images contains names of Scaleway images indexed by identifier - Images map[string]string `json:"images"` + Images map[string][CacheMaxfield]string `json:"images"` // Snapshots contains names of Scaleway snapshots indexed by identifier - Snapshots map[string]string `json:"snapshots"` + Snapshots map[string][CacheMaxfield]string `json:"snapshots"` // Volumes contains names of Scaleway volumes indexed by identifier - Volumes map[string]string `json:"volumes"` + Volumes map[string][CacheMaxfield]string `json:"volumes"` // Bootscripts contains names of Scaleway bootscripts indexed by identifier - Bootscripts map[string]string `json:"bootscripts"` + Bootscripts map[string][CacheMaxfield]string `json:"bootscripts"` // Servers contains names of Scaleway C1 servers indexed by identifier - Servers map[string]string `json:"servers"` + Servers map[string][CacheMaxfield]string `json:"servers"` // Path is the path to the cache file Path string `json:"-"` @@ -67,6 +80,7 @@ type ScalewayResolverResult struct { Identifier string Type int Name string + Arch string Needle string RankMatch int } @@ -74,6 +88,16 @@ type ScalewayResolverResult struct { // ScalewayResolverResults is a list of `ScalewayResolverResult` type ScalewayResolverResults []ScalewayResolverResult +// NewScalewayResolverResult returns a new ScalewayResolverResult +func NewScalewayResolverResult(Identifier, Name, Arch string, Type int) ScalewayResolverResult { + return ScalewayResolverResult{ + Identifier: Identifier, + Type: Type, + Name: Name, + Arch: Arch, + } +} + func (s ScalewayResolverResults) Len() int { return len(s) } @@ -117,6 +141,18 @@ func (s *ScalewayResolverResult) CodeName() string { return fmt.Sprintf("%s:%s", strings.ToLower(identifierTypeName(s.Type)), name) } +// FilterByArch deletes the elements which not match with arch +func (s *ScalewayResolverResults) FilterByArch(arch string) { +REDO: + for i := range *s { + if (*s)[i].Arch != arch { + (*s)[i] = (*s)[len(*s)-1] + *s = (*s)[:len(*s)-1] + goto REDO + } + } +} + // NewScalewayCache loads a per-user cache func NewScalewayCache() (*ScalewayCache, error) { homeDir := os.Getenv("HOME") // *nix @@ -130,11 +166,11 @@ func NewScalewayCache() (*ScalewayCache, error) { _, err := os.Stat(cachePath) if os.IsNotExist(err) { return &ScalewayCache{ - Images: make(map[string]string), - Snapshots: make(map[string]string), - Volumes: make(map[string]string), - Bootscripts: make(map[string]string), - Servers: make(map[string]string), + Images: make(map[string][CacheMaxfield]string), + Snapshots: make(map[string][CacheMaxfield]string), + Volumes: make(map[string][CacheMaxfield]string), + Bootscripts: make(map[string][CacheMaxfield]string), + Servers: make(map[string][CacheMaxfield]string), Path: cachePath, }, nil } else if err != nil { @@ -145,25 +181,37 @@ func NewScalewayCache() (*ScalewayCache, error) { return nil, err } var cache ScalewayCache + cache.Path = cachePath err = json.Unmarshal(file, &cache) if err != nil { - return nil, err + // fix compatibility with older version + if err = os.Remove(cachePath); err != nil { + return nil, err + } + return &ScalewayCache{ + Images: make(map[string][CacheMaxfield]string), + Snapshots: make(map[string][CacheMaxfield]string), + Volumes: make(map[string][CacheMaxfield]string), + Bootscripts: make(map[string][CacheMaxfield]string), + Servers: make(map[string][CacheMaxfield]string), + Path: cachePath, + }, nil } if cache.Images == nil { - cache.Images = make(map[string]string) + cache.Images = make(map[string][CacheMaxfield]string) } if cache.Snapshots == nil { - cache.Snapshots = make(map[string]string) + cache.Snapshots = make(map[string][CacheMaxfield]string) } if cache.Volumes == nil { - cache.Volumes = make(map[string]string) + cache.Volumes = make(map[string][CacheMaxfield]string) } if cache.Servers == nil { - cache.Servers = make(map[string]string) + cache.Servers = make(map[string][CacheMaxfield]string) } if cache.Bootscripts == nil { - cache.Bootscripts = make(map[string]string) + cache.Bootscripts = make(map[string][CacheMaxfield]string) } return &cache, nil } @@ -216,34 +264,24 @@ func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) ScalewayRes var exactMatches ScalewayResolverResults if acceptUUID && anonuuid.IsUUID(needle) == nil { - entry := ScalewayResolverResult{ - Identifier: needle, - Name: needle, - Type: IdentifierImage, + if fields, ok := c.Images[needle]; ok { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierImage) + entry.ComputeRankMatch(needle) + res = append(res, entry) } - entry.ComputeRankMatch(needle) - res = append(res, entry) } needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "") // FIXME: if 'user/' is in needle, only watch for a user image nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*")) - for identifier, name := range c.Images { - if name == needle { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierImage, - } + for identifier, fields := range c.Images { + if fields[CacheTitle] == needle { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage) entry.ComputeRankMatch(needle) exactMatches = append(exactMatches, entry) } - if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierImage, - } + if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage) entry.ComputeRankMatch(needle) res = append(res, entry) } @@ -265,33 +303,23 @@ func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) Scaleway var exactMatches ScalewayResolverResults if acceptUUID && anonuuid.IsUUID(needle) == nil { - entry := ScalewayResolverResult{ - Identifier: needle, - Name: needle, - Type: IdentifierSnapshot, + if fields, ok := c.Snapshots[needle]; ok { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot) + entry.ComputeRankMatch(needle) + res = append(res, entry) } - entry.ComputeRankMatch(needle) - res = append(res, entry) } needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "") nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*")) - for identifier, name := range c.Snapshots { - if name == needle { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierSnapshot, - } + for identifier, fields := range c.Snapshots { + if fields[CacheTitle] == needle { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot) entry.ComputeRankMatch(needle) exactMatches = append(exactMatches, entry) } - if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierSnapshot, - } + if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot) entry.ComputeRankMatch(needle) res = append(res, entry) } @@ -313,32 +341,22 @@ func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) ScalewayRe var exactMatches ScalewayResolverResults if acceptUUID && anonuuid.IsUUID(needle) == nil { - entry := ScalewayResolverResult{ - Identifier: needle, - Name: needle, - Type: IdentifierVolume, + if fields, ok := c.Volumes[needle]; ok { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierVolume) + entry.ComputeRankMatch(needle) + res = append(res, entry) } - entry.ComputeRankMatch(needle) - res = append(res, entry) } nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*")) - for identifier, name := range c.Volumes { - if name == needle { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierVolume, - } + for identifier, fields := range c.Volumes { + if fields[CacheTitle] == needle { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierVolume) entry.ComputeRankMatch(needle) exactMatches = append(exactMatches, entry) } - if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierVolume, - } + if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierVolume) entry.ComputeRankMatch(needle) res = append(res, entry) } @@ -360,32 +378,22 @@ func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) Scalew var exactMatches ScalewayResolverResults if acceptUUID && anonuuid.IsUUID(needle) == nil { - entry := ScalewayResolverResult{ - Identifier: needle, - Name: needle, - Type: IdentifierBootscript, + if fields, ok := c.Bootscripts[needle]; ok { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierBootscript) + entry.ComputeRankMatch(needle) + res = append(res, entry) } - entry.ComputeRankMatch(needle) - res = append(res, entry) } nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*")) - for identifier, name := range c.Bootscripts { - if name == needle { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierBootscript, - } + for identifier, fields := range c.Bootscripts { + if fields[CacheTitle] == needle { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript) entry.ComputeRankMatch(needle) exactMatches = append(exactMatches, entry) } - if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierBootscript, - } + if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript) entry.ComputeRankMatch(needle) res = append(res, entry) } @@ -407,32 +415,22 @@ func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) ScalewayRe var exactMatches ScalewayResolverResults if acceptUUID && anonuuid.IsUUID(needle) == nil { - entry := ScalewayResolverResult{ - Identifier: needle, - Name: needle, - Type: IdentifierServer, + if fields, ok := c.Servers[needle]; ok { + entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierServer) + entry.ComputeRankMatch(needle) + res = append(res, entry) } - entry.ComputeRankMatch(needle) - res = append(res, entry) } nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*")) - for identifier, name := range c.Servers { - if name == needle { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierServer, - } + for identifier, fields := range c.Servers { + if fields[CacheTitle] == needle { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer) entry.ComputeRankMatch(needle) exactMatches = append(exactMatches, entry) } - if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) { - entry := ScalewayResolverResult{ - Identifier: identifier, - Name: name, - Type: IdentifierServer, - } + if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) { + entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer) entry.ComputeRankMatch(needle) res = append(res, entry) } @@ -494,11 +492,7 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults if identifierType&(IdentifierUnknown|IdentifierServer) > 0 { for _, result := range c.LookUpServers(needle, false) { - entry := ScalewayResolverResult{ - Identifier: result.Identifier, - Name: result.Name, - Type: IdentifierServer, - } + entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierServer) entry.ComputeRankMatch(needle) results = append(results, entry) } @@ -506,11 +500,7 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults if identifierType&(IdentifierUnknown|IdentifierImage) > 0 { for _, result := range c.LookUpImages(needle, false) { - entry := ScalewayResolverResult{ - Identifier: result.Identifier, - Name: result.Name, - Type: IdentifierImage, - } + entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierImage) entry.ComputeRankMatch(needle) results = append(results, entry) } @@ -518,11 +508,7 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 { for _, result := range c.LookUpSnapshots(needle, false) { - entry := ScalewayResolverResult{ - Identifier: result.Identifier, - Name: result.Name, - Type: IdentifierSnapshot, - } + entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierSnapshot) entry.ComputeRankMatch(needle) results = append(results, entry) } @@ -530,11 +516,7 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 { for _, result := range c.LookUpVolumes(needle, false) { - entry := ScalewayResolverResult{ - Identifier: result.Identifier, - Name: result.Name, - Type: IdentifierVolume, - } + entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierVolume) entry.ComputeRankMatch(needle) results = append(results, entry) } @@ -542,11 +524,7 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 { for _, result := range c.LookUpBootscripts(needle, false) { - entry := ScalewayResolverResult{ - Identifier: result.Identifier, - Name: result.Name, - Type: IdentifierBootscript, - } + entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierBootscript) entry.ComputeRankMatch(needle) results = append(results, entry) } @@ -556,13 +534,13 @@ func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults } // InsertServer registers a server in the cache -func (c *ScalewayCache) InsertServer(identifier, name string) { +func (c *ScalewayCache) InsertServer(identifier, region, arch, owner, name string) { c.Lock.Lock() defer c.Lock.Unlock() - currentName, exists := c.Servers[identifier] - if !exists || currentName != name { - c.Servers[identifier] = name + fields, exists := c.Servers[identifier] + if !exists || fields[CacheTitle] != name { + c.Servers[identifier] = [CacheMaxfield]string{region, arch, owner, name} c.Modified = true } } @@ -581,18 +559,18 @@ func (c *ScalewayCache) ClearServers() { c.Lock.Lock() defer c.Lock.Unlock() - c.Servers = make(map[string]string) + c.Servers = make(map[string][CacheMaxfield]string) c.Modified = true } // InsertImage registers an image in the cache -func (c *ScalewayCache) InsertImage(identifier, name string) { +func (c *ScalewayCache) InsertImage(identifier, region, arch, owner, name string) { c.Lock.Lock() defer c.Lock.Unlock() - currentName, exists := c.Images[identifier] - if !exists || currentName != name { - c.Images[identifier] = name + fields, exists := c.Images[identifier] + if !exists || fields[CacheTitle] != name { + c.Images[identifier] = [CacheMaxfield]string{region, arch, owner, name} c.Modified = true } } @@ -611,18 +589,18 @@ func (c *ScalewayCache) ClearImages() { c.Lock.Lock() defer c.Lock.Unlock() - c.Images = make(map[string]string) + c.Images = make(map[string][CacheMaxfield]string) c.Modified = true } // InsertSnapshot registers an snapshot in the cache -func (c *ScalewayCache) InsertSnapshot(identifier, name string) { +func (c *ScalewayCache) InsertSnapshot(identifier, region, arch, owner, name string) { c.Lock.Lock() defer c.Lock.Unlock() - currentName, exists := c.Snapshots[identifier] - if !exists || currentName != name { - c.Snapshots[identifier] = name + fields, exists := c.Snapshots[identifier] + if !exists || fields[CacheTitle] != name { + c.Snapshots[identifier] = [CacheMaxfield]string{region, arch, owner, name} c.Modified = true } } @@ -641,18 +619,18 @@ func (c *ScalewayCache) ClearSnapshots() { c.Lock.Lock() defer c.Lock.Unlock() - c.Snapshots = make(map[string]string) + c.Snapshots = make(map[string][CacheMaxfield]string) c.Modified = true } // InsertVolume registers an volume in the cache -func (c *ScalewayCache) InsertVolume(identifier, name string) { +func (c *ScalewayCache) InsertVolume(identifier, region, arch, owner, name string) { c.Lock.Lock() defer c.Lock.Unlock() - currentName, exists := c.Volumes[identifier] - if !exists || currentName != name { - c.Volumes[identifier] = name + fields, exists := c.Volumes[identifier] + if !exists || fields[CacheTitle] != name { + c.Volumes[identifier] = [CacheMaxfield]string{region, arch, owner, name} c.Modified = true } } @@ -671,18 +649,18 @@ func (c *ScalewayCache) ClearVolumes() { c.Lock.Lock() defer c.Lock.Unlock() - c.Volumes = make(map[string]string) + c.Volumes = make(map[string][CacheMaxfield]string) c.Modified = true } // InsertBootscript registers an bootscript in the cache -func (c *ScalewayCache) InsertBootscript(identifier, name string) { +func (c *ScalewayCache) InsertBootscript(identifier, region, arch, owner, name string) { c.Lock.Lock() defer c.Lock.Unlock() - currentName, exists := c.Bootscripts[identifier] - if !exists || currentName != name { - c.Bootscripts[identifier] = name + fields, exists := c.Bootscripts[identifier] + if !exists || fields[CacheTitle] != name { + c.Bootscripts[identifier] = [CacheMaxfield]string{region, arch, owner, name} c.Modified = true } } @@ -701,7 +679,7 @@ func (c *ScalewayCache) ClearBootscripts() { c.Lock.Lock() defer c.Lock.Unlock() - c.Bootscripts = make(map[string]string) + c.Bootscripts = make(map[string][CacheMaxfield]string) c.Modified = true } diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 846f266848..8cd38a7aa5 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -12,12 +12,12 @@ import ( "sync" "time" - "github.com/scaleway/scaleway-cli/pkg/utils" "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/namesgenerator" "github.com/dustin/go-humanize" "github.com/moul/anonuuid" + "github.com/scaleway/scaleway-cli/pkg/utils" ) // ScalewayResolvedIdentifier represents a list of matching identifier for a specifier pattern @@ -39,6 +39,8 @@ type ScalewayImageInterface struct { Public bool Type string Organization string + Arch string + Region string } // ResolveGateway tries to resolve a server public ip address, else returns the input string, i.e. IPv4, hostname @@ -60,8 +62,7 @@ func ResolveGateway(api *ScalewayAPI, gateway string) (string, error) { } if len(servers) > 1 { - showResolverResults(gateway, servers) - return "", fmt.Errorf("Gateway '%s' is ambiguous", gateway) + return "", showResolverResults(gateway, servers) } // if len(servers) == 1 { @@ -92,7 +93,7 @@ func CreateVolumeFromHumanSize(api *ScalewayAPI, size string) (*string, error) { return &volumeID, nil } -// fillIdentifierCache fills the cache by fetching fro the API +// fillIdentifierCache fills the cache by fetching from the API func fillIdentifierCache(api *ScalewayAPI, identifierType int) { log.Debugf("Filling the cache") var wg sync.WaitGroup @@ -131,24 +132,22 @@ func fillIdentifierCache(api *ScalewayAPI, identifierType int) { } // 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 { +func GetIdentifier(api *ScalewayAPI, needle string) (*ScalewayResolverResult, error) { idents := ResolveIdentifier(api, needle) if len(idents) == 1 { - return &idents[0] + return &idents[0], nil } if len(idents) == 0 { - log.Fatalf("No such identifier: %s", needle) + return nil, fmt.Errorf("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 + return nil, fmt.Errorf("Too many candidates for %s (%d)", needle, len(idents)) } // ResolveIdentifier resolves needle provided by the user @@ -229,7 +228,7 @@ func InspectIdentifiers(api *ScalewayAPI, ci chan ScalewayResolvedIdentifier, cj if len(idents.Identifiers) == 0 { log.Errorf("Unable to resolve identifier %s", idents.Needle) } else { - showResolverResults(idents.Needle, idents.Identifiers) + logrus.Fatal(showResolverResults(idents.Needle, idents.Identifiers)) } } else { ident := idents.Identifiers[0] @@ -310,9 +309,9 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) { } var server ScalewayServerDefinition + server.CommercialType = c.CommercialType server.Volumes = make(map[string]string) - server.DynamicIPRequired = &c.DynamicIPRequired if c.IP != "" { if anonuuid.IsUUID(c.IP) == nil { @@ -349,12 +348,12 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) { server.Volumes[volumeIDx] = *volumeID } } - server.Name = c.Name - if c.Bootscript != "" { - bootscript := api.GetBootscriptID(c.Bootscript) - server.Bootscript = &bootscript + // FIXME build images only on ARM ? + imageIdentifier := &ScalewayImageIdentifier{ + Arch: "arm", + Region: "fr-1", } - + server.Name = c.Name inheritingVolume := false _, err := humanize.ParseBytes(c.ImageName) if err == nil { @@ -368,11 +367,17 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) { // Use an existing image // FIXME: handle snapshots inheritingVolume = true - image := api.GetImageID(c.ImageName, false) - if image != "" { - server.Image = &image + imageIdentifier, err = api.GetImageID(c.ImageName, false) + if err != nil { + return "", err + } + if imageIdentifier.Identifier != "" { + server.Image = &imageIdentifier.Identifier } else { - snapshotID := api.GetSnapshotID(c.ImageName) + snapshotID, err := api.GetSnapshotID(c.ImageName) + if err != nil { + return "", err + } snapshot, err := api.GetSnapshot(snapshotID) if err != nil { return "", err @@ -383,7 +388,13 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) { server.Volumes["0"] = snapshot.BaseVolume.Identifier } } - + if c.Bootscript != "" { + bootscript, err := api.GetBootscriptID(c.Bootscript, imageIdentifier.Arch) + if err != nil { + return "", err + } + server.Bootscript = &bootscript + } serverID, err := api.PostServer(server) if err != nil { return "", err @@ -536,10 +547,12 @@ func (a ByCreationDate) Less(i, j int) bool { return a[j].CreationDate.Before(a[ // StartServer start a server based on its needle, can optionaly block while server is booting func StartServer(api *ScalewayAPI, needle string, wait bool) error { - server := api.GetServerID(needle) - - err := api.PostServerAction(server, "poweron") + server, err := api.GetServerID(needle) if err != nil { + return err + } + + if err = api.PostServerAction(server, "poweron"); err != nil { if err.Error() == "server should be stopped" { return fmt.Errorf("server %s is already started: %v", server, err) } diff --git a/pkg/cli/cmd_help.go b/pkg/cli/cmd_help.go index 317d5be439..bc22136210 100644 --- a/pkg/cli/cmd_help.go +++ b/pkg/cli/cmd_help.go @@ -5,9 +5,8 @@ package cli import ( + "fmt" "text/template" - - "github.com/Sirupsen/logrus" ) // CmdHelp is the 'scw help' command @@ -67,12 +66,10 @@ func runHelp(cmd *Command, rawArgs []string) error { 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 fmt.Errorf("Unknown help topic `%s`. Run 'scw help'.", name) } - return nil + t := template.New("top") + template.Must(t.Parse(helpTemplate)) + ctx := cmd.GetContext(rawArgs) + return t.Execute(ctx.Stdout, Commands) } diff --git a/pkg/cli/command.go b/pkg/cli/command.go index 58a741d72c..5d259ec1cf 100644 --- a/pkg/cli/command.go +++ b/pkg/cli/command.go @@ -13,7 +13,6 @@ import ( "strings" "text/template" - "github.com/Sirupsen/logrus" flag "github.com/docker/docker/pkg/mflag" "github.com/scaleway/scaleway-cli/pkg/api" @@ -103,7 +102,7 @@ func (c *Command) Name() string { func (c *Command) PrintUsage() error { helpMessage, err := commandHelpMessage(c) if err != nil { - logrus.Fatalf("%v", err) + return err } fmt.Fprintf(c.Streams().Stdout, "%s\n", helpMessage) return ErrExitFailure diff --git a/pkg/cli/x_completion.go b/pkg/cli/x_completion.go index f8184b7a02..b7bbbc9a99 100644 --- a/pkg/cli/x_completion.go +++ b/pkg/cli/x_completion.go @@ -9,8 +9,8 @@ import ( "sort" "strings" + "github.com/scaleway/scaleway-cli/pkg/api" utils "github.com/scaleway/scaleway-cli/pkg/utils" - "github.com/Sirupsen/logrus" ) var cmdCompletion = &Command{ @@ -65,47 +65,47 @@ func runCompletion(cmd *Command, args []string) error { switch category { case "servers-all": - for identifier, name := range cmd.API.Cache.Servers { - elements = append(elements, identifier, wordifyName(name, "server")) + for identifier, fields := range cmd.API.Cache.Servers { + elements = append(elements, identifier, wordifyName(fields[api.CacheTitle], "server")) } case "servers-names": - for _, name := range cmd.API.Cache.Servers { - elements = append(elements, wordifyName(name, "server")) + for _, fields := range cmd.API.Cache.Servers { + elements = append(elements, wordifyName(fields[api.CacheTitle], "server")) } case "images-all": - for identifier, name := range cmd.API.Cache.Images { - elements = append(elements, identifier, wordifyName(name, "image")) + for identifier, fields := range cmd.API.Cache.Images { + elements = append(elements, identifier, wordifyName(fields[api.CacheTitle], "image")) } case "images-names": - for _, name := range cmd.API.Cache.Images { - elements = append(elements, wordifyName(name, "image")) + for _, fields := range cmd.API.Cache.Images { + elements = append(elements, wordifyName(fields[api.CacheTitle], "image")) } case "volumes-all": - for identifier, name := range cmd.API.Cache.Volumes { - elements = append(elements, identifier, wordifyName(name, "volume")) + for identifier, fields := range cmd.API.Cache.Volumes { + elements = append(elements, identifier, wordifyName(fields[api.CacheTitle], "volume")) } case "volumes-names": - for _, name := range cmd.API.Cache.Volumes { - elements = append(elements, wordifyName(name, "volume")) + for _, fields := range cmd.API.Cache.Volumes { + elements = append(elements, wordifyName(fields[api.CacheTitle], "volume")) } case "snapshots-all": - for identifier, name := range cmd.API.Cache.Snapshots { - elements = append(elements, identifier, wordifyName(name, "snapshot")) + for identifier, fields := range cmd.API.Cache.Snapshots { + elements = append(elements, identifier, wordifyName(fields[api.CacheTitle], "snapshot")) } case "snapshots-names": - for _, name := range cmd.API.Cache.Snapshots { - elements = append(elements, wordifyName(name, "snapshot")) + for _, fields := range cmd.API.Cache.Snapshots { + elements = append(elements, wordifyName(fields[api.CacheTitle], "snapshot")) } case "bootscripts-all": - for identifier, name := range cmd.API.Cache.Bootscripts { - elements = append(elements, identifier, wordifyName(name, "bootscript")) + for identifier, fields := range cmd.API.Cache.Bootscripts { + elements = append(elements, identifier, wordifyName(fields[api.CacheTitle], "bootscript")) } case "bootscripts-names": - for _, name := range cmd.API.Cache.Bootscripts { - elements = append(elements, wordifyName(name, "bootscript")) + for _, fields := range cmd.API.Cache.Bootscripts { + elements = append(elements, wordifyName(fields[api.CacheTitle], "bootscript")) } default: - logrus.Fatalf("Unhandled category of completion: %s", category) + return fmt.Errorf("Unhandled category of completion: %s", category) } sort.Strings(elements) diff --git a/pkg/cli/x_flushcache.go b/pkg/cli/x_flushcache.go index 25251fde85..ae5bc96183 100644 --- a/pkg/cli/x_flushcache.go +++ b/pkg/cli/x_flushcache.go @@ -4,11 +4,7 @@ package cli -import ( - "fmt" - - "github.com/Sirupsen/logrus" -) +import "fmt" var cmdFlushCache = &Command{ Exec: runFlushCache, @@ -35,10 +31,8 @@ func runFlushCache(cmd *Command, args []string) error { err := cmd.API.Cache.Flush() if err != nil { - logrus.Fatal("Failed to flush the cache") + return fmt.Errorf("Failed to flush the cache") } - fmt.Println("Cache flushed") - return nil } diff --git a/pkg/cli/x_patch.go b/pkg/cli/x_patch.go index 36c5c595c2..e94d738116 100644 --- a/pkg/cli/x_patch.go +++ b/pkg/cli/x_patch.go @@ -50,12 +50,15 @@ func runPatch(cmd *Command, args []string) error { changes := 0 - ident := api.GetIdentifier(cmd.API, args[0]) + ident, err := api.GetIdentifier(cmd.API, args[0]) + if err != nil { + return err + } 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) + return fmt.Errorf("Cannot get server %s: %v", ident.Identifier, err) } var payload api.ScalewayServerPatchDefinition @@ -93,7 +96,7 @@ func runPatch(cmd *Command, args []string) error { changes++ payload.Tags = &newTags default: - log.Fatalf("'_patch server %s=' not implemented", fieldName) + return fmt.Errorf("'_patch server %s=' not implemented", fieldName) } // FIXME: volumes, tags, dynamic_ip_required @@ -104,12 +107,11 @@ func runPatch(cmd *Command, args []string) error { log.Debugf("no changes, not updating server") } if err != nil { - log.Fatalf("Cannot update server: %v", err) + return fmt.Errorf("Cannot update server: %v", err) } default: - log.Fatalf("_patch not implemented for this kind of object") + return fmt.Errorf("_patch not implemented for this kind of object") } fmt.Println(ident.Identifier) - return nil } diff --git a/pkg/cli/x_userdata.go b/pkg/cli/x_userdata.go index 22f98aca92..bdf1594640 100644 --- a/pkg/cli/x_userdata.go +++ b/pkg/cli/x_userdata.go @@ -55,7 +55,10 @@ func runUserdata(cmd *Command, args []string) error { if ctx.API == nil { return fmt.Errorf("You need to login first: 'scw login'") } - serverID = ctx.API.GetServerID(args[0]) + serverID, err = ctx.API.GetServerID(args[0]) + if err != nil { + return err + } API = ctx.API } diff --git a/pkg/commands/attach.go b/pkg/commands/attach.go index 224c9d5090..b522e30093 100644 --- a/pkg/commands/attach.go +++ b/pkg/commands/attach.go @@ -14,8 +14,10 @@ type AttachArgs struct { // RunAttach is the handler for 'scw attach' func RunAttach(ctx CommandContext, args AttachArgs) error { - serverID := ctx.API.GetServerID(args.Server) - + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } _, done, err := utils.AttachToSerial(serverID, ctx.API.Token) if err != nil { return err diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go index 9196babe38..7cc7e48912 100644 --- a/pkg/commands/commit.go +++ b/pkg/commands/commit.go @@ -17,7 +17,10 @@ type CommitArgs struct { // RunCommit is the handler for 'scw commit' func RunCommit(ctx CommandContext, args CommitArgs) error { - serverID := ctx.API.GetServerID(args.Server) + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } server, err := ctx.API.GetServer(serverID) if err != nil { return fmt.Errorf("Cannot fetch server: %v", err) diff --git a/pkg/commands/cp.go b/pkg/commands/cp.go index 5325d385b4..dab4bfd86a 100644 --- a/pkg/commands/cp.go +++ b/pkg/commands/cp.go @@ -12,10 +12,10 @@ import ( "path/filepath" "strings" - "github.com/scaleway/scaleway-cli/pkg/api" - "github.com/scaleway/scaleway-cli/pkg/utils" "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/archive" + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" ) // CpArgs are arguments passed to `RunCp` @@ -55,7 +55,10 @@ func TarFromSource(ctx CommandContext, source string, gateway string) (*io.ReadC return nil, fmt.Errorf("invalid source uri, see 'scw cp -h' for usage") } - serverID := ctx.API.GetServerID(serverParts[0]) + serverID, err := ctx.API.GetServerID(serverParts[0]) + if err != nil { + return nil, err + } server, err := ctx.API.GetServer(serverID) if err != nil { @@ -157,7 +160,10 @@ func UntarToDest(ctx CommandContext, sourceStream *io.ReadCloser, destination st return fmt.Errorf("invalid destination uri, see 'scw cp -h' for usage") } - serverID := ctx.API.GetServerID(serverParts[0]) + serverID, err := ctx.API.GetServerID(serverParts[0]) + if err != nil { + return err + } server, err := ctx.API.GetServer(serverID) if err != nil { diff --git a/pkg/commands/exec.go b/pkg/commands/exec.go index ac81a99b1a..d98e939db1 100644 --- a/pkg/commands/exec.go +++ b/pkg/commands/exec.go @@ -25,14 +25,17 @@ type ExecArgs struct { // RunExec is the handler for 'scw exec' func RunExec(ctx CommandContext, args ExecArgs) error { - serverID := ctx.API.GetServerID(args.Server) + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } // Resolve gateway if args.Gateway == "" { args.Gateway = ctx.Getenv("SCW_GATEWAY") } var gateway string - var err error + if args.Gateway == serverID || args.Gateway == args.Server { log.Debugf("The server and the gateway are the same host, using direct access to the server") gateway = "" diff --git a/pkg/commands/history.go b/pkg/commands/history.go index a67b1aa736..c4ec560e9f 100644 --- a/pkg/commands/history.go +++ b/pkg/commands/history.go @@ -9,8 +9,8 @@ import ( "text/tabwriter" "time" - "github.com/scaleway/scaleway-cli/pkg/utils" "github.com/docker/docker/pkg/units" + "github.com/scaleway/scaleway-cli/pkg/utils" ) // HistoryArgs are flags for the `RunHistory` function @@ -22,14 +22,17 @@ type HistoryArgs struct { // RunHistory is the handler for 'scw history' func RunHistory(ctx CommandContext, args HistoryArgs) error { - imageID := ctx.API.GetImageID(args.Image, true) - image, err := ctx.API.GetImage(imageID) + imageID, err := ctx.API.GetImageID(args.Image, true) + if err != nil { + return err + } + image, err := ctx.API.GetImage(imageID.Identifier) if err != nil { - return fmt.Errorf("cannot get image %s: %v", imageID, err) + return fmt.Errorf("cannot get image %s: %v", imageID.Identifier, err) } if args.Quiet { - fmt.Fprintln(ctx.Stdout, imageID) + fmt.Fprintln(ctx.Stdout, imageID.Identifier) return nil } diff --git a/pkg/commands/images.go b/pkg/commands/images.go index da854d3df2..0e55f1d4e8 100644 --- a/pkg/commands/images.go +++ b/pkg/commands/images.go @@ -12,11 +12,11 @@ import ( "text/tabwriter" "time" - "github.com/scaleway/scaleway-cli/pkg/api" - "github.com/scaleway/scaleway-cli/pkg/utils" "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/units" "github.com/renstrom/fuzzysearch/fuzzy" + "github.com/scaleway/scaleway-cli/pkg/api" + "github.com/scaleway/scaleway-cli/pkg/utils" ) // ImagesArgs are flags for the `RunImages` function @@ -31,24 +31,25 @@ type ImagesArgs struct { 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"] - // 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) + 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 { - logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) + errChan <- fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) + return } chEntries <- api.ScalewayImageInterface{ Type: "image", @@ -59,6 +60,9 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { Tag: "latest", VirtualSize: float64(val.RootVolume.Size), Organization: val.Organization, + // FIXME the region should not be hardcoded + Region: "fr-1", + Arch: val.Arch, } } }() @@ -71,12 +75,14 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { defer wg.Done() snapshots, err := ctx.API.GetSnapshots() if err != nil { - logrus.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err) + 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 { - logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) + errChan <- fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) + return } chEntries <- api.ScalewayImageInterface{ Type: "snapshot", @@ -87,6 +93,8 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, + // FIXME the region should not be hardcoded + Region: "fr-1", } } }() @@ -98,7 +106,8 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { defer wg.Done() bootscripts, err := ctx.API.GetBootscripts() if err != nil { - logrus.Fatalf("unable to fetch bootscripts from the Scaleway API: %v", err) + errChan <- fmt.Errorf("unable to fetch bootscripts from the Scaleway API: %v", err) + return } for _, val := range *bootscripts { chEntries <- api.ScalewayImageInterface{ @@ -107,6 +116,9 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { Name: val.Title, Tag: "", Public: false, + // FIXME the region should not be hardcoded + Region: "fr-1", + Arch: val.Arch, } } }() @@ -118,12 +130,14 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { defer wg.Done() volumes, err := ctx.API.GetVolumes() if err != nil { - logrus.Fatalf("unable to fetch volumes from the Scaleway API: %v", err) + 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 { - logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err) + errChan <- fmt.Errorf("unable to parse creation date from the Scaleway API: %v", err) + return } chEntries <- api.ScalewayImageInterface{ Type: "volume", @@ -134,6 +148,8 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { VirtualSize: float64(val.Size), Public: false, Organization: val.Organization, + // FIXME the region should not be hardcoded + Region: "fr-1", } } }() @@ -145,21 +161,19 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { close(chEntries) }() - done := false for { - select { - case entry, ok := <-chEntries: - if !ok { - done = true - break - } - entries = append(entries, entry) - } - if done { + 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": @@ -172,7 +186,7 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { 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") + fmt.Fprintf(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE\tREGION\tARCH\n") } sort.Sort(api.ByCreationDate(entries)) for _, image := range entries { @@ -227,7 +241,10 @@ func RunImages(ctx CommandContext, args ImagesArgs) error { } else { virtualSize = units.HumanSize(image.VirtualSize) } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", shortName, tag, shortID, creationDate, virtualSize) + if image.Arch == "" { + image.Arch = "n/a" + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", shortName, tag, shortID, creationDate, virtualSize, image.Region, image.Arch) } skipimage: diff --git a/pkg/commands/kill.go b/pkg/commands/kill.go index 0650e71b46..5f4742eabb 100644 --- a/pkg/commands/kill.go +++ b/pkg/commands/kill.go @@ -8,9 +8,9 @@ import ( "fmt" "os/exec" + "github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/pkg/api" "github.com/scaleway/scaleway-cli/pkg/utils" - "github.com/Sirupsen/logrus" ) // KillArgs are flags for the `RunKill` function @@ -21,7 +21,10 @@ type KillArgs struct { // RunKill is the handler for 'scw kill' func RunKill(ctx CommandContext, args KillArgs) error { - serverID := ctx.API.GetServerID(args.Server) + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } command := "halt" server, err := ctx.API.GetServer(serverID) if err != nil { diff --git a/pkg/commands/login.go b/pkg/commands/login.go index 93eb8690c0..8e945b120d 100644 --- a/pkg/commands/login.go +++ b/pkg/commands/login.go @@ -56,7 +56,9 @@ func selectKey(args *LoginArgs) error { fmt.Printf("[%d] %s\n", i+1, pubs[i]) } for { - promptUser("Which [id]: ", &args.SSHKey, true) + if err := promptUser("Which [id]: ", &args.SSHKey, true); err != nil { + return err + } id, err := strconv.ParseUint(strings.TrimSpace(args.SSHKey), 10, 32) if err != nil { fmt.Println(err) @@ -148,8 +150,12 @@ func connectAPI() (string, string, error) { if err != nil { return "", "", fmt.Errorf("unable to get your Hostname %v", err) } - promptUser("Login (cloud.scaleway.com): ", &email, true) - promptUser("Password: ", &password, false) + if err := promptUser("Login (cloud.scaleway.com): ", &email, true); err != nil { + return "", "", err + } + if err := promptUser("Password: ", &password, false); err != nil { + return "", "", err + } connect := api.ScalewayConnect{ Email: strings.Trim(email, "\n"), @@ -229,7 +235,7 @@ func RunLogin(ctx CommandContext, args LoginArgs) error { return cfg.Save() } -func promptUser(prompt string, output *string, echo bool) { +func promptUser(prompt string, output *string, echo bool) error { // FIXME: should use stdin/stdout from command context fmt.Fprintf(os.Stdout, prompt) os.Stdout.Sync() @@ -237,7 +243,7 @@ func promptUser(prompt string, output *string, echo bool) { if !echo { b, err := terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { - logrus.Fatalf("Unable to prompt for password: %s", err) + return fmt.Errorf("Unable to prompt for password: %s", err) } *output = string(b) fmt.Fprintf(os.Stdout, "\n") @@ -245,4 +251,5 @@ func promptUser(prompt string, output *string, echo bool) { reader := bufio.NewReader(os.Stdin) *output, _ = reader.ReadString('\n') } + return nil } diff --git a/pkg/commands/logs.go b/pkg/commands/logs.go index 3cbdcfe5e2..e7bc7359dd 100644 --- a/pkg/commands/logs.go +++ b/pkg/commands/logs.go @@ -19,7 +19,10 @@ type LogsArgs struct { // RunLogs is the handler for 'scw logs' func RunLogs(ctx CommandContext, args LogsArgs) error { - serverID := ctx.API.GetServerID(args.Server) + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } server, err := ctx.API.GetServer(serverID) if err != nil { return fmt.Errorf("failed to get server information for %s: %v", serverID, err) diff --git a/pkg/commands/port.go b/pkg/commands/port.go index 3d4092f8e7..6369c64200 100644 --- a/pkg/commands/port.go +++ b/pkg/commands/port.go @@ -19,7 +19,10 @@ type PortArgs struct { // RunPort is the handler for 'scw port' func RunPort(ctx CommandContext, args PortArgs) error { - serverID := ctx.API.GetServerID(args.Server) + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } server, err := ctx.API.GetServer(serverID) if err != nil { return fmt.Errorf("failed to get server information for %s: %v", serverID, err) diff --git a/pkg/commands/ps.go b/pkg/commands/ps.go index 25e31f96c6..aff77283e8 100644 --- a/pkg/commands/ps.go +++ b/pkg/commands/ps.go @@ -82,8 +82,11 @@ func RunPs(ctx CommandContext, args PsArgs) error { goto skipServer } case "image": - imageID := ctx.API.GetImageID(value, true) - if imageID != server.Image.Identifier { + imageID, err := ctx.API.GetImageID(value, true) + if err != nil { + goto skipServer + } + if imageID.Identifier != server.Image.Identifier { goto skipServer } case "ip": diff --git a/pkg/commands/rename.go b/pkg/commands/rename.go index 52dd98e5d1..f31c792e5c 100644 --- a/pkg/commands/rename.go +++ b/pkg/commands/rename.go @@ -18,17 +18,17 @@ type RenameArgs struct { // RunRename is the handler for 'scw rename' func RunRename(ctx CommandContext, args RenameArgs) error { - serverID := ctx.API.GetServerID(args.Server) - + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } var server api.ScalewayServerPatchDefinition - server.Name = &args.NewName - err := ctx.API.PatchServer(serverID, server) - if err != nil { + server.Name = &args.NewName + if err = ctx.API.PatchServer(serverID, server); err != nil { return fmt.Errorf("cannot rename server: %v", err) } - - ctx.API.Cache.InsertServer(serverID, *server.Name) - + // FIXME region, arch, owner, title + ctx.API.Cache.InsertServer(serverID, "fr-1", *server.Arch, *server.Organization, *server.Name) return nil } diff --git a/pkg/commands/restart.go b/pkg/commands/restart.go index b8d48ead36..846b6b57e5 100644 --- a/pkg/commands/restart.go +++ b/pkg/commands/restart.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/scaleway/scaleway-cli/pkg/api" "github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/pkg/api" ) // RestartArgs are flags for the `RunRestart` function @@ -26,19 +26,25 @@ func restartIdentifiers(ctx CommandContext, wait bool, servers []string, cr chan for _, needle := range servers { wg.Add(1) go func(needle string) { + res := "" + defer wg.Done() - server := ctx.API.GetServerID(needle) - res := server - err := ctx.API.PostServerAction(server, "reboot") + server, err := ctx.API.GetServerID(needle) if err != nil { - if err.Error() != "server is being stopped or rebooted" { - logrus.Errorf("failed to restart server %s: %s", server, err) - } - res = "" + logrus.Error(err) } else { - if wait { - // FIXME: handle gateway - api.WaitForServerReady(ctx.API, server, "") + 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 diff --git a/pkg/commands/rm.go b/pkg/commands/rm.go index 790704b3bd..08d26a324d 100644 --- a/pkg/commands/rm.go +++ b/pkg/commands/rm.go @@ -20,8 +20,10 @@ type RmArgs struct { func RunRm(ctx CommandContext, args RmArgs) error { hasError := false for _, needle := range args.Servers { - server := ctx.API.GetServerID(needle) - var err error + server, err := ctx.API.GetServerID(needle) + if err != nil { + return err + } if args.Force { err = ctx.API.DeleteServerSafe(server) } else { diff --git a/pkg/commands/rmi.go b/pkg/commands/rmi.go index 4978cf7037..7277dd7427 100644 --- a/pkg/commands/rmi.go +++ b/pkg/commands/rmi.go @@ -19,10 +19,12 @@ type RmiArgs struct { 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) + image, err := ctx.API.GetImageID(needle, true) if err != nil { - logrus.Errorf("failed to delete image %s: %s", image, err) + return err + } + if err = ctx.API.DeleteImage(image.Identifier); err != nil { + logrus.Errorf("failed to delete image %s: %s", image.Identifier, err) hasError = true } else { fmt.Fprintln(ctx.Stdout, needle) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 758620ec7f..39dc5f300a 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -12,10 +12,10 @@ import ( "strings" "time" + "github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/pkg/api" "github.com/scaleway/scaleway-cli/pkg/config" "github.com/scaleway/scaleway-cli/pkg/utils" - "github.com/Sirupsen/logrus" ) // RunArgs are flags for the `Run` function @@ -133,8 +133,7 @@ func Run(ctx CommandContext, args RunArgs) error { // start SERVER logrus.Info("Server start requested ...") - err = api.StartServer(ctx.API, serverID, false) - if err != nil { + if err = api.StartServer(ctx.API, serverID, false); err != nil { return fmt.Errorf("failed to start server %s: %v", serverID, err) } logrus.Info("Server is starting, this may take up to a minute ...") @@ -142,13 +141,13 @@ func Run(ctx CommandContext, args RunArgs) error { if args.Userdata != "" { addUserData(ctx, strings.Split(args.Userdata, " "), serverID) } + // Sync cache on disk + ctx.API.Sync() if args.Detach { fmt.Fprintln(ctx.Stdout, serverID) return nil } - // Sync cache on disk - ctx.API.Sync() closeTimeout := make(chan struct{}) timeoutExit := make(chan struct{}) diff --git a/pkg/commands/stop.go b/pkg/commands/stop.go index b49864015f..3683b99122 100644 --- a/pkg/commands/stop.go +++ b/pkg/commands/stop.go @@ -8,8 +8,8 @@ import ( "fmt" "time" - "github.com/scaleway/scaleway-cli/pkg/api" "github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/pkg/api" ) // StopArgs are flags for the `RunStop` function @@ -24,13 +24,15 @@ 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) + serverID, err := ctx.API.GetServerID(needle) + if err != nil { + return err + } action := "poweroff" if args.Terminate { action = "terminate" } - err := ctx.API.PostServerAction(serverID, action) - if err != nil { + if err = ctx.API.PostServerAction(serverID, action); 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 @@ -39,8 +41,7 @@ func RunStop(ctx CommandContext, args StopArgs) error { 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 { + if _, err = api.WaitForServerStopped(ctx.API, serverID); err != nil { logrus.Errorf("failed to wait for server %s: %v", serverID, err) hasError = true } diff --git a/pkg/commands/tag.go b/pkg/commands/tag.go index 2da961a37b..3bdf784cfd 100644 --- a/pkg/commands/tag.go +++ b/pkg/commands/tag.go @@ -16,7 +16,10 @@ type TagArgs struct { // RunTag is the handler for 'scw tag' func RunTag(ctx CommandContext, args TagArgs) error { - snapshotID := ctx.API.GetSnapshotID(args.Snapshot) + snapshotID, err := ctx.API.GetSnapshotID(args.Snapshot) + if err != nil { + return err + } snapshot, err := ctx.API.GetSnapshot(snapshotID) if err != nil { return fmt.Errorf("cannot fetch snapshot: %v", err) @@ -24,9 +27,11 @@ func RunTag(ctx CommandContext, args TagArgs) error { bootscriptID := "" if args.Bootscript != "" { - bootscriptID = ctx.API.GetBootscriptID(args.Bootscript) + bootscriptID, err = ctx.API.GetBootscriptID(args.Bootscript, args.Arch) + if err != nil { + return err + } } - image, err := ctx.API.PostImage(snapshot.Identifier, args.Name, bootscriptID, args.Arch) if err != nil { return fmt.Errorf("cannot create image: %v", err) diff --git a/pkg/commands/top.go b/pkg/commands/top.go index e9078d9bce..1a07b99a4b 100644 --- a/pkg/commands/top.go +++ b/pkg/commands/top.go @@ -8,9 +8,9 @@ import ( "fmt" "os/exec" + "github.com/Sirupsen/logrus" "github.com/scaleway/scaleway-cli/pkg/api" "github.com/scaleway/scaleway-cli/pkg/utils" - "github.com/Sirupsen/logrus" ) // TopArgs are flags for the `RunTop` function @@ -21,7 +21,10 @@ type TopArgs struct { // RunTop is the handler for 'scw top' func RunTop(ctx CommandContext, args TopArgs) error { - serverID := ctx.API.GetServerID(args.Server) + serverID, err := ctx.API.GetServerID(args.Server) + if err != nil { + return err + } command := "ps" server, err := ctx.API.GetServer(serverID) if err != nil { diff --git a/pkg/commands/wait.go b/pkg/commands/wait.go index 4cf8da0e00..fcb681c7f2 100644 --- a/pkg/commands/wait.go +++ b/pkg/commands/wait.go @@ -7,8 +7,8 @@ package commands import ( "fmt" - "github.com/scaleway/scaleway-cli/pkg/api" "github.com/Sirupsen/logrus" + "github.com/scaleway/scaleway-cli/pkg/api" ) // WaitArgs are flags for the `RunWait` function @@ -20,12 +20,15 @@ type WaitArgs struct { 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) + serverIdentifier, err := ctx.API.GetServerID(needle) if err != nil { - logrus.Errorf("failed to wait for server %s: %v", serverIdentifier, err) + logrus.Error(err) hasError = true + } else { + if _, err := api.WaitForServerStopped(ctx.API, serverIdentifier); err != nil { + logrus.Errorf("failed to wait for server %s: %v", serverIdentifier, err) + hasError = true + } } } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 8ecce97c7f..223f190e50 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -24,10 +24,10 @@ import ( "golang.org/x/crypto/ssh" - "github.com/scaleway/scaleway-cli/pkg/sshcommand" "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus" "github.com/moul/gotty-client" + "github.com/scaleway/scaleway-cli/pkg/sshcommand" ) // SpawnRedirection is used to redirects the fluxes