Add --json to many of the CLI commands

Add a --json flag to most of the CLI commands, to force output that
would have gone to stdout to be output as JSON instead of whatever form
it would otherwise take.  Error messages sent to stderr are still plain
text.

Use this chance to rework the wrapping logic so that it's more
consistent: commands that take multiple IDs attempt the specified
operation on all of them, and report errors afterward.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
Nalin Dahyabhai 2016-07-20 15:19:49 -04:00
parent 62e67e678d
commit b50f33bf70
13 changed files with 226 additions and 52 deletions

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -19,11 +20,19 @@ func container(flags *mflag.FlagSet, action string, m storage.Mall, args []strin
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
matches := []storage.Container{}
for _, container := range containers {
for _, arg := range args {
if container.ID != arg && container.Name != "" && container.Name != arg {
continue
}
matches = append(matches, container)
}
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(matches)
} else {
for _, container := range matches {
fmt.Printf("ID: %s\n", container.ID)
if container.Name != "" {
fmt.Printf("Name: %s\n", container.Name)
@ -37,6 +46,9 @@ func container(flags *mflag.FlagSet, action string, m storage.Mall, args []strin
fmt.Printf("Layer: %s\n", container.LayerID)
}
}
if len(matches) != len(args) {
return 1
}
return 0
}
@ -47,5 +59,8 @@ func init() {
usage: "Examine a container",
action: container,
minArgs: 1,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -14,10 +15,14 @@ func containers(flags *mflag.FlagSet, action string, m storage.Mall, args []stri
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
for _, container := range containers {
fmt.Printf("%s\n", container.ID)
if container.Name != "" {
fmt.Printf("\t%s\n", container.Name)
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(containers)
} else {
for _, container := range containers {
fmt.Printf("%s\n", container.ID)
if container.Name != "" {
fmt.Printf("\t%s\n", container.Name)
}
}
}
return 0
@ -30,5 +35,8 @@ func init() {
usage: "List containers",
action: containers,
maxArgs: 0,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -29,10 +30,14 @@ func createLayer(flags *mflag.FlagSet, action string, m storage.Mall, args []str
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
if layer.Name != "" {
fmt.Printf("%s\t%s\n", layer.ID, layer.Name)
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(layer)
} else {
fmt.Printf("%s\n", layer.ID)
if layer.Name != "" {
fmt.Printf("%s\t%s\n", layer.ID, layer.Name)
} else {
fmt.Printf("%s\n", layer.ID)
}
}
return 0
}
@ -56,10 +61,14 @@ func createImage(flags *mflag.FlagSet, action string, m storage.Mall, args []str
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
if image.Name != "" {
fmt.Printf("%s\t%s\n", image.ID, image.Name)
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(image)
} else {
fmt.Printf("%s\n", image.ID)
if image.Name != "" {
fmt.Printf("%s\t%s\n", image.ID, image.Name)
} else {
fmt.Printf("%s\n", image.ID)
}
}
return 0
}
@ -83,10 +92,14 @@ func createContainer(flags *mflag.FlagSet, action string, m storage.Mall, args [
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
if container.Name != "" {
fmt.Printf("%s\t%s\n", container.ID, container.Name)
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(container)
} else {
fmt.Printf("%s\n", container.ID)
if container.Name != "" {
fmt.Printf("%s\t%s\n", container.ID, container.Name)
} else {
fmt.Printf("%s\n", container.ID)
}
}
return 0
}
@ -103,6 +116,7 @@ func init() {
flags.StringVar(&paramName, []string{"-name", "n"}, "", "Layer name")
flags.StringVar(&paramID, []string{"-id", "i"}, "", "Layer ID")
flags.BoolVar(&paramCreateRO, []string{"-readonly", "r"}, false, "Mark as read-only")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
commands = append(commands, command{
@ -117,6 +131,7 @@ func init() {
flags.StringVar(&paramID, []string{"-id", "i"}, "", "Image ID")
flags.StringVar(&paramMetadata, []string{"-metadata", "m"}, "", "Metadata")
flags.StringVar(&paramMetadataFile, []string{"-metadata-file", "f"}, "", "Metadata File")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
commands = append(commands, command{
@ -131,6 +146,7 @@ func init() {
flags.StringVar(&paramID, []string{"-id", "i"}, "", "Container ID")
flags.StringVar(&paramMetadata, []string{"-metadata", "m"}, "", "Metadata")
flags.StringVar(&paramMetadataFile, []string{"-metadata-file", "f"}, "", "Metadata File")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -12,10 +13,22 @@ func deleteThing(flags *mflag.FlagSet, action string, m storage.Mall, args []str
if len(args) < 1 {
return 1
}
deleted := make(map[string]error)
for _, what := range args {
err := m.Delete(what)
deleted[what] = err
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(deleted)
} else {
for what, err := range deleted {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", what, err)
}
}
}
for _, err := range deleted {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", what, err)
return 1
}
}
@ -29,5 +42,8 @@ func init() {
usage: "Delete a layer or image or container",
minArgs: 1,
action: deleteThing,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,7 +1,9 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/storage"
@ -13,15 +15,25 @@ func exist(flags *mflag.FlagSet, action string, m storage.Mall, args []string) i
if len(args) < 1 {
return 1
}
allExist := true
anyMissing := false
existDict := make(map[string]bool)
for _, what := range args {
exists := m.Exists(what)
if !existQuiet {
fmt.Printf("%s: %v\n", what, exists)
existDict[what] = exists
if !exists {
anyMissing = true
}
allExist = allExist && exists
}
if !allExist {
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(existDict)
} else {
if !existQuiet {
for what, exists := range existDict {
fmt.Printf("%s: %v\n", what, exists)
}
}
}
if anyMissing {
return 1
}
return 0
@ -36,6 +48,7 @@ func init() {
action: exist,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&existQuiet, []string{"-quiet", "q"}, existQuiet, "Don't print names")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -14,11 +15,19 @@ func image(flags *mflag.FlagSet, action string, m storage.Mall, args []string) i
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
matched := []storage.Image{}
for _, image := range images {
for _, arg := range args {
if image.ID != arg && image.Name != "" && image.Name != arg {
continue
}
matched = append(matched, image)
}
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(matched)
} else {
for _, image := range matched {
fmt.Printf("ID: %s\n", image.ID)
if image.Name != "" {
fmt.Printf("Name: %s\n", image.Name)
@ -26,6 +35,9 @@ func image(flags *mflag.FlagSet, action string, m storage.Mall, args []string) i
fmt.Printf("Top Layer: %s\n", image.TopLayer)
}
}
if len(matched) != len(args) {
return 1
}
return 0
}
@ -36,5 +48,8 @@ func init() {
usage: "Examine an image",
action: image,
minArgs: 1,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -14,10 +15,14 @@ func images(flags *mflag.FlagSet, action string, m storage.Mall, args []string)
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
for _, image := range images {
fmt.Printf("%s\n", image.ID)
if image.Name != "" {
fmt.Printf("\t%s\n", image.Name)
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(images)
} else {
for _, image := range images {
fmt.Printf("%s\n", image.ID)
if image.Name != "" {
fmt.Printf("\t%s\n", image.Name)
}
}
}
return 0
@ -30,5 +35,8 @@ func init() {
usage: "List images",
action: images,
maxArgs: 0,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -16,6 +17,10 @@ func layers(flags *mflag.FlagSet, action string, m storage.Mall, args []string)
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(layers)
return 0
}
imageMap := make(map[string]storage.Image)
if images, err := m.Images(); err == nil {
for _, image := range images {
@ -92,6 +97,7 @@ func init() {
maxArgs: 0,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&listLayersTree, []string{"-tree", "t"}, listLayersTree, "Use a tree")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -22,7 +22,10 @@ type command struct {
action func(*mflag.FlagSet, string, storage.Mall, []string) int
}
var commands = []command{}
var (
commands = []command{}
jsonOutput = false
)
func main() {
if reexec.Init() {
@ -30,18 +33,24 @@ func main() {
}
graphRoot := "/var/lib/oci-storage"
graphDriver := os.Getenv("DOCKER_GRAPHDRIVER")
graphOptions := strings.Split(os.Getenv("DOCKER_STORAGE_OPTS"), ",")
graphDriver := os.Getenv("STORAGE_DRIVER")
if graphDriver == "" {
graphDriver = os.Getenv("DOCKER_GRAPHDRIVER")
}
graphOptions := strings.Split(os.Getenv("STORAGE_OPTS"), ",")
if len(graphOptions) == 1 && graphOptions[0] == "" {
graphOptions = nil
graphOptions = strings.Split(os.Getenv("DOCKER_STORAGE_OPTS"), ",")
if len(graphOptions) == 1 && graphOptions[0] == "" {
graphOptions = nil
}
}
debug := false
makeFlags := func(command string, eh mflag.ErrorHandling) *mflag.FlagSet {
flags := mflag.NewFlagSet(command, eh)
flags.StringVar(&graphRoot, []string{"-graph", "g"}, graphRoot, "Root of the storage tree")
flags.StringVar(&graphDriver, []string{"-storage-driver", "s"}, graphDriver, "Storage driver to use ($DOCKER_GRAPHDRIVER)")
flags.Var(opts.NewListOptsRef(&graphOptions, nil), []string{"-storage-opt"}, "Set storage driver options ($DOCKER_STORAGE_OPTS)")
flags.StringVar(&graphDriver, []string{"-storage-driver", "s"}, graphDriver, "Storage driver to use ($STORAGE_DRIVER)")
flags.Var(opts.NewListOptsRef(&graphOptions, nil), []string{"-storage-opt"}, "Set storage driver options ($STORAGE_OPTS)")
flags.BoolVar(&debug, []string{"-debug", "D"}, debug, "Print debugging information")
return flags
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -16,27 +17,29 @@ func metadata(flags *mflag.FlagSet, action string, m storage.Mall, args []string
if len(args) < 1 {
return 1
}
allExist := true
metadataDict := make(map[string]string)
missingAny := false
for _, what := range args {
exists := false
if container, err := m.GetContainer(what); err == nil {
exists = true
if metadataQuiet {
fmt.Printf("%s\n", strings.TrimSuffix(container.Metadata, "\n"))
} else {
fmt.Printf("%s: %s\n", what, strings.TrimSuffix(container.Metadata, "\n"))
}
metadataDict[what] = strings.TrimSuffix(container.Metadata, "\n")
} else if image, err := m.GetImage(what); err == nil {
exists = true
metadataDict[what] = strings.TrimSuffix(image.Metadata, "\n")
} else {
missingAny = true
}
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(metadataDict)
} else {
for id, metadata := range metadataDict {
if metadataQuiet {
fmt.Printf("%s\n", strings.TrimSuffix(image.Metadata, "\n"))
fmt.Printf("%s\n", metadata)
} else {
fmt.Printf("%s: %s\n", what, strings.TrimSuffix(image.Metadata, "\n"))
fmt.Printf("%s: %s\n", id, metadata)
}
}
allExist = allExist && exists
}
if !allExist {
if missingAny {
return 1
}
return 0
@ -78,7 +81,8 @@ func init() {
minArgs: 1,
action: metadata,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&metadataQuiet, []string{"-quiet", "q"}, metadataQuiet, "Don't print names and IDs")
flags.BoolVar(&metadataQuiet, []string{"-quiet", "q"}, metadataQuiet, "Omit names and IDs")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
commands = append(commands, command{

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -8,24 +9,61 @@ import (
"github.com/containers/storage/storage"
)
type mountPointOrError struct {
ID string `json:"id"`
MountPoint string `json:"mountpoint"`
Error error `json:"error"`
}
type mountPointError struct {
ID string `json:"id"`
Error error `json:"error"`
}
func mount(flags *mflag.FlagSet, action string, m storage.Mall, args []string) int {
moes := []mountPointOrError{}
for _, arg := range args {
result, err := m.Mount(arg, paramMountLabel)
if err != nil {
fmt.Fprintf(os.Stderr, "%s while mounting %s\n", err, arg)
moes = append(moes, mountPointOrError{arg, result, err})
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(moes)
} else {
for _, mountOrError := range moes {
if mountOrError.Error != nil {
fmt.Fprintf(os.Stderr, "%s while mounting %s\n", mountOrError.Error, mountOrError.ID)
}
fmt.Printf("%s\n", mountOrError.MountPoint)
}
}
for _, mountOrErr := range moes {
if mountOrErr.Error != nil {
return 1
}
fmt.Printf("%s\n", result)
}
return 0
}
func unmount(flags *mflag.FlagSet, action string, m storage.Mall, args []string) int {
mes := []mountPointError{}
errors := false
for _, arg := range args {
if err := m.Unmount(arg); err != nil {
fmt.Fprintf(os.Stderr, "%s while unmounting %s\n", err, arg)
return 1
err := m.Unmount(arg)
if err != nil {
errors = true
}
mes = append(mes, mountPointError{arg, err})
}
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(mes)
} else {
for _, me := range mes {
if me.Error != nil {
fmt.Fprintf(os.Stderr, "%s while unmounting %s\n", me.Error, me.ID)
}
}
}
if errors {
return 1
}
return 0
}
@ -39,6 +77,7 @@ func init() {
action: mount,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.StringVar(&paramMountLabel, []string{"-label", "l"}, "", "Mount Label")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
commands = append(commands, command{
@ -47,5 +86,8 @@ func init() {
usage: "Unmount a layer or container",
minArgs: 1,
action: unmount,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -14,8 +15,12 @@ func status(flags *mflag.FlagSet, action string, m storage.Mall, args []string)
fmt.Fprintf(os.Stderr, "status: %v\n", err)
return 1
}
for _, pair := range status {
fmt.Fprintf(os.Stderr, "%s: %s\n", pair[0], pair[1])
if jsonOutput {
json.NewEncoder(os.Stdout).Encode(status)
} else {
for _, pair := range status {
fmt.Fprintf(os.Stderr, "%s: %s\n", pair[0], pair[1])
}
}
return 0
}
@ -26,5 +31,8 @@ func init() {
usage: "Check on graph driver status",
minArgs: 0,
action: status,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
"os"
@ -10,8 +11,18 @@ import (
func wipe(flags *mflag.FlagSet, action string, m storage.Mall, args []string) int {
err := m.Wipe()
if jsonOutput {
if err == nil {
json.NewEncoder(os.Stdout).Encode(string(""))
} else {
json.NewEncoder(os.Stdout).Encode(err)
}
} else {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", action, err)
}
}
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", action, err)
return 1
}
return 0
@ -23,5 +34,8 @@ func init() {
usage: "Wipe all layers, images, and containers",
minArgs: 0,
action: wipe,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output")
},
})
}