diff --git a/cmd/oci-storage/container.go b/cmd/oci-storage/container.go index cac5b36f8..c84115a2b 100644 --- a/cmd/oci-storage/container.go +++ b/cmd/oci-storage/container.go @@ -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") + }, }) } diff --git a/cmd/oci-storage/containers.go b/cmd/oci-storage/containers.go index 8f51c7a38..f6e217265 100644 --- a/cmd/oci-storage/containers.go +++ b/cmd/oci-storage/containers.go @@ -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") + }, }) } diff --git a/cmd/oci-storage/create.go b/cmd/oci-storage/create.go index c32f83d2b..5aeec150c 100644 --- a/cmd/oci-storage/create.go +++ b/cmd/oci-storage/create.go @@ -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(¶mName, []string{"-name", "n"}, "", "Layer name") flags.StringVar(¶mID, []string{"-id", "i"}, "", "Layer ID") flags.BoolVar(¶mCreateRO, []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(¶mID, []string{"-id", "i"}, "", "Image ID") flags.StringVar(¶mMetadata, []string{"-metadata", "m"}, "", "Metadata") flags.StringVar(¶mMetadataFile, []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(¶mID, []string{"-id", "i"}, "", "Container ID") flags.StringVar(¶mMetadata, []string{"-metadata", "m"}, "", "Metadata") flags.StringVar(¶mMetadataFile, []string{"-metadata-file", "f"}, "", "Metadata File") + flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "prefer JSON output") }, }) } diff --git a/cmd/oci-storage/delete.go b/cmd/oci-storage/delete.go index 722feeb54..f03319d79 100644 --- a/cmd/oci-storage/delete.go +++ b/cmd/oci-storage/delete.go @@ -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") + }, }) } diff --git a/cmd/oci-storage/exists.go b/cmd/oci-storage/exists.go index 1fae225de..1e3dd2ac5 100644 --- a/cmd/oci-storage/exists.go +++ b/cmd/oci-storage/exists.go @@ -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") }, }) } diff --git a/cmd/oci-storage/image.go b/cmd/oci-storage/image.go index e9a0b6911..3ca7e0ccb 100644 --- a/cmd/oci-storage/image.go +++ b/cmd/oci-storage/image.go @@ -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") + }, }) } diff --git a/cmd/oci-storage/images.go b/cmd/oci-storage/images.go index 8a989333e..9f3232b71 100644 --- a/cmd/oci-storage/images.go +++ b/cmd/oci-storage/images.go @@ -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") + }, }) } diff --git a/cmd/oci-storage/layers.go b/cmd/oci-storage/layers.go index da0af8dec..76ff6e15e 100644 --- a/cmd/oci-storage/layers.go +++ b/cmd/oci-storage/layers.go @@ -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") }, }) } diff --git a/cmd/oci-storage/main.go b/cmd/oci-storage/main.go index b61c4931d..cd85c684b 100644 --- a/cmd/oci-storage/main.go +++ b/cmd/oci-storage/main.go @@ -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 } diff --git a/cmd/oci-storage/metadata.go b/cmd/oci-storage/metadata.go index c7f8641c5..b4914839b 100644 --- a/cmd/oci-storage/metadata.go +++ b/cmd/oci-storage/metadata.go @@ -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{ diff --git a/cmd/oci-storage/mount.go b/cmd/oci-storage/mount.go index ea23fd09d..9258a3224 100644 --- a/cmd/oci-storage/mount.go +++ b/cmd/oci-storage/mount.go @@ -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(¶mMountLabel, []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") + }, }) } diff --git a/cmd/oci-storage/status.go b/cmd/oci-storage/status.go index a38d67afb..8faf39809 100644 --- a/cmd/oci-storage/status.go +++ b/cmd/oci-storage/status.go @@ -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") + }, }) } diff --git a/cmd/oci-storage/wipe.go b/cmd/oci-storage/wipe.go index 996c612f2..97296d10c 100644 --- a/cmd/oci-storage/wipe.go +++ b/cmd/oci-storage/wipe.go @@ -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") + }, }) }