Skip to content

Commit

Permalink
Support of 'scw images --filter' (scaleway#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
moul committed Aug 26, 2015
1 parent b1145cf commit a266afb
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 74 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,29 @@ List images.
Options:

-a, --all=false Show all iamges
-f, --filter="" Filter output based on conditions provided
-h, --help=false Print usage
--no-trunc=false Don't truncate output
-q, --quiet=false Only show numeric IDs

Examples:

$ scw images
$ scw images -a
$ scw images -q
$ scw images --no-trunc
$ scw images -f organization=me
$ scw images -f organization=official-distribs
$ scw images -f organization=official-apps
$ scw images -f organization=UUIDOFORGANIZATION
$ scw images -f name=ubuntu
$ scw images -f type=image
$ scw images -f type=bootscript
$ scw images -f type=snapshot
$ scw images -f type=volume
$ scw images -f public=true
$ scw images -f public=false
$ scw images -f "organization=me type=volume" -q
```


Expand Down Expand Up @@ -1088,10 +1108,11 @@ $ scw inspect myserver | jq '.[0].public_ip.address'

#### Features

* Support of `scw images --filter` option *(type, organization, name, public)* ([#134](https://github.com/scaleway/scaleway-cli/issues/134))
* Syncing cache to disk after server creation when running `scw run` in a non-detached mode
* Bump to Golang 1.5
* Support --tmp-ssh-key `scw {run,create}` option ([#99](https://github.com/scaleway/scaleway-cli/issues/99))
* Support -f `scw run --rm` option ([#117](https://github.com/scaleway/scaleway-cli/issues/117))
* Support of `scw run --rm` option ([#117](https://github.com/scaleway/scaleway-cli/issues/117))
* Support of `--gateway=login@host` ([#110](https://github.com/scaleway/scaleway-cli/issues/110))
* Upload local ssh key to scaleway account on `scw login` ([#100](https://github.com/scaleway/scaleway-cli/issues/100))
* Add a 'running indicator' for `scw run`, can be disabled with the new flag `--quiet`
Expand Down
1 change: 1 addition & 0 deletions pkg/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type ScalewayImageInterface struct {
VirtualSize float64
Public bool
Type string
Organization string
}

// ResolveGateway tries to resolve a server public ip address, else returns the input string, i.e. IPv4, hostname
Expand Down
46 changes: 41 additions & 5 deletions pkg/cli/cmd_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,52 @@

package cli

import "github.com/scaleway/scaleway-cli/pkg/commands"
import (
"strings"

"github.com/Sirupsen/logrus"
"github.com/scaleway/scaleway-cli/pkg/commands"
)

var cmdImages = &Command{
Exec: runImages,
UsageLine: "images [OPTIONS]",
Description: "List images",
Help: "List images.",
Examples: `
$ scw images
$ scw images -a
$ scw images -q
$ scw images --no-trunc
$ scw images -f organization=me
$ scw images -f organization=official-distribs
$ scw images -f organization=official-apps
$ scw images -f organization=UUIDOFORGANIZATION
$ scw images -f name=ubuntu
$ scw images -f type=image
$ scw images -f type=bootscript
$ scw images -f type=snapshot
$ scw images -f type=volume
$ scw images -f public=true
$ scw images -f public=false
$ scw images -f "organization=me type=volume" -q
`,
}

func init() {
cmdImages.Flag.BoolVar(&imagesA, []string{"a", "-all"}, false, "Show all iamges")
cmdImages.Flag.BoolVar(&imagesNoTrunc, []string{"-no-trunc"}, false, "Don't truncate output")
cmdImages.Flag.BoolVar(&imagesQ, []string{"q", "-quiet"}, false, "Only show numeric IDs")
cmdImages.Flag.BoolVar(&imagesHelp, []string{"h", "-help"}, false, "Print usage")
cmdImages.Flag.StringVar(&imagesFilters, []string{"f", "-filter"}, "", "Filter output based on conditions provided")
}

// Flags
var imagesA bool // -a flag
var imagesQ bool // -q flag
var imagesNoTrunc bool // -no-trunc flag
var imagesHelp bool // -h, --help flag
var imagesA bool // -a flag
var imagesQ bool // -q flag
var imagesNoTrunc bool // -no-trunc flag
var imagesHelp bool // -h, --help flag
var imagesFilters string // -f, --filters

func runImages(cmd *Command, rawArgs []string) error {
if imagesHelp {
Expand All @@ -38,6 +63,17 @@ func runImages(cmd *Command, rawArgs []string) error {
All: imagesA,
Quiet: imagesQ,
NoTrunc: imagesNoTrunc,
Filters: make(map[string]string, 0),
}
if imagesFilters != "" {
for _, filter := range strings.Split(imagesFilters, " ") {
parts := strings.SplitN(filter, "=", 2)
if _, ok := args.Filters[parts[0]]; ok {
logrus.Warnf("Duplicated filter: %q", parts[0])
} else {
args.Filters[parts[0]] = parts[1]
}
}
}
ctx := cmd.GetContext(rawArgs)
return commands.RunImages(ctx, args)
Expand Down
194 changes: 126 additions & 68 deletions pkg/commands/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ package commands
import (
"fmt"
"sort"
"strings"
"sync"
"text/tabwriter"
"time"

"github.com/renstrom/fuzzysearch/fuzzy"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-cli/pkg/utils"
"github.com/scaleway/scaleway-cli/vendor/github.com/Sirupsen/logrus"
Expand All @@ -22,6 +24,7 @@ type ImagesArgs struct {
All bool
NoTrunc bool
Quiet bool
Filters map[string]string
}

// RunImages is the handler for 'scw images'
Expand All @@ -30,98 +33,111 @@ func RunImages(ctx CommandContext, args ImagesArgs) error {
chEntries := make(chan api.ScalewayImageInterface)
var entries = []api.ScalewayImageInterface{}

// FIXME: remove log.Fatalf in routines
filterType := args.Filters["type"]

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),
}
}
}()
// FIXME: remove log.Fatalf in routines

if args.All {
if filterType == "" || filterType == "image" {
wg.Add(1)
go func() {
defer wg.Done()
snapshots, err := ctx.API.GetSnapshots()
images, err := ctx.API.GetImages()
if err != nil {
logrus.Fatalf("unable to fetch snapshots from the Scaleway API: %v", err)
logrus.Fatalf("unable to fetch images from the Scaleway API: %v", err)
}
for _, val := range *snapshots {
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: "snapshot",
Type: "image",
CreationDate: creationDate,
Identifier: val.Identifier,
Name: val.Name,
Tag: "<snapshot>",
VirtualSize: float64(val.Size),
Public: false,
Public: val.Public,
Tag: "latest",
VirtualSize: float64(val.RootVolume.Size),
Organization: val.Organization,
}
}
}()
}

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 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,
}
}
}()
}

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 filterType == "" || filterType == "bootscript" {
wg.Add(1)
go func() {
defer wg.Done()
bootscripts, err := ctx.API.GetBootscripts()
if err != nil {
logrus.Fatalf("unable to parse creation date from the Scaleway API: %v", err)
logrus.Fatalf("unable to fetch bootscripts 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,
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() {
Expand All @@ -144,13 +160,52 @@ func RunImages(ctx CommandContext, args ImagesArgs) error {
}
}

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 {
Expand All @@ -174,6 +229,9 @@ func RunImages(ctx CommandContext, args ImagesArgs) error {
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", shortName, tag, shortID, creationDate, virtualSize)
}

skipimage:
continue
}
return nil
}

0 comments on commit a266afb

Please sign in to comment.