Merge pull request #523 from mtrmac/cli-parsing

RFC: Reliable CLI parsing
This commit is contained in:
Daniel J Walsh
2018-12-07 09:24:31 -05:00
committed by GitHub
23 changed files with 3452 additions and 1410 deletions

View File

@@ -3,7 +3,7 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"os" "io"
"strings" "strings"
"github.com/containers/image/copy" "github.com/containers/image/copy"
@@ -11,100 +11,33 @@ import (
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
"github.com/containers/image/transports" "github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports" "github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// contextsFromGlobalOptions returns source and destionation types.SystemContext depending on c. type copyOptions struct {
func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.SystemContext, error) { global *globalOptions
sourceCtx, err := contextFromGlobalOptions(c, "src-") srcImage *imageOptions
if err != nil { destImage *imageDestOptions
return nil, nil, err additionalTags cli.StringSlice // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these
} removeSignatures bool // Do not copy signatures from the source image
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
destinationCtx, err := contextFromGlobalOptions(c, "dest-") format optionalString // Force conversion of the image to a specified format
if err != nil {
return nil, nil, err
}
return sourceCtx, destinationCtx, nil
} }
func copyHandler(c *cli.Context) error { func copyCmd(global *globalOptions) cli.Command {
if len(c.Args()) != 2 { sharedFlags, sharedOpts := sharedImageFlags()
cli.ShowCommandHelp(c, "copy") srcFlags, srcOpts := imageFlags(global, sharedOpts, "src-", "screds")
return errors.New("Exactly two arguments expected") destFlags, destOpts := imageDestFlags(global, sharedOpts, "dest-", "dcreds")
opts := copyOptions{global: global,
srcImage: srcOpts,
destImage: destOpts,
} }
policyContext, err := getPolicyContext(c) return cli.Command{
if err != nil { Name: "copy",
return fmt.Errorf("Error loading trust policy: %v", err) Usage: "Copy an IMAGE-NAME from one location to another",
} Description: fmt.Sprintf(`
defer policyContext.Destroy()
srcRef, err := alltransports.ParseImageName(c.Args()[0])
if err != nil {
return fmt.Errorf("Invalid source name %s: %v", c.Args()[0], err)
}
destRef, err := alltransports.ParseImageName(c.Args()[1])
if err != nil {
return fmt.Errorf("Invalid destination name %s: %v", c.Args()[1], err)
}
signBy := c.String("sign-by")
removeSignatures := c.Bool("remove-signatures")
sourceCtx, destinationCtx, err := contextsFromGlobalOptions(c)
if err != nil {
return err
}
var manifestType string
if c.IsSet("format") {
switch c.String("format") {
case "oci":
manifestType = imgspecv1.MediaTypeImageManifest
case "v2s1":
manifestType = manifest.DockerV2Schema1SignedMediaType
case "v2s2":
manifestType = manifest.DockerV2Schema2MediaType
default:
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format"))
}
}
if c.IsSet("additional-tag") {
for _, image := range c.StringSlice("additional-tag") {
ref, err := reference.ParseNormalizedNamed(image)
if err != nil {
return fmt.Errorf("error parsing additional-tag '%s': %v", image, err)
}
namedTagged, isNamedTagged := ref.(reference.NamedTagged)
if !isNamedTagged {
return fmt.Errorf("additional-tag '%s' must be a tagged reference", image)
}
destinationCtx.DockerArchiveAdditionalTags = append(destinationCtx.DockerArchiveAdditionalTags, namedTagged)
}
}
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
defer cancel()
_, err = copy.Image(ctx, policyContext, destRef, srcRef, &copy.Options{
RemoveSignatures: removeSignatures,
SignBy: signBy,
ReportWriter: os.Stdout,
SourceCtx: sourceCtx,
DestinationCtx: destinationCtx,
ForceManifestMIMEType: manifestType,
})
return err
}
var copyCmd = cli.Command{
Name: "copy",
Usage: "Copy an IMAGE-NAME from one location to another",
Description: fmt.Sprintf(`
Container "IMAGE-NAME" uses a "transport":"details" format. Container "IMAGE-NAME" uses a "transport":"details" format.
@@ -113,86 +46,99 @@ var copyCmd = cli.Command{
See skopeo(1) section "IMAGE NAMES" for the expected format See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")), `, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE", ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE",
Action: copyHandler, Action: commandAction(opts.run),
// FIXME: Do we need to namespace the GPG aspect? // FIXME: Do we need to namespace the GPG aspect?
Flags: []cli.Flag{ Flags: append(append(append([]cli.Flag{
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "additional-tag", Name: "additional-tag",
Usage: "additional tags (supports docker-archive)", Usage: "additional tags (supports docker-archive)",
}, Value: &opts.additionalTags, // Surprisingly StringSliceFlag does not support Destination:, but modifies Value: in place.
cli.StringFlag{ },
Name: "authfile", cli.BoolFlag{
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", Name: "remove-signatures",
}, Usage: "Do not copy signatures from SOURCE-IMAGE",
cli.BoolFlag{ Destination: &opts.removeSignatures,
Name: "remove-signatures", },
Usage: "Do not copy signatures from SOURCE-IMAGE", cli.StringFlag{
}, Name: "sign-by",
cli.StringFlag{ Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
Name: "sign-by", Destination: &opts.signByFingerprint,
Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`", },
}, cli.GenericFlag{
cli.StringFlag{ Name: "format, f",
Name: "src-creds, screds", Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)",
Value: "", Value: newOptionalStringValue(&opts.format),
Usage: "Use `USERNAME[:PASSWORD]` for accessing the source registry", },
}, }, sharedFlags...), srcFlags...), destFlags...),
cli.StringFlag{ }
Name: "dest-creds, dcreds", }
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the destination registry", func (opts *copyOptions) run(args []string, stdout io.Writer) error {
}, if len(args) != 2 {
cli.StringFlag{ return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")}
Name: "src-cert-dir", }
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry or daemon", policyContext, err := opts.global.getPolicyContext()
}, if err != nil {
cli.BoolTFlag{ return fmt.Errorf("Error loading trust policy: %v", err)
Name: "src-tls-verify", }
Usage: "require HTTPS and verify certificates when talking to the container source registry or daemon (defaults to true)", defer policyContext.Destroy()
},
cli.StringFlag{ srcRef, err := alltransports.ParseImageName(args[0])
Name: "dest-cert-dir", if err != nil {
Value: "", return fmt.Errorf("Invalid source name %s: %v", args[0], err)
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry or daemon", }
}, destRef, err := alltransports.ParseImageName(args[1])
cli.BoolTFlag{ if err != nil {
Name: "dest-tls-verify", return fmt.Errorf("Invalid destination name %s: %v", args[1], err)
Usage: "require HTTPS and verify certificates when talking to the container destination registry or daemon (defaults to true)", }
},
cli.StringFlag{ sourceCtx, err := opts.srcImage.newSystemContext()
Name: "dest-ostree-tmp-dir", if err != nil {
Value: "", return err
Usage: "`DIRECTORY` to use for OSTree temporary files", }
}, destinationCtx, err := opts.destImage.newSystemContext()
cli.StringFlag{ if err != nil {
Name: "src-shared-blob-dir", return err
Value: "", }
Usage: "`DIRECTORY` to use to fetch retrieved blobs (OCI layout sources only)",
}, var manifestType string
cli.StringFlag{ if opts.format.present {
Name: "dest-shared-blob-dir", switch opts.format.value {
Value: "", case "oci":
Usage: "`DIRECTORY` to use to store retrieved blobs (OCI layout destinations only)", manifestType = imgspecv1.MediaTypeImageManifest
}, case "v2s1":
cli.StringFlag{ manifestType = manifest.DockerV2Schema1SignedMediaType
Name: "format, f", case "v2s2":
Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)", manifestType = manifest.DockerV2Schema2MediaType
}, default:
cli.BoolFlag{ return fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci', 'v2s1', or 'v2s2'", opts.format.value)
Name: "dest-compress", }
Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)", }
},
cli.StringFlag{ for _, image := range opts.additionalTags {
Name: "src-daemon-host", ref, err := reference.ParseNormalizedNamed(image)
Value: "", if err != nil {
Usage: "use docker daemon host at `HOST` (docker-daemon sources only)", return fmt.Errorf("error parsing additional-tag '%s': %v", image, err)
}, }
cli.StringFlag{ namedTagged, isNamedTagged := ref.(reference.NamedTagged)
Name: "dest-daemon-host", if !isNamedTagged {
Value: "", return fmt.Errorf("additional-tag '%s' must be a tagged reference", image)
Usage: "use docker daemon host at `HOST` (docker-daemon destinations only)", }
}, destinationCtx.DockerArchiveAdditionalTags = append(destinationCtx.DockerArchiveAdditionalTags, namedTagged)
}, }
ctx, cancel := opts.global.commandTimeoutContext()
defer cancel()
_, err = copy.Image(ctx, policyContext, destRef, srcRef, &copy.Options{
RemoveSignatures: opts.removeSignatures,
SignBy: opts.signByFingerprint,
ReportWriter: stdout,
SourceCtx: sourceCtx,
DestinationCtx: destinationCtx,
ForceManifestMIMEType: manifestType,
})
return err
} }

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"strings" "strings"
"github.com/containers/image/transports" "github.com/containers/image/transports"
@@ -10,30 +11,22 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func deleteHandler(c *cli.Context) error { type deleteOptions struct {
if len(c.Args()) != 1 { global *globalOptions
return errors.New("Usage: delete imageReference") image *imageOptions
}
ref, err := alltransports.ParseImageName(c.Args()[0])
if err != nil {
return fmt.Errorf("Invalid source name %s: %v", c.Args()[0], err)
}
sys, err := contextFromGlobalOptions(c, "")
if err != nil {
return err
}
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
defer cancel()
return ref.DeleteImage(ctx, sys)
} }
var deleteCmd = cli.Command{ func deleteCmd(global *globalOptions) cli.Command {
Name: "delete", sharedFlags, sharedOpts := sharedImageFlags()
Usage: "Delete image IMAGE-NAME", imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
Description: fmt.Sprintf(` opts := deleteOptions{
global: global,
image: imageOpts,
}
return cli.Command{
Name: "delete",
Usage: "Delete image IMAGE-NAME",
Description: fmt.Sprintf(`
Delete an "IMAGE_NAME" from a transport Delete an "IMAGE_NAME" from a transport
Supported transports: Supported transports:
@@ -41,26 +34,28 @@ var deleteCmd = cli.Command{
See skopeo(1) section "IMAGE NAMES" for the expected format See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")), `, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "IMAGE-NAME", ArgsUsage: "IMAGE-NAME",
Action: deleteHandler, Action: commandAction(opts.run),
Flags: []cli.Flag{ Flags: append(sharedFlags, imageFlags...),
cli.StringFlag{ }
Name: "authfile", }
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
}, func (opts *deleteOptions) run(args []string, stdout io.Writer) error {
cli.StringFlag{ if len(args) != 1 {
Name: "creds", return errors.New("Usage: delete imageReference")
Value: "", }
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry",
}, ref, err := alltransports.ParseImageName(args[0])
cli.StringFlag{ if err != nil {
Name: "cert-dir", return fmt.Errorf("Invalid source name %s: %v", args[0], err)
Value: "", }
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry",
}, sys, err := opts.image.newSystemContext()
cli.BoolTFlag{ if err != nil {
Name: "tls-verify", return err
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)", }
},
}, ctx, cancel := opts.global.commandTimeoutContext()
defer cancel()
return ref.DeleteImage(ctx, sys)
} }

75
cmd/skopeo/flag.go Normal file
View File

@@ -0,0 +1,75 @@
package main
import (
"strconv"
"github.com/urfave/cli"
)
// optionalBool is a boolean with a separate presence flag.
type optionalBool struct {
present bool
value bool
}
// optionalBool is a cli.Generic == flag.Value implementation equivalent to
// the one underlying flag.Bool, except that it records whether the flag has been set.
// This is distinct from optionalBool to (pretend to) force callers to use
// newOptionalBool
type optionalBoolValue optionalBool
func newOptionalBoolValue(p *optionalBool) cli.Generic {
p.present = false
return (*optionalBoolValue)(p)
}
func (ob *optionalBoolValue) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
return err
}
ob.value = v
ob.present = true
return nil
}
func (ob *optionalBoolValue) String() string {
if !ob.present {
return "" // This is, sadly, not round-trip safe: --flag is interpreted as --flag=true
}
return strconv.FormatBool(ob.value)
}
func (ob *optionalBoolValue) IsBoolFlag() bool {
return true
}
// optionalString is a string with a separate presence flag.
type optionalString struct {
present bool
value string
}
// optionalString is a cli.Generic == flag.Value implementation equivalent to
// the one underlying flag.String, except that it records whether the flag has been set.
// This is distinct from optionalString to (pretend to) force callers to use
// newoptionalString
type optionalStringValue optionalString
func newOptionalStringValue(p *optionalString) cli.Generic {
p.present = false
return (*optionalStringValue)(p)
}
func (ob *optionalStringValue) Set(s string) error {
ob.value = s
ob.present = true
return nil
}
func (ob *optionalStringValue) String() string {
if !ob.present {
return "" // This is, sadly, not round-trip safe: --flag= is interpreted as {present:true, value:""}
}
return ob.value
}

239
cmd/skopeo/flag_test.go Normal file
View File

@@ -0,0 +1,239 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
)
func TestOptionalBoolSet(t *testing.T) {
for _, c := range []struct {
input string
accepted bool
value bool
}{
// Valid inputs documented for strconv.ParseBool == flag.BoolVar
{"1", true, true},
{"t", true, true},
{"T", true, true},
{"TRUE", true, true},
{"true", true, true},
{"True", true, true},
{"0", true, false},
{"f", true, false},
{"F", true, false},
{"FALSE", true, false},
{"false", true, false},
{"False", true, false},
// A few invalid inputs
{"", false, false},
{"yes", false, false},
{"no", false, false},
{"2", false, false},
} {
var ob optionalBool
v := newOptionalBoolValue(&ob)
require.False(t, ob.present)
err := v.Set(c.input)
if c.accepted {
assert.NoError(t, err, c.input)
assert.Equal(t, c.value, ob.value)
} else {
assert.Error(t, err, c.input)
assert.False(t, ob.present) // Just to be extra paranoid.
}
}
// Nothing actually explicitly says that .Set() is never called when the flag is not present on the command line;
// so, check that it is not being called, at least in the straightforward case (it's not possible to test that it
// is not called in any possible situation).
var globalOB, commandOB optionalBool
actionRun := false
app := cli.NewApp()
app.EnableBashCompletion = true
app.Flags = []cli.Flag{
cli.GenericFlag{
Name: "global-OB",
Value: newOptionalBoolValue(&globalOB),
},
}
app.Commands = []cli.Command{{
Name: "cmd",
Flags: []cli.Flag{
cli.GenericFlag{
Name: "command-OB",
Value: newOptionalBoolValue(&commandOB),
},
},
Action: func(*cli.Context) error {
assert.False(t, globalOB.present)
assert.False(t, commandOB.present)
actionRun = true
return nil
},
}}
err := app.Run([]string{"app", "cmd"})
require.NoError(t, err)
assert.True(t, actionRun)
}
func TestOptionalBoolString(t *testing.T) {
for _, c := range []struct {
input optionalBool
expected string
}{
{optionalBool{present: true, value: true}, "true"},
{optionalBool{present: true, value: false}, "false"},
{optionalBool{present: false, value: true}, ""},
{optionalBool{present: false, value: false}, ""},
} {
var ob optionalBool
v := newOptionalBoolValue(&ob)
ob = c.input
res := v.String()
assert.Equal(t, c.expected, res)
}
}
func TestOptionalBoolIsBoolFlag(t *testing.T) {
// IsBoolFlag means that the argument value must either be part of the same argument, with =;
// if there is no =, the value is set to true.
// This differs form other flags, where the argument is required and may be either separated with = or supplied in the next argument.
for _, c := range []struct {
input []string
expectedOB optionalBool
expectedArgs []string
}{
{[]string{"1", "2"}, optionalBool{present: false}, []string{"1", "2"}}, // Flag not present
{[]string{"--OB=true", "1", "2"}, optionalBool{present: true, value: true}, []string{"1", "2"}}, // --OB=true
{[]string{"--OB=false", "1", "2"}, optionalBool{present: true, value: false}, []string{"1", "2"}}, // --OB=false
{[]string{"--OB", "true", "1", "2"}, optionalBool{present: true, value: true}, []string{"true", "1", "2"}}, // --OB true
{[]string{"--OB", "false", "1", "2"}, optionalBool{present: true, value: true}, []string{"false", "1", "2"}}, // --OB false
} {
var ob optionalBool
actionRun := false
app := cli.NewApp()
app.Commands = []cli.Command{{
Name: "cmd",
Flags: []cli.Flag{
cli.GenericFlag{
Name: "OB",
Value: newOptionalBoolValue(&ob),
},
},
Action: func(ctx *cli.Context) error {
assert.Equal(t, c.expectedOB, ob)
assert.Equal(t, c.expectedArgs, ([]string)(ctx.Args()))
actionRun = true
return nil
},
}}
err := app.Run(append([]string{"app", "cmd"}, c.input...))
require.NoError(t, err)
assert.True(t, actionRun)
}
}
func TestOptionalStringSet(t *testing.T) {
// Really just a smoke test, but differentiating between not present and empty.
for _, c := range []string{"", "hello"} {
var os optionalString
v := newOptionalStringValue(&os)
require.False(t, os.present)
err := v.Set(c)
assert.NoError(t, err, c)
assert.Equal(t, c, os.value)
}
// Nothing actually explicitly says that .Set() is never called when the flag is not present on the command line;
// so, check that it is not being called, at least in the straightforward case (it's not possible to test that it
// is not called in any possible situation).
var globalOS, commandOS optionalString
actionRun := false
app := cli.NewApp()
app.EnableBashCompletion = true
app.Flags = []cli.Flag{
cli.GenericFlag{
Name: "global-OS",
Value: newOptionalStringValue(&globalOS),
},
}
app.Commands = []cli.Command{{
Name: "cmd",
Flags: []cli.Flag{
cli.GenericFlag{
Name: "command-OS",
Value: newOptionalStringValue(&commandOS),
},
},
Action: func(*cli.Context) error {
assert.False(t, globalOS.present)
assert.False(t, commandOS.present)
actionRun = true
return nil
},
}}
err := app.Run([]string{"app", "cmd"})
require.NoError(t, err)
assert.True(t, actionRun)
}
func TestOptionalStringString(t *testing.T) {
for _, c := range []struct {
input optionalString
expected string
}{
{optionalString{present: true, value: "hello"}, "hello"},
{optionalString{present: true, value: ""}, ""},
{optionalString{present: false, value: "hello"}, ""},
{optionalString{present: false, value: ""}, ""},
} {
var os optionalString
v := newOptionalStringValue(&os)
os = c.input
res := v.String()
assert.Equal(t, c.expected, res)
}
}
func TestOptionalStringIsBoolFlag(t *testing.T) {
// NOTE: optionalStringValue does not implement IsBoolFlag!
// IsBoolFlag means that the argument value must either be part of the same argument, with =;
// if there is no =, the value is set to true.
// This differs form other flags, where the argument is required and may be either separated with = or supplied in the next argument.
for _, c := range []struct {
input []string
expectedOS optionalString
expectedArgs []string
}{
{[]string{"1", "2"}, optionalString{present: false}, []string{"1", "2"}}, // Flag not present
{[]string{"--OS=hello", "1", "2"}, optionalString{present: true, value: "hello"}, []string{"1", "2"}}, // --OS=true
{[]string{"--OS=", "1", "2"}, optionalString{present: true, value: ""}, []string{"1", "2"}}, // --OS=false
{[]string{"--OS", "hello", "1", "2"}, optionalString{present: true, value: "hello"}, []string{"1", "2"}}, // --OS true
{[]string{"--OS", "", "1", "2"}, optionalString{present: true, value: ""}, []string{"1", "2"}}, // --OS false
} {
var os optionalString
actionRun := false
app := cli.NewApp()
app.Commands = []cli.Command{{
Name: "cmd",
Flags: []cli.Flag{
cli.GenericFlag{
Name: "OS",
Value: newOptionalStringValue(&os),
},
},
Action: func(ctx *cli.Context) error {
assert.Equal(t, c.expectedOS, os)
assert.Equal(t, c.expectedArgs, ([]string)(ctx.Args()))
actionRun = true
return nil
},
}}
err := app.Run(append([]string{"app", "cmd"}, c.input...))
require.NoError(t, err)
assert.True(t, actionRun)
}
}

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"strings" "strings"
"time" "time"
@@ -29,10 +30,23 @@ type inspectOutput struct {
Layers []string Layers []string
} }
var inspectCmd = cli.Command{ type inspectOptions struct {
Name: "inspect", global *globalOptions
Usage: "Inspect image IMAGE-NAME", image *imageOptions
Description: fmt.Sprintf(` raw bool // Output the raw manifest instead of parsing information about the image
}
func inspectCmd(global *globalOptions) cli.Command {
sharedFlags, sharedOpts := sharedImageFlags()
imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
opts := inspectOptions{
global: global,
image: imageOpts,
}
return cli.Command{
Name: "inspect",
Usage: "Inspect image IMAGE-NAME",
Description: fmt.Sprintf(`
Return low-level information about "IMAGE-NAME" in a registry/transport Return low-level information about "IMAGE-NAME" in a registry/transport
Supported transports: Supported transports:
@@ -40,101 +54,90 @@ var inspectCmd = cli.Command{
See skopeo(1) section "IMAGE NAMES" for the expected format See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")), `, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "IMAGE-NAME", ArgsUsage: "IMAGE-NAME",
Flags: []cli.Flag{ Flags: append(append([]cli.Flag{
cli.StringFlag{ cli.BoolFlag{
Name: "authfile", Name: "raw",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", Usage: "output raw manifest",
}, Destination: &opts.raw,
cli.StringFlag{ },
Name: "cert-dir", }, sharedFlags...), imageFlags...),
Value: "", Action: commandAction(opts.run),
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry", }
}, }
cli.BoolTFlag{
Name: "tls-verify", func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) {
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)", ctx, cancel := opts.global.commandTimeoutContext()
}, defer cancel()
cli.BoolFlag{
Name: "raw", if len(args) != 1 {
Usage: "output raw manifest", return errors.New("Exactly one argument expected")
}, }
cli.StringFlag{ img, err := parseImage(ctx, opts.image, args[0])
Name: "creds", if err != nil {
Value: "", return err
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry", }
},
}, defer func() {
Action: func(c *cli.Context) (retErr error) { if err := img.Close(); err != nil {
ctx, cancel := commandTimeoutContextFromGlobalOptions(c) retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err))
defer cancel() }
}()
img, err := parseImage(ctx, c)
if err != nil { rawManifest, _, err := img.Manifest(ctx)
return err if err != nil {
} return err
}
defer func() { if opts.raw {
if err := img.Close(); err != nil { _, err := stdout.Write(rawManifest)
retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err)) if err != nil {
} return fmt.Errorf("Error writing manifest to standard output: %v", err)
}() }
return nil
rawManifest, _, err := img.Manifest(ctx) }
if err != nil { imgInspect, err := img.Inspect(ctx)
return err if err != nil {
} return err
if c.Bool("raw") { }
_, err := c.App.Writer.Write(rawManifest) outputData := inspectOutput{
if err != nil { Name: "", // Set below if DockerReference() is known
return fmt.Errorf("Error writing manifest to standard output: %v", err) Tag: imgInspect.Tag,
} // Digest is set below.
return nil RepoTags: []string{}, // Possibly overriden for docker.Transport.
} Created: imgInspect.Created,
imgInspect, err := img.Inspect(ctx) DockerVersion: imgInspect.DockerVersion,
if err != nil { Labels: imgInspect.Labels,
return err Architecture: imgInspect.Architecture,
} Os: imgInspect.Os,
outputData := inspectOutput{ Layers: imgInspect.Layers,
Name: "", // Set below if DockerReference() is known }
Tag: imgInspect.Tag, outputData.Digest, err = manifest.Digest(rawManifest)
// Digest is set below. if err != nil {
RepoTags: []string{}, // Possibly overriden for docker.Transport. return fmt.Errorf("Error computing manifest digest: %v", err)
Created: imgInspect.Created, }
DockerVersion: imgInspect.DockerVersion, if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
Labels: imgInspect.Labels, outputData.Name = dockerRef.Name()
Architecture: imgInspect.Architecture, }
Os: imgInspect.Os, if img.Reference().Transport() == docker.Transport {
Layers: imgInspect.Layers, sys, err := opts.image.newSystemContext()
} if err != nil {
outputData.Digest, err = manifest.Digest(rawManifest) return err
if err != nil { }
return fmt.Errorf("Error computing manifest digest: %v", err) outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference())
} if err != nil {
if dockerRef := img.Reference().DockerReference(); dockerRef != nil { // some registries may decide to block the "list all tags" endpoint
outputData.Name = dockerRef.Name() // gracefully allow the inspect to continue in this case. Currently
} // the IBM Bluemix container registry has this restriction.
if img.Reference().Transport() == docker.Transport { if !strings.Contains(err.Error(), "401") {
sys, err := contextFromGlobalOptions(c, "") return fmt.Errorf("Error determining repository tags: %v", err)
if err != nil { }
return err logrus.Warnf("Registry disallows tag list retrieval; skipping")
} }
outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference()) }
if err != nil { out, err := json.MarshalIndent(outputData, "", " ")
// some registries may decide to block the "list all tags" endpoint if err != nil {
// gracefully allow the inspect to continue in this case. Currently return err
// the IBM Bluemix container registry has this restriction. }
if !strings.Contains(err.Error(), "401") { fmt.Fprintf(stdout, "%s\n", string(out))
return fmt.Errorf("Error determining repository tags: %v", err) return nil
}
logrus.Warnf("Registry disallows tag list retrieval; skipping")
}
}
out, err := json.MarshalIndent(outputData, "", " ")
if err != nil {
return err
}
fmt.Fprintln(c.App.Writer, string(out))
return nil
},
} }

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
@@ -15,114 +16,130 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var layersCmd = cli.Command{ type layersOptions struct {
Name: "layers", global *globalOptions
Usage: "Get layers of IMAGE-NAME", image *imageOptions
ArgsUsage: "IMAGE-NAME [LAYER...]", }
Hidden: true,
Action: func(c *cli.Context) (retErr error) { func layersCmd(global *globalOptions) cli.Command {
fmt.Fprintln(os.Stderr, `DEPRECATED: skopeo layers is deprecated in favor of skopeo copy`) sharedFlags, sharedOpts := sharedImageFlags()
if c.NArg() == 0 { imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
return errors.New("Usage: layers imageReference [layer...]") opts := layersOptions{
global: global,
image: imageOpts,
}
return cli.Command{
Name: "layers",
Usage: "Get layers of IMAGE-NAME",
ArgsUsage: "IMAGE-NAME [LAYER...]",
Hidden: true,
Action: commandAction(opts.run),
Flags: append(sharedFlags, imageFlags...),
}
}
func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) {
fmt.Fprintln(os.Stderr, `DEPRECATED: skopeo layers is deprecated in favor of skopeo copy`)
if len(args) == 0 {
return errors.New("Usage: layers imageReference [layer...]")
}
ctx, cancel := opts.global.commandTimeoutContext()
defer cancel()
sys, err := opts.image.newSystemContext()
if err != nil {
return err
}
cache := blobinfocache.DefaultCache(sys)
rawSource, err := parseImageSource(ctx, opts.image, args[0])
if err != nil {
return err
}
src, err := image.FromSource(ctx, sys, rawSource)
if err != nil {
if closeErr := rawSource.Close(); closeErr != nil {
return errors.Wrapf(err, " (close error: %v)", closeErr)
} }
ctx, cancel := commandTimeoutContextFromGlobalOptions(c) return err
defer cancel() }
defer func() {
if err := src.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
sys, err := contextFromGlobalOptions(c, "") type blobDigest struct {
digest digest.Digest
isConfig bool
}
var blobDigests []blobDigest
for _, dString := range args[1:] {
if !strings.HasPrefix(dString, "sha256:") {
dString = "sha256:" + dString
}
d, err := digest.Parse(dString)
if err != nil { if err != nil {
return err return err
} }
cache := blobinfocache.DefaultCache(sys) blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false})
rawSource, err := parseImageSource(ctx, c, c.Args()[0]) }
if len(blobDigests) == 0 {
layers := src.LayerInfos()
seenLayers := map[digest.Digest]struct{}{}
for _, info := range layers {
if _, ok := seenLayers[info.Digest]; !ok {
blobDigests = append(blobDigests, blobDigest{digest: info.Digest, isConfig: false})
seenLayers[info.Digest] = struct{}{}
}
}
configInfo := src.ConfigInfo()
if configInfo.Digest != "" {
blobDigests = append(blobDigests, blobDigest{digest: configInfo.Digest, isConfig: true})
}
}
tmpDir, err := ioutil.TempDir(".", "layers-")
if err != nil {
return err
}
tmpDirRef, err := directory.NewReference(tmpDir)
if err != nil {
return err
}
dest, err := tmpDirRef.NewImageDestination(ctx, nil)
if err != nil {
return err
}
defer func() {
if err := dest.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
for _, bd := range blobDigests {
r, blobSize, err := rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1}, cache)
if err != nil { if err != nil {
return err return err
} }
src, err := image.FromSource(ctx, sys, rawSource) if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil {
if err != nil { if closeErr := r.Close(); closeErr != nil {
if closeErr := rawSource.Close(); closeErr != nil {
return errors.Wrapf(err, " (close error: %v)", closeErr) return errors.Wrapf(err, " (close error: %v)", closeErr)
} }
return err return err
} }
defer func() { }
if err := src.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
type blobDigest struct { manifest, _, err := src.Manifest(ctx)
digest digest.Digest if err != nil {
isConfig bool return err
} }
var blobDigests []blobDigest if err := dest.PutManifest(ctx, manifest); err != nil {
for _, dString := range c.Args().Tail() { return err
if !strings.HasPrefix(dString, "sha256:") { }
dString = "sha256:" + dString
}
d, err := digest.Parse(dString)
if err != nil {
return err
}
blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false})
}
if len(blobDigests) == 0 { return dest.Commit(ctx)
layers := src.LayerInfos()
seenLayers := map[digest.Digest]struct{}{}
for _, info := range layers {
if _, ok := seenLayers[info.Digest]; !ok {
blobDigests = append(blobDigests, blobDigest{digest: info.Digest, isConfig: false})
seenLayers[info.Digest] = struct{}{}
}
}
configInfo := src.ConfigInfo()
if configInfo.Digest != "" {
blobDigests = append(blobDigests, blobDigest{digest: configInfo.Digest, isConfig: true})
}
}
tmpDir, err := ioutil.TempDir(".", "layers-")
if err != nil {
return err
}
tmpDirRef, err := directory.NewReference(tmpDir)
if err != nil {
return err
}
dest, err := tmpDirRef.NewImageDestination(ctx, nil)
if err != nil {
return err
}
defer func() {
if err := dest.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
for _, bd := range blobDigests {
r, blobSize, err := rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1}, cache)
if err != nil {
return err
}
if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil {
if closeErr := r.Close(); closeErr != nil {
return errors.Wrapf(err, " (close error: %v)", closeErr)
}
return err
}
}
manifest, _, err := src.Manifest(ctx)
if err != nil {
return err
}
if err := dest.PutManifest(ctx, manifest); err != nil {
return err
}
return dest.Commit(ctx)
},
} }

View File

@@ -1,8 +1,10 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"time"
"github.com/containers/image/signature" "github.com/containers/image/signature"
"github.com/containers/skopeo/version" "github.com/containers/skopeo/version"
@@ -15,8 +17,21 @@ import (
// and will be populated by the Makefile // and will be populated by the Makefile
var gitCommit = "" var gitCommit = ""
// createApp returns a cli.App to be run or tested. type globalOptions struct {
func createApp() *cli.App { debug bool // Enable debug output
tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
policyPath string // Path to a signature verification policy file
insecurePolicy bool // Use an "allow everything" signature verification policy
registriesDirPath string // Path to a "registries.d" registry configuratio directory
overrideArch string // Architecture to use for choosing images, instead of the runtime one
overrideOS string // OS to use for choosing images, instead of the runtime one
commandTimeout time.Duration // Timeout for the command execution
}
// createApp returns a cli.App, and the underlying globalOptions object, to be run or tested.
func createApp() (*cli.App, *globalOptions) {
opts := globalOptions{}
app := cli.NewApp() app := cli.NewApp()
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.Name = "skopeo" app.Name = "skopeo"
@@ -28,89 +43,106 @@ func createApp() *cli.App {
app.Usage = "Various operations with container images and container image registries" app.Usage = "Various operations with container images and container image registries"
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
Usage: "enable debug output", Usage: "enable debug output",
Destination: &opts.debug,
}, },
cli.BoolTFlag{ cli.GenericFlag{
Name: "tls-verify", Name: "tls-verify",
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)", Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)",
Hidden: true, Hidden: true,
Value: newOptionalBoolValue(&opts.tlsVerify),
}, },
cli.StringFlag{ cli.StringFlag{
Name: "policy", Name: "policy",
Value: "", Usage: "Path to a trust policy file",
Usage: "Path to a trust policy file", Destination: &opts.policyPath,
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "insecure-policy", Name: "insecure-policy",
Usage: "run the tool without any policy check", Usage: "run the tool without any policy check",
Destination: &opts.insecurePolicy,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "registries.d", Name: "registries.d",
Value: "", Usage: "use registry configuration files in `DIR` (e.g. for container signature storage)",
Usage: "use registry configuration files in `DIR` (e.g. for container signature storage)", Destination: &opts.registriesDirPath,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "override-arch", Name: "override-arch",
Value: "", Usage: "use `ARCH` instead of the architecture of the machine for choosing images",
Usage: "use `ARCH` instead of the architecture of the machine for choosing images", Destination: &opts.overrideArch,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "override-os", Name: "override-os",
Value: "", Usage: "use `OS` instead of the running OS for choosing images",
Usage: "use `OS` instead of the running OS for choosing images", Destination: &opts.overrideOS,
}, },
cli.DurationFlag{ cli.DurationFlag{
Name: "command-timeout", Name: "command-timeout",
Usage: "timeout for the command execution", Usage: "timeout for the command execution",
Destination: &opts.commandTimeout,
}, },
} }
app.Before = func(c *cli.Context) error { app.Before = opts.before
if c.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}
if c.GlobalIsSet("tls-verify") {
logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
}
return nil
}
app.Commands = []cli.Command{ app.Commands = []cli.Command{
copyCmd, copyCmd(&opts),
inspectCmd, inspectCmd(&opts),
layersCmd, layersCmd(&opts),
deleteCmd, deleteCmd(&opts),
manifestDigestCmd, manifestDigestCmd(),
standaloneSignCmd, standaloneSignCmd(),
standaloneVerifyCmd, standaloneVerifyCmd(),
untrustedSignatureDumpCmd, untrustedSignatureDumpCmd(),
} }
return app return app, &opts
}
// before is run by the cli package for any command, before running the command-specific handler.
func (opts *globalOptions) before(_ *cli.Context) error {
if opts.debug {
logrus.SetLevel(logrus.DebugLevel)
}
if opts.tlsVerify.present {
logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
}
return nil
} }
func main() { func main() {
if reexec.Init() { if reexec.Init() {
return return
} }
app := createApp() app, _ := createApp()
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }
// getPolicyContext handles the global "policy" flag. // getPolicyContext returns a *signature.PolicyContext based on opts.
func getPolicyContext(c *cli.Context) (*signature.PolicyContext, error) { func (opts *globalOptions) getPolicyContext() (*signature.PolicyContext, error) {
policyPath := c.GlobalString("policy") var policy *signature.Policy // This could be cached across calls in opts.
var policy *signature.Policy // This could be cached across calls, if we had an application context.
var err error var err error
if c.GlobalBool("insecure-policy") { if opts.insecurePolicy {
policy = &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}} policy = &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
} else if policyPath == "" { } else if opts.policyPath == "" {
policy, err = signature.DefaultPolicy(nil) policy, err = signature.DefaultPolicy(nil)
} else { } else {
policy, err = signature.NewPolicyFromFile(policyPath) policy, err = signature.NewPolicyFromFile(opts.policyPath)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
return signature.NewPolicyContext(policy) return signature.NewPolicyContext(policy)
} }
// commandTimeoutContext returns a context.Context and a cancellation callback based on opts.
// The caller should usually "defer cancel()" immediately after calling this.
func (opts *globalOptions) commandTimeoutContext() (context.Context, context.CancelFunc) {
ctx := context.Background()
var cancel context.CancelFunc = func() {}
if opts.commandTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, opts.commandTimeout)
}
return ctx, cancel
}

View File

@@ -5,7 +5,7 @@ import "bytes"
// runSkopeo creates an app object and runs it with args, with an implied first "skopeo". // runSkopeo creates an app object and runs it with args, with an implied first "skopeo".
// Returns output intended for stdout and the returned error, if any. // Returns output intended for stdout and the returned error, if any.
func runSkopeo(args ...string) (string, error) { func runSkopeo(args ...string) (string, error) {
app := createApp() app, _ := createApp()
stdout := bytes.Buffer{} stdout := bytes.Buffer{}
app.Writer = &stdout app.Writer = &stdout
args = append([]string{"skopeo"}, args...) args = append([]string{"skopeo"}, args...)

View File

@@ -3,17 +3,31 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func manifestDigest(context *cli.Context) error { type manifestDigestOptions struct {
if len(context.Args()) != 1 { }
func manifestDigestCmd() cli.Command {
opts := manifestDigestOptions{}
return cli.Command{
Name: "manifest-digest",
Usage: "Compute a manifest digest of a file",
ArgsUsage: "MANIFEST",
Action: commandAction(opts.run),
}
}
func (opts *manifestDigestOptions) run(args []string, stdout io.Writer) error {
if len(args) != 1 {
return errors.New("Usage: skopeo manifest-digest manifest") return errors.New("Usage: skopeo manifest-digest manifest")
} }
manifestPath := context.Args()[0] manifestPath := args[0]
man, err := ioutil.ReadFile(manifestPath) man, err := ioutil.ReadFile(manifestPath)
if err != nil { if err != nil {
@@ -23,13 +37,6 @@ func manifestDigest(context *cli.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("Error computing digest: %v", err) return fmt.Errorf("Error computing digest: %v", err)
} }
fmt.Fprintf(context.App.Writer, "%s\n", digest) fmt.Fprintf(stdout, "%s\n", digest)
return nil return nil
} }
var manifestDigestCmd = cli.Command{
Name: "manifest-digest",
Usage: "Compute a manifest digest of a file",
ArgsUsage: "MANIFEST",
Action: manifestDigest,
}

View File

@@ -4,20 +4,41 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"github.com/containers/image/signature" "github.com/containers/image/signature"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func standaloneSign(c *cli.Context) error { type standaloneSignOptions struct {
outputFile := c.String("output") output string // Output file path
if len(c.Args()) != 3 || outputFile == "" { }
func standaloneSignCmd() cli.Command {
opts := standaloneSignOptions{}
return cli.Command{
Name: "standalone-sign",
Usage: "Create a signature using local files",
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT",
Action: commandAction(opts.run),
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "output the signature to `SIGNATURE`",
Destination: &opts.output,
},
},
}
}
func (opts *standaloneSignOptions) run(args []string, stdout io.Writer) error {
if len(args) != 3 || opts.output == "" {
return errors.New("Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature") return errors.New("Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature")
} }
manifestPath := c.Args()[0] manifestPath := args[0]
dockerReference := c.Args()[1] dockerReference := args[1]
fingerprint := c.Args()[2] fingerprint := args[2]
manifest, err := ioutil.ReadFile(manifestPath) manifest, err := ioutil.ReadFile(manifestPath)
if err != nil { if err != nil {
@@ -34,33 +55,33 @@ func standaloneSign(c *cli.Context) error {
return fmt.Errorf("Error creating signature: %v", err) return fmt.Errorf("Error creating signature: %v", err)
} }
if err := ioutil.WriteFile(outputFile, signature, 0644); err != nil { if err := ioutil.WriteFile(opts.output, signature, 0644); err != nil {
return fmt.Errorf("Error writing signature to %s: %v", outputFile, err) return fmt.Errorf("Error writing signature to %s: %v", opts.output, err)
} }
return nil return nil
} }
var standaloneSignCmd = cli.Command{ type standaloneVerifyOptions struct {
Name: "standalone-sign",
Usage: "Create a signature using local files",
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT",
Action: standaloneSign,
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "output the signature to `SIGNATURE`",
},
},
} }
func standaloneVerify(c *cli.Context) error { func standaloneVerifyCmd() cli.Command {
if len(c.Args()) != 4 { opts := standaloneVerifyOptions{}
return cli.Command{
Name: "standalone-verify",
Usage: "Verify a signature using local files",
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE",
Action: commandAction(opts.run),
}
}
func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error {
if len(args) != 4 {
return errors.New("Usage: skopeo standalone-verify manifest docker-reference key-fingerprint signature") return errors.New("Usage: skopeo standalone-verify manifest docker-reference key-fingerprint signature")
} }
manifestPath := c.Args()[0] manifestPath := args[0]
expectedDockerReference := c.Args()[1] expectedDockerReference := args[1]
expectedFingerprint := c.Args()[2] expectedFingerprint := args[2]
signaturePath := c.Args()[3] signaturePath := args[3]
unverifiedManifest, err := ioutil.ReadFile(manifestPath) unverifiedManifest, err := ioutil.ReadFile(manifestPath)
if err != nil { if err != nil {
@@ -81,22 +102,35 @@ func standaloneVerify(c *cli.Context) error {
return fmt.Errorf("Error verifying signature: %v", err) return fmt.Errorf("Error verifying signature: %v", err)
} }
fmt.Fprintf(c.App.Writer, "Signature verified, digest %s\n", sig.DockerManifestDigest) fmt.Fprintf(stdout, "Signature verified, digest %s\n", sig.DockerManifestDigest)
return nil return nil
} }
var standaloneVerifyCmd = cli.Command{ // WARNING: Do not use the contents of this for ANY security decisions,
Name: "standalone-verify", // and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
Usage: "Verify a signature using local files", // There is NO REASON to expect the values to be correct, or not intentionally misleading
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE", // (including things like “✅ Verified by $authority”)
Action: standaloneVerify, //
// The subcommand is undocumented, and it may be renamed or entirely disappear in the future.
type untrustedSignatureDumpOptions struct {
} }
func untrustedSignatureDump(c *cli.Context) error { func untrustedSignatureDumpCmd() cli.Command {
if len(c.Args()) != 1 { opts := untrustedSignatureDumpOptions{}
return cli.Command{
Name: "untrusted-signature-dump-without-verification",
Usage: "Dump contents of a signature WITHOUT VERIFYING IT",
ArgsUsage: "SIGNATURE",
Hidden: true,
Action: commandAction(opts.run),
}
}
func (opts *untrustedSignatureDumpOptions) run(args []string, stdout io.Writer) error {
if len(args) != 1 {
return errors.New("Usage: skopeo untrusted-signature-dump-without-verification signature") return errors.New("Usage: skopeo untrusted-signature-dump-without-verification signature")
} }
untrustedSignaturePath := c.Args()[0] untrustedSignaturePath := args[0]
untrustedSignature, err := ioutil.ReadFile(untrustedSignaturePath) untrustedSignature, err := ioutil.ReadFile(untrustedSignaturePath)
if err != nil { if err != nil {
@@ -111,20 +145,6 @@ func untrustedSignatureDump(c *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintln(c.App.Writer, string(untrustedOut)) fmt.Fprintln(stdout, string(untrustedOut))
return nil return nil
} }
// WARNING: Do not use the contents of this for ANY security decisions,
// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
// There is NO REASON to expect the values to be correct, or not intentionally misleading
// (including things like “✅ Verified by $authority”)
//
// The subcommand is undocumented, and it may be renamed or entirely disappear in the future.
var untrustedSignatureDumpCmd = cli.Command{
Name: "untrusted-signature-dump-without-verification",
Usage: "Dump contents of a signature WITHOUT VERIFYING IT",
ArgsUsage: "SIGNATURE",
Hidden: true,
Action: untrustedSignatureDump,
}

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"errors" "errors"
"io"
"strings" "strings"
"github.com/containers/image/transports/alltransports" "github.com/containers/image/transports/alltransports"
@@ -10,31 +11,125 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func contextFromGlobalOptions(c *cli.Context, flagPrefix string) (*types.SystemContext, error) { // errorShouldDisplayUsage is a subtype of error used by command handlers to indicate that cli.ShowSubcommandHelp should be called.
type errorShouldDisplayUsage struct {
error
}
// commandAction intermediates between the cli.ActionFunc interface and the real handler,
// primarily to ensure that cli.Context is not available to the handler, which in turn
// makes sure that the cli.String() etc. flag access functions are not used,
// and everything is done using the *Options structures and the Destination: members of cli.Flag.
// handler may return errorShouldDisplayUsage to cause cli.ShowSubcommandHelp to be called.
func commandAction(handler func(args []string, stdout io.Writer) error) cli.ActionFunc {
return func(c *cli.Context) error {
err := handler(([]string)(c.Args()), c.App.Writer)
if _, ok := err.(errorShouldDisplayUsage); ok {
cli.ShowSubcommandHelp(c)
}
return err
}
}
// sharedImageOptions collects CLI flags which are image-related, but do not change across images.
// This really should be a part of globalOptions, but that would break existing users of (skopeo copy --authfile=).
type sharedImageOptions struct {
authFilePath string // Path to a */containers/auth.json
}
// imageFlags prepares a collection of CLI flags writing into sharedImageOptions, and the managed sharedImageOptions structure.
func sharedImageFlags() ([]cli.Flag, *sharedImageOptions) {
opts := sharedImageOptions{}
return []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
Destination: &opts.authFilePath,
},
}, &opts
}
// imageOptions collects CLI flags which are the same across subcommands, but may be different for each image
// (e.g. may differ between the source and destination of a copy)
type imageOptions struct {
global *globalOptions // May be shared across several imageOptions instances.
shared *sharedImageOptions // May be shared across several imageOptions instances.
credsOption optionalString // username[:password] for accessing a registry
dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon
tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:)
sharedBlobDir string // A directory to use for OCI blobs, shared across repositories
dockerDaemonHost string // docker-daemon: host to connect to
}
// imageFlags prepares a collection of CLI flags writing into imageOptions, and the managed imageOptions structure.
func imageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageOptions) {
opts := imageOptions{
global: global,
shared: shared,
}
// This is horribly ugly, but we need to support the old option forms of (skopeo copy) for compatibility.
// Don't add any more cases like this.
credsOptionExtra := ""
if credsOptionAlias != "" {
credsOptionExtra += "," + credsOptionAlias
}
return []cli.Flag{
cli.GenericFlag{
Name: flagPrefix + "creds" + credsOptionExtra,
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry",
Value: newOptionalStringValue(&opts.credsOption),
},
cli.StringFlag{
Name: flagPrefix + "cert-dir",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry or daemon",
Destination: &opts.dockerCertPath,
},
cli.GenericFlag{
Name: flagPrefix + "tls-verify",
Usage: "require HTTPS and verify certificates when talking to the container registry or daemon (defaults to true)",
Value: newOptionalBoolValue(&opts.tlsVerify),
},
cli.StringFlag{
Name: flagPrefix + "shared-blob-dir",
Usage: "`DIRECTORY` to use to share blobs across OCI repositories",
Destination: &opts.sharedBlobDir,
},
cli.StringFlag{
Name: flagPrefix + "daemon-host",
Usage: "use docker daemon host at `HOST` (docker-daemon: only)",
Destination: &opts.dockerDaemonHost,
},
}, &opts
}
// newSystemContext returns a *types.SystemContext corresponding to opts.
// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) {
ctx := &types.SystemContext{ ctx := &types.SystemContext{
RegistriesDirPath: c.GlobalString("registries.d"), RegistriesDirPath: opts.global.registriesDirPath,
ArchitectureChoice: c.GlobalString("override-arch"), ArchitectureChoice: opts.global.overrideArch,
OSChoice: c.GlobalString("override-os"), OSChoice: opts.global.overrideOS,
DockerCertPath: c.String(flagPrefix + "cert-dir"), DockerCertPath: opts.dockerCertPath,
OSTreeTmpDirPath: c.String(flagPrefix + "ostree-tmp-dir"), OCISharedBlobDirPath: opts.sharedBlobDir,
OCISharedBlobDirPath: c.String(flagPrefix + "shared-blob-dir"), AuthFilePath: opts.shared.authFilePath,
DirForceCompress: c.Bool(flagPrefix + "compress"), DockerDaemonHost: opts.dockerDaemonHost,
AuthFilePath: c.String("authfile"), DockerDaemonCertPath: opts.dockerCertPath,
DockerDaemonHost: c.String(flagPrefix + "daemon-host"),
DockerDaemonCertPath: c.String(flagPrefix + "cert-dir"),
DockerDaemonInsecureSkipTLSVerify: !c.BoolT(flagPrefix + "tls-verify"),
} }
// DEPRECATED: we support --tls-verify for backward compatibility, but override if opts.tlsVerify.present {
// it if per-subcommand flags are provided (see below). ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.value
if c.GlobalIsSet("tls-verify") {
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.GlobalBoolT("tls-verify"))
} }
if c.IsSet(flagPrefix + "tls-verify") { // DEPRECATED: We support this for backward compatibility, but override it if a per-image flag is provided.
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT(flagPrefix + "tls-verify")) if opts.global.tlsVerify.present {
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.global.tlsVerify.value)
} }
if c.IsSet(flagPrefix + "creds") { if opts.tlsVerify.present {
ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!opts.tlsVerify.value)
}
if opts.credsOption.present {
var err error var err error
ctx.DockerAuthConfig, err = getDockerAuth(c.String(flagPrefix + "creds")) ctx.DockerAuthConfig, err = getDockerAuth(opts.credsOption.value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -42,13 +137,43 @@ func contextFromGlobalOptions(c *cli.Context, flagPrefix string) (*types.SystemC
return ctx, nil return ctx, nil
} }
func commandTimeoutContextFromGlobalOptions(c *cli.Context) (context.Context, context.CancelFunc) { // imageDestOptions is a superset of imageOptions specialized for iamge destinations.
ctx := context.Background() type imageDestOptions struct {
var cancel context.CancelFunc = func() {} *imageOptions
if c.GlobalDuration("command-timeout") > 0 { osTreeTmpDir string // A directory to use for OSTree temporary files
ctx, cancel = context.WithTimeout(ctx, c.GlobalDuration("command-timeout")) dirForceCompression bool // Compress layers when saving to the dir: transport
}
// imageDestFlags prepares a collection of CLI flags writing into imageDestOptions, and the managed imageDestOptions structure.
func imageDestFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageDestOptions) {
genericFlags, genericOptions := imageFlags(global, shared, flagPrefix, credsOptionAlias)
opts := imageDestOptions{imageOptions: genericOptions}
return append(genericFlags, []cli.Flag{
cli.StringFlag{
Name: flagPrefix + "ostree-tmp-dir",
Usage: "`DIRECTORY` to use for OSTree temporary files",
Destination: &opts.osTreeTmpDir,
},
cli.BoolFlag{
Name: flagPrefix + "compress",
Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
Destination: &opts.dirForceCompression,
},
}...), &opts
}
// newSystemContext returns a *types.SystemContext corresponding to opts.
// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
func (opts *imageDestOptions) newSystemContext() (*types.SystemContext, error) {
ctx, err := opts.imageOptions.newSystemContext()
if err != nil {
return nil, err
} }
return ctx, cancel
ctx.OSTreeTmpDirPath = opts.osTreeTmpDir
ctx.DirForceCompress = opts.dirForceCompression
return ctx, err
} }
func parseCreds(creds string) (string, string, error) { func parseCreds(creds string) (string, string, error) {
@@ -78,13 +203,12 @@ func getDockerAuth(creds string) (*types.DockerAuthConfig, error) {
// parseImage converts image URL-like string to an initialized handler for that image. // parseImage converts image URL-like string to an initialized handler for that image.
// The caller must call .Close() on the returned ImageCloser. // The caller must call .Close() on the returned ImageCloser.
func parseImage(ctx context.Context, c *cli.Context) (types.ImageCloser, error) { func parseImage(ctx context.Context, opts *imageOptions, name string) (types.ImageCloser, error) {
imgName := c.Args().First() ref, err := alltransports.ParseImageName(name)
ref, err := alltransports.ParseImageName(imgName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sys, err := contextFromGlobalOptions(c, "") sys, err := opts.newSystemContext()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -93,12 +217,12 @@ func parseImage(ctx context.Context, c *cli.Context) (types.ImageCloser, error)
// parseImageSource converts image URL-like string to an ImageSource. // parseImageSource converts image URL-like string to an ImageSource.
// The caller must call .Close() on the returned ImageSource. // The caller must call .Close() on the returned ImageSource.
func parseImageSource(ctx context.Context, c *cli.Context, name string) (types.ImageSource, error) { func parseImageSource(ctx context.Context, opts *imageOptions, name string) (types.ImageSource, error) {
ref, err := alltransports.ParseImageName(name) ref, err := alltransports.ParseImageName(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sys, err := contextFromGlobalOptions(c, "") sys, err := opts.newSystemContext()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -7,67 +7,60 @@ import (
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli"
) )
// fakeContext creates inputs for contextFromGlobalOptions. // fakeGlobalOptions creates globalOptions and sets it according to flags.
// NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens. // NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens.
func fakeContext(t *testing.T, cmdName string, globalFlags []string, cmdFlags []string) *cli.Context { func fakeGlobalOptions(t *testing.T, flags []string) *globalOptions {
app := createApp() app, opts := createApp()
globalSet := flag.NewFlagSet(app.Name, flag.ContinueOnError) flagSet := flag.NewFlagSet(app.Name, flag.ContinueOnError)
for _, f := range app.Flags { for _, f := range app.Flags {
f.Apply(globalSet) f.Apply(flagSet)
} }
err := globalSet.Parse(globalFlags) err := flagSet.Parse(flags)
require.NoError(t, err) require.NoError(t, err)
globalCtx := cli.NewContext(app, globalSet, nil)
cmd := app.Command(cmdName) return opts
require.NotNil(t, cmd)
cmdSet := flag.NewFlagSet(cmd.Name, flag.ContinueOnError)
for _, f := range cmd.Flags {
f.Apply(cmdSet)
}
err = cmdSet.Parse(cmdFlags)
require.NoError(t, err)
return cli.NewContext(app, cmdSet, globalCtx)
} }
func TestContextFromGlobalOptions(t *testing.T) { // fakeImageOptions creates imageOptions and sets it according to globalFlags/cmdFlags.
// FIXME: All of this only tests (skopeo copy --dest) // NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens.
// FIXME FIXME: Apparently BoolT values are set to false if the flag is not declared for the specific subcommand!! func fakeImageOptions(t *testing.T, flagPrefix string, globalFlags []string, cmdFlags []string) *imageOptions {
globalOpts := fakeGlobalOptions(t, globalFlags)
// Default state sharedFlags, sharedOpts := sharedImageFlags()
c := fakeContext(t, "copy", []string{}, []string{}) imageFlags, imageOpts := imageFlags(globalOpts, sharedOpts, flagPrefix, "")
res, err := contextFromGlobalOptions(c, "dest-") flagSet := flag.NewFlagSet("fakeImageOptions", flag.ContinueOnError)
for _, f := range append(sharedFlags, imageFlags...) {
f.Apply(flagSet)
}
err := flagSet.Parse(cmdFlags)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &types.SystemContext{}, res) return imageOpts
}
// Explicitly set everything to default, except for when the default is “not present” func TestImageOptionsNewSystemContext(t *testing.T) {
c = fakeContext(t, "copy", []string{}, []string{ // Default state
"--dest-compress=false", opts := fakeImageOptions(t, "dest-", []string{}, []string{})
}) res, err := opts.newSystemContext()
res, err = contextFromGlobalOptions(c, "dest-")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &types.SystemContext{}, res) assert.Equal(t, &types.SystemContext{}, res)
// Set everything to non-default values. // Set everything to non-default values.
c = fakeContext(t, "copy", []string{ opts = fakeImageOptions(t, "dest-", []string{
"--registries.d", "/srv/registries.d", "--registries.d", "/srv/registries.d",
"--override-arch", "overridden-arch", "--override-arch", "overridden-arch",
"--override-os", "overridden-os", "--override-os", "overridden-os",
}, []string{ }, []string{
"--authfile", "/srv/authfile", "--authfile", "/srv/authfile",
"--dest-cert-dir", "/srv/cert-dir", "--dest-cert-dir", "/srv/cert-dir",
"--dest-ostree-tmp-dir", "/srv/ostree-tmp-dir",
"--dest-shared-blob-dir", "/srv/shared-blob-dir", "--dest-shared-blob-dir", "/srv/shared-blob-dir",
"--dest-compress=true",
"--dest-daemon-host", "daemon-host.example.com", "--dest-daemon-host", "daemon-host.example.com",
"--dest-tls-verify=false", "--dest-tls-verify=false",
"--dest-creds", "creds-user:creds-password", "--dest-creds", "creds-user:creds-password",
}) })
res, err = contextFromGlobalOptions(c, "dest-") res, err = opts.newSystemContext()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &types.SystemContext{ assert.Equal(t, &types.SystemContext{
RegistriesDirPath: "/srv/registries.d", RegistriesDirPath: "/srv/registries.d",
@@ -78,11 +71,9 @@ func TestContextFromGlobalOptions(t *testing.T) {
DockerCertPath: "/srv/cert-dir", DockerCertPath: "/srv/cert-dir",
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue, DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
DockerAuthConfig: &types.DockerAuthConfig{Username: "creds-user", Password: "creds-password"}, DockerAuthConfig: &types.DockerAuthConfig{Username: "creds-user", Password: "creds-password"},
OSTreeTmpDirPath: "/srv/ostree-tmp-dir",
DockerDaemonCertPath: "/srv/cert-dir", DockerDaemonCertPath: "/srv/cert-dir",
DockerDaemonHost: "daemon-host.example.com", DockerDaemonHost: "daemon-host.example.com",
DockerDaemonInsecureSkipTLSVerify: true, DockerDaemonInsecureSkipTLSVerify: true,
DirForceCompress: true,
}, res) }, res)
// Global/per-command tlsVerify behavior // Global/per-command tlsVerify behavior
@@ -109,15 +100,85 @@ func TestContextFromGlobalOptions(t *testing.T) {
if c.cmd != "" { if c.cmd != "" {
cmdFlags = append(cmdFlags, "--dest-tls-verify="+c.cmd) cmdFlags = append(cmdFlags, "--dest-tls-verify="+c.cmd)
} }
ctx := fakeContext(t, "copy", globalFlags, cmdFlags) opts := fakeImageOptions(t, "dest-", globalFlags, cmdFlags)
res, err = contextFromGlobalOptions(ctx, "dest-") res, err = opts.newSystemContext()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, c.expectedDocker, res.DockerInsecureSkipTLSVerify, "%#v", c) assert.Equal(t, c.expectedDocker, res.DockerInsecureSkipTLSVerify, "%#v", c)
assert.Equal(t, c.expectedDockerDaemon, res.DockerDaemonInsecureSkipTLSVerify, "%#v", c) assert.Equal(t, c.expectedDockerDaemon, res.DockerDaemonInsecureSkipTLSVerify, "%#v", c)
} }
// Invalid option values // Invalid option values
c = fakeContext(t, "copy", []string{}, []string{"--dest-creds", ""}) opts = fakeImageOptions(t, "dest-", []string{}, []string{"--dest-creds", ""})
_, err = contextFromGlobalOptions(c, "dest-") _, err = opts.newSystemContext()
assert.Error(t, err)
}
// fakeImageDestOptions creates imageDestOptions and sets it according to globalFlags/cmdFlags.
// NOTE: This is QUITE FAKE; none of the urfave/cli normalization and the like happens.
func fakeImageDestOptions(t *testing.T, flagPrefix string, globalFlags []string, cmdFlags []string) *imageDestOptions {
globalOpts := fakeGlobalOptions(t, globalFlags)
sharedFlags, sharedOpts := sharedImageFlags()
imageFlags, imageOpts := imageDestFlags(globalOpts, sharedOpts, flagPrefix, "")
flagSet := flag.NewFlagSet("fakeImageDestOptions", flag.ContinueOnError)
for _, f := range append(sharedFlags, imageFlags...) {
f.Apply(flagSet)
}
err := flagSet.Parse(cmdFlags)
require.NoError(t, err)
return imageOpts
}
func TestImageDestOptionsNewSystemContext(t *testing.T) {
// Default state
opts := fakeImageDestOptions(t, "dest-", []string{}, []string{})
res, err := opts.newSystemContext()
require.NoError(t, err)
assert.Equal(t, &types.SystemContext{}, res)
// Explicitly set everything to default, except for when the default is “not present”
opts = fakeImageDestOptions(t, "dest-", []string{}, []string{
"--dest-compress=false",
})
res, err = opts.newSystemContext()
require.NoError(t, err)
assert.Equal(t, &types.SystemContext{}, res)
// Set everything to non-default values.
opts = fakeImageDestOptions(t, "dest-", []string{
"--registries.d", "/srv/registries.d",
"--override-arch", "overridden-arch",
"--override-os", "overridden-os",
}, []string{
"--authfile", "/srv/authfile",
"--dest-cert-dir", "/srv/cert-dir",
"--dest-ostree-tmp-dir", "/srv/ostree-tmp-dir",
"--dest-shared-blob-dir", "/srv/shared-blob-dir",
"--dest-compress=true",
"--dest-daemon-host", "daemon-host.example.com",
"--dest-tls-verify=false",
"--dest-creds", "creds-user:creds-password",
})
res, err = opts.newSystemContext()
require.NoError(t, err)
assert.Equal(t, &types.SystemContext{
RegistriesDirPath: "/srv/registries.d",
AuthFilePath: "/srv/authfile",
ArchitectureChoice: "overridden-arch",
OSChoice: "overridden-os",
OCISharedBlobDirPath: "/srv/shared-blob-dir",
DockerCertPath: "/srv/cert-dir",
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
DockerAuthConfig: &types.DockerAuthConfig{Username: "creds-user", Password: "creds-password"},
OSTreeTmpDirPath: "/srv/ostree-tmp-dir",
DockerDaemonCertPath: "/srv/cert-dir",
DockerDaemonHost: "daemon-host.example.com",
DockerDaemonInsecureSkipTLSVerify: true,
DirForceCompress: true,
}, res)
// Invalid option values in imageOptions
opts = fakeImageDestOptions(t, "dest-", []string{}, []string{"--dest-creds", ""})
_, err = opts.newSystemContext()
assert.Error(t, err) assert.Error(t, err)
} }

View File

@@ -1,4 +1,4 @@
github.com/urfave/cli v1.17.0 github.com/urfave/cli v1.20.0
github.com/kr/pretty v0.1.0 github.com/kr/pretty v0.1.0
github.com/kr/text v0.1.0 github.com/kr/text v0.1.0
github.com/containers/image master github.com/containers/image master

28
vendor/github.com/urfave/cli/LICENSE generated vendored
View File

@@ -1,21 +1,21 @@
Copyright (C) 2013 Jeremy Saenz MIT License
All Rights Reserved.
MIT LICENSE Copyright (c) 2016 Jeremy Saenz & Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy
this software and associated documentation files (the "Software"), to deal in of this software and associated documentation files (the "Software"), to deal
the Software without restriction, including without limitation the rights to in the Software without restriction, including without limitation the rights
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
the Software, and to permit persons to whom the Software is furnished to do so, copies of the Software, and to permit persons to whom the Software is
subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1202
vendor/github.com/urfave/cli/README.md generated vendored

File diff suppressed because it is too large Load Diff

141
vendor/github.com/urfave/cli/app.go generated vendored
View File

@@ -6,23 +6,19 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"sort" "sort"
"time" "time"
) )
var ( var (
changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md" changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errNonFuncAction = NewExitError("ERROR invalid Action type. "+ errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+ fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+
fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2) fmt.Sprintf("See %s", appActionDeprecationURL), 2)
) )
@@ -41,6 +37,8 @@ type App struct {
ArgsUsage string ArgsUsage string
// Version of the program // Version of the program
Version string Version string
// Description of the program
Description string
// List of commands to execute // List of commands to execute
Commands []Command Commands []Command
// List of flags to parse // List of flags to parse
@@ -61,10 +59,11 @@ type App struct {
// An action to execute after any subcommands are run, but after the subcommand has finished // An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics // It is run even if Action() panics
After AfterFunc After AfterFunc
// The action to execute when no subcommands are specified // The action to execute when no subcommands are specified
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
// *Note*: support for the deprecated `Action` signature will be removed in a future version
Action interface{} Action interface{}
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
// of deprecation period has passed, maybe?
// Execute this function if the proper command cannot be found // Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc CommandNotFound CommandNotFoundFunc
@@ -86,6 +85,12 @@ type App struct {
ErrWriter io.Writer ErrWriter io.Writer
// Other custom info // Other custom info
Metadata map[string]interface{} Metadata map[string]interface{}
// Carries a function which returns app specific info.
ExtraInfo func() map[string]string
// CustomAppHelpTemplate the text template for app help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomAppHelpTemplate string
didSetup bool didSetup bool
} }
@@ -139,13 +144,6 @@ func (a *App) Setup() {
} }
a.Commands = newCmds a.Commands = newCmds
a.categories = CommandCategories{}
for _, command := range a.Commands {
a.categories = a.categories.AddCommand(command.Category, command)
}
sort.Sort(a.categories)
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand) a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) { if (HelpFlag != BoolFlag{}) {
@@ -153,14 +151,23 @@ func (a *App) Setup() {
} }
} }
//append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion { if !a.HideVersion {
a.appendFlag(VersionFlag) a.appendFlag(VersionFlag)
} }
a.categories = CommandCategories{}
for _, command := range a.Commands {
a.categories = a.categories.AddCommand(command.Category, command)
}
sort.Sort(a.categories)
if a.Metadata == nil {
a.Metadata = make(map[string]interface{})
}
if a.Writer == nil {
a.Writer = os.Stdout
}
} }
// Run is the entry point to the cli app. Parses the arguments slice and routes // Run is the entry point to the cli app. Parses the arguments slice and routes
@@ -168,8 +175,20 @@ func (a *App) Setup() {
func (a *App) Run(arguments []string) (err error) { func (a *App) Run(arguments []string) (err error) {
a.Setup() a.Setup()
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags // parse flags
set := flagSet(a.Name, a.Flags) set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:]) err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
@@ -179,6 +198,7 @@ func (a *App) Run(arguments []string) (err error) {
ShowAppHelp(context) ShowAppHelp(context)
return nerr return nerr
} }
context.shellComplete = shellComplete
if checkCompletions(context) { if checkCompletions(context) {
return nil return nil
@@ -190,7 +210,7 @@ func (a *App) Run(arguments []string) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context) ShowAppHelp(context)
return err return err
} }
@@ -220,7 +240,6 @@ func (a *App) Run(arguments []string) (err error) {
if a.Before != nil { if a.Before != nil {
beforeErr := a.Before(context) beforeErr := a.Before(context)
if beforeErr != nil { if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
ShowAppHelp(context) ShowAppHelp(context)
HandleExitCoder(beforeErr) HandleExitCoder(beforeErr)
err = beforeErr err = beforeErr
@@ -237,6 +256,10 @@ func (a *App) Run(arguments []string) (err error) {
} }
} }
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action // Run default Action
err = HandleAction(a.Action, context) err = HandleAction(a.Action, context)
@@ -244,11 +267,12 @@ func (a *App) Run(arguments []string) (err error) {
return err return err
} }
// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling // RunAndExitOnError calls .Run() and exits non-zero if an error was returned
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
// to cli.App.Run. This will cause the application to exit with the given eror
// code in the cli.ExitCoder
func (a *App) RunAndExitOnError() { func (a *App) RunAndExitOnError() {
fmt.Fprintf(a.errWriter(),
"DEPRECATED cli.App.RunAndExitOnError. %s See %s\n",
contactSysadmin, runAndExitOnErrorDeprecationURL)
if err := a.Run(os.Args); err != nil { if err := a.Run(os.Args); err != nil {
fmt.Fprintln(a.errWriter(), err) fmt.Fprintln(a.errWriter(), err)
OsExiter(1) OsExiter(1)
@@ -277,13 +301,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
} }
a.Commands = newCmds a.Commands = newCmds
// append flags // parse flags
if a.EnableBashCompletion { set, err := flagSet(a.Name, a.Flags)
a.appendFlag(BashCompletionFlag) if err != nil {
return err
} }
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail()) err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
@@ -310,7 +333,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context) ShowSubcommandHelp(context)
return err return err
} }
@@ -451,48 +474,24 @@ type Author struct {
func (a Author) String() string { func (a Author) String() string {
e := "" e := ""
if a.Email != "" { if a.Email != "" {
e = "<" + a.Email + "> " e = " <" + a.Email + ">"
} }
return fmt.Sprintf("%v %v", a.Name, e) return fmt.Sprintf("%v%v", a.Name, e)
} }
// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an // HandleAction attempts to figure out which Action signature was used. If
// ActionFunc, a func with the legacy signature for Action, or some other // it's an ActionFunc or a func with the legacy signature for Action, the func
// invalid thing. If it's an ActionFunc or a func with the legacy signature for // is run!
// Action, the func is run!
func HandleAction(action interface{}, context *Context) (err error) { func HandleAction(action interface{}, context *Context) (err error) {
defer func() { if a, ok := action.(ActionFunc); ok {
if r := recover(); r != nil { return a(context)
switch r.(type) { } else if a, ok := action.(func(*Context) error); ok {
case error: return a(context)
err = r.(error) } else if a, ok := action.(func(*Context)); ok { // deprecated function signature
default: a(context)
err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2)
}
}
}()
if reflect.TypeOf(action).Kind() != reflect.Func {
return errNonFuncAction
}
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
if len(vals) == 0 {
fmt.Fprintf(ErrWriter,
"DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n",
contactSysadmin, appActionDeprecationURL)
return nil return nil
} else {
return errInvalidActionType
} }
if len(vals) > 1 {
return errInvalidActionSignature
}
if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok {
return retErr
}
return err
} }

View File

@@ -12,8 +12,11 @@
// app.Usage = "say a greeting" // app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) error { // app.Action = func(c *cli.Context) error {
// println("Greetings") // println("Greetings")
// return nil
// } // }
// //
// app.Run(os.Args) // app.Run(os.Args)
// } // }
package cli package cli
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go

View File

@@ -46,6 +46,11 @@ type Command struct {
Flags []Flag Flags []Flag
// Treat all flags as normal arguments if true // Treat all flags as normal arguments if true
SkipFlagParsing bool SkipFlagParsing bool
// Skip argument reordering which attempts to move flags before arguments,
// but only works if all flags appear after all arguments. This behavior was
// removed n version 2 since it only works under specific conditions so we
// backport here by exposing it as an option for compatibility.
SkipArgReorder bool
// Boolean to hide built-in help command // Boolean to hide built-in help command
HideHelp bool HideHelp bool
// Boolean to hide this command from help or completion // Boolean to hide this command from help or completion
@@ -54,6 +59,25 @@ type Command struct {
// Full name of command for help, defaults to full command name, including parent commands. // Full name of command for help, defaults to full command name, including parent commands.
HelpName string HelpName string
commandNamePath []string commandNamePath []string
// CustomHelpTemplate the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomHelpTemplate string
}
type CommandsByName []Command
func (c CommandsByName) Len() int {
return len(c)
}
func (c CommandsByName) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandsByName) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
} }
// FullName returns the full name of the command. // FullName returns the full name of the command.
@@ -82,14 +106,15 @@ func (c Command) Run(ctx *Context) (err error) {
) )
} }
if ctx.App.EnableBashCompletion { set, err := flagSet(c.Name, c.Flags)
c.Flags = append(c.Flags, BashCompletionFlag) if err != nil {
return err
} }
set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
if !c.SkipFlagParsing { if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
} else if !c.SkipArgReorder {
firstFlagIndex := -1 firstFlagIndex := -1
terminatorIndex := -1 terminatorIndex := -1
for index, arg := range ctx.Args() { for index, arg := range ctx.Args() {
@@ -122,21 +147,7 @@ func (c Command) Run(ctx *Context) (err error) {
err = set.Parse(ctx.Args().Tail()) err = set.Parse(ctx.Args().Tail())
} }
} else { } else {
if c.SkipFlagParsing { err = set.Parse(ctx.Args().Tail())
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
}
}
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(ctx, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
} }
nerr := normalizeFlags(c.Flags, set) nerr := normalizeFlags(c.Flags, set)
@@ -148,11 +159,23 @@ func (c Command) Run(ctx *Context) (err error) {
} }
context := NewContext(ctx.App, set, ctx) context := NewContext(ctx.App, set, ctx)
context.Command = c
if checkCommandCompletions(context, c.Name) { if checkCommandCompletions(context, c.Name) {
return nil return nil
} }
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(context.App.Writer)
ShowCommandHelp(context, c.Name)
return err
}
if checkCommandHelp(context, c.Name) { if checkCommandHelp(context, c.Name) {
return nil return nil
} }
@@ -174,15 +197,16 @@ func (c Command) Run(ctx *Context) (err error) {
if c.Before != nil { if c.Before != nil {
err = c.Before(context) err = c.Before(context)
if err != nil { if err != nil {
fmt.Fprintln(ctx.App.Writer, err) ShowCommandHelp(context, c.Name)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
} }
context.Command = c if c.Action == nil {
c.Action = helpSubcommand.Action
}
err = HandleAction(c.Action, context) err = HandleAction(c.Action, context)
if err != nil { if err != nil {
@@ -223,14 +247,13 @@ func (c Command) startApp(ctx *Context) error {
app.HelpName = app.Name app.HelpName = app.Name
} }
if c.Description != "" { app.Usage = c.Usage
app.Usage = c.Description app.Description = c.Description
} else { app.ArgsUsage = c.ArgsUsage
app.Usage = c.Usage
}
// set CommandNotFound // set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound
app.CustomAppHelpTemplate = c.CustomHelpTemplate
// set the flags and commands // set the flags and commands
app.Commands = c.Subcommands app.Commands = c.Subcommands
@@ -243,6 +266,7 @@ func (c Command) startApp(ctx *Context) error {
app.Author = ctx.App.Author app.Author = ctx.App.Author
app.Email = ctx.App.Email app.Email = ctx.App.Email
app.Writer = ctx.App.Writer app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
app.categories = CommandCategories{} app.categories = CommandCategories{}
for _, command := range c.Subcommands { for _, command := range c.Subcommands {
@@ -265,6 +289,7 @@ func (c Command) startApp(ctx *Context) error {
} else { } else {
app.Action = helpSubcommand.Action app.Action = helpSubcommand.Action
} }
app.OnUsageError = c.OnUsageError
for index, cc := range app.Commands { for index, cc := range app.Commands {
app.Commands[index].commandNamePath = []string{c.Name, cc.Name} app.Commands[index].commandNamePath = []string{c.Name, cc.Name}

View File

@@ -3,9 +3,9 @@ package cli
import ( import (
"errors" "errors"
"flag" "flag"
"strconv" "reflect"
"strings" "strings"
"time" "syscall"
) )
// Context is a type that is passed through to // Context is a type that is passed through to
@@ -13,147 +13,23 @@ import (
// can be used to retrieve context-specific Args and // can be used to retrieve context-specific Args and
// parsed command-line options. // parsed command-line options.
type Context struct { type Context struct {
App *App App *App
Command Command Command Command
flagSet *flag.FlagSet shellComplete bool
setFlags map[string]bool flagSet *flag.FlagSet
globalSetFlags map[string]bool setFlags map[string]bool
parentContext *Context parentContext *Context
} }
// NewContext creates a new context. For use in when invoking an App or Command action. // NewContext creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx} c := &Context{App: app, flagSet: set, parentContext: parentCtx}
}
// Int looks up the value of a local int flag, returns 0 if no int flag exists if parentCtx != nil {
func (c *Context) Int(name string) int { c.shellComplete = parentCtx.shellComplete
return lookupInt(name, c.flagSet)
}
// Duration looks up the value of a local time.Duration flag, returns 0 if no
// time.Duration flag exists
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// Float64 looks up the value of a local float64 flag, returns 0 if no float64
// flag exists
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// Bool looks up the value of a local bool flag, returns false if no bool flag exists
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// String looks up the value of a local string flag, returns "" if no string flag exists
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// StringSlice looks up the value of a local string slice flag, returns nil if no
// string slice flag exists
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// IntSlice looks up the value of a local int slice flag, returns nil if no int
// slice flag exists
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// Generic looks up the value of a local generic flag, returns nil if no generic
// flag exists
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalInt looks up the value of a global int flag, returns 0 if no int flag exists
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
} }
return 0
}
// GlobalFloat64 looks up the value of a global float64 flag, returns float64(0) return c
// if no float64 flag exists
func (c *Context) GlobalFloat64(name string) float64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return float64(0)
}
// GlobalDuration looks up the value of a global time.Duration flag, returns 0
// if no time.Duration flag exists
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// GlobalBool looks up the value of a global bool flag, returns false if no bool
// flag exists
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// GlobalBoolT looks up the value of a global bool flag, returns true if no bool
// flag exists
func (c *Context) GlobalBoolT(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBoolT(name, fs)
}
return false
}
// GlobalString looks up the value of a global string flag, returns "" if no
// string flag exists
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
// GlobalStringSlice looks up the value of a global string slice flag, returns
// nil if no string slice flag exists
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
// GlobalIntSlice looks up the value of a global int slice flag, returns nil if
// no int slice flag exists
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
// GlobalGeneric looks up the value of a global generic flag, returns nil if no
// generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
} }
// NumFlags returns the number of flags set // NumFlags returns the number of flags set
@@ -163,11 +39,13 @@ func (c *Context) NumFlags() int {
// Set sets a context flag to a value. // Set sets a context flag to a value.
func (c *Context) Set(name, value string) error { func (c *Context) Set(name, value string) error {
c.setFlags = nil
return c.flagSet.Set(name, value) return c.flagSet.Set(name, value)
} }
// GlobalSet sets a context flag to a value on the global flagset // GlobalSet sets a context flag to a value on the global flagset
func (c *Context) GlobalSet(name, value string) error { func (c *Context) GlobalSet(name, value string) error {
globalContext(c).setFlags = nil
return globalContext(c).flagSet.Set(name, value) return globalContext(c).flagSet.Set(name, value)
} }
@@ -175,28 +53,78 @@ func (c *Context) GlobalSet(name, value string) error {
func (c *Context) IsSet(name string) bool { func (c *Context) IsSet(name string) bool {
if c.setFlags == nil { if c.setFlags == nil {
c.setFlags = make(map[string]bool) c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) { c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true c.setFlags[f.Name] = true
}) })
c.flagSet.VisitAll(func(f *flag.Flag) {
if _, ok := c.setFlags[f.Name]; ok {
return
}
c.setFlags[f.Name] = false
})
// XXX hack to support IsSet for flags with EnvVar
//
// There isn't an easy way to do this with the current implementation since
// whether a flag was set via an environment variable is very difficult to
// determine here. Instead, we intend to introduce a backwards incompatible
// change in version 2 to add `IsSet` to the Flag interface to push the
// responsibility closer to where the information required to determine
// whether a flag is set by non-standard means such as environment
// variables is avaliable.
//
// See https://github.com/urfave/cli/issues/294 for additional discussion
flags := c.Command.Flags
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
if c.App != nil {
flags = c.App.Flags
}
}
for _, f := range flags {
eachName(f.GetName(), func(name string) {
if isSet, ok := c.setFlags[name]; isSet || !ok {
return
}
val := reflect.ValueOf(f)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
envVarValue := val.FieldByName("EnvVar")
if !envVarValue.IsValid() {
return
}
eachName(envVarValue.String(), func(envVar string) {
envVar = strings.TrimSpace(envVar)
if _, ok := syscall.Getenv(envVar); ok {
c.setFlags[name] = true
return
}
})
})
}
} }
return c.setFlags[name] == true
return c.setFlags[name]
} }
// GlobalIsSet determines if the global flag was actually set // GlobalIsSet determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool { func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil { ctx := c
c.globalSetFlags = make(map[string]bool) if ctx.parentContext != nil {
ctx := c ctx = ctx.parentContext
if ctx.parentContext != nil { }
ctx = ctx.parentContext
} for ; ctx != nil; ctx = ctx.parentContext {
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { if ctx.IsSet(name) {
ctx.flagSet.Visit(func(f *flag.Flag) { return true
c.globalSetFlags[f.Name] = true
})
} }
} }
return c.globalSetFlags[name] return false
} }
// FlagNames returns a slice of flag names used in this context. // FlagNames returns a slice of flag names used in this context.
@@ -228,6 +156,11 @@ func (c *Context) Parent() *Context {
return c.parentContext return c.parentContext
} }
// value returns the value of the flag coressponding to `name`
func (c *Context) value(name string) interface{} {
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
}
// Args contains apps console arguments // Args contains apps console arguments
type Args []string type Args []string
@@ -303,107 +236,6 @@ func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
return nil return nil
} }
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
val, err := strconv.Atoi(f.Value.String())
if err != nil {
return 0
}
return val
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
val, err := time.ParseDuration(f.Value.String())
if err == nil {
return val
}
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return val
}
return 0
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
return f.Value.String()
}
return ""
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*StringSlice)).Value()
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*IntSlice)).Value()
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
return f.Value
}
return nil
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return val
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return true
}
return val
}
return false
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) { switch ff.Value.(type) {
case *StringSlice: case *StringSlice:

View File

@@ -24,7 +24,7 @@ func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err} return MultiError{Errors: err}
} }
// Error implents the error interface. // Error implements the error interface.
func (m MultiError) Error() string { func (m MultiError) Error() string {
errs := make([]string, len(m.Errors)) errs := make([]string, len(m.Errors))
for i, err := range m.Errors { for i, err := range m.Errors {
@@ -34,6 +34,10 @@ func (m MultiError) Error() string {
return strings.Join(errs, "\n") return strings.Join(errs, "\n")
} }
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit // ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code // code
type ExitCoder interface { type ExitCoder interface {
@@ -44,11 +48,11 @@ type ExitCoder interface {
// ExitError fulfills both the builtin `error` interface and `ExitCoder` // ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct { type ExitError struct {
exitCode int exitCode int
message string message interface{}
} }
// NewExitError makes a new *ExitError // NewExitError makes a new *ExitError
func NewExitError(message string, exitCode int) *ExitError { func NewExitError(message interface{}, exitCode int) *ExitError {
return &ExitError{ return &ExitError{
exitCode: exitCode, exitCode: exitCode,
message: message, message: message,
@@ -58,7 +62,7 @@ func NewExitError(message string, exitCode int) *ExitError {
// Error returns the string message, fulfilling the interface required by // Error returns the string message, fulfilling the interface required by
// `error` // `error`
func (ee *ExitError) Error() string { func (ee *ExitError) Error() string {
return ee.message return fmt.Sprintf("%v", ee.message)
} }
// ExitCode returns the exit code, fulfilling the interface required by // ExitCode returns the exit code, fulfilling the interface required by
@@ -70,7 +74,7 @@ func (ee *ExitError) ExitCode() int {
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the // so prints the error to stderr (if it is non-empty) and calls OsExiter with the
// given exit code. If the given error is a MultiError, then this func is // given exit code. If the given error is a MultiError, then this func is
// called on all members of the Errors slice. // called on all members of the Errors slice and calls OsExiter with the last exit code.
func HandleExitCoder(err error) { func HandleExitCoder(err error) {
if err == nil { if err == nil {
return return
@@ -78,15 +82,34 @@ func HandleExitCoder(err error) {
if exitErr, ok := err.(ExitCoder); ok { if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" { if err.Error() != "" {
fmt.Fprintln(ErrWriter, err) if _, ok := exitErr.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
} }
OsExiter(exitErr.ExitCode()) OsExiter(exitErr.ExitCode())
return return
} }
if multiErr, ok := err.(MultiError); ok { if multiErr, ok := err.(MultiError); ok {
for _, merr := range multiErr.Errors { code := handleMultiError(multiErr)
HandleExitCoder(merr) OsExiter(code)
} return
} }
} }
func handleMultiError(multiErr MultiError) int {
code := 1
for _, merr := range multiErr.Errors {
if multiErr2, ok := merr.(MultiError); ok {
code = handleMultiError(multiErr2)
} else {
fmt.Fprintln(ErrWriter, merr)
if exitErr, ok := merr.(ExitCoder); ok {
code = exitErr.ExitCode()
}
}
}
return code
}

562
vendor/github.com/urfave/cli/flag.go generated vendored
View File

@@ -3,24 +3,24 @@ package cli
import ( import (
"flag" "flag"
"fmt" "fmt"
"os"
"reflect" "reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
) )
const defaultPlaceholder = "value" const defaultPlaceholder = "value"
// BashCompletionFlag enables bash-completion for all commands and subcommands // BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{ var BashCompletionFlag Flag = BoolFlag{
Name: "generate-bash-completion", Name: "generate-bash-completion",
Hidden: true, Hidden: true,
} }
// VersionFlag prints the version for the application // VersionFlag prints the version for the application
var VersionFlag = BoolFlag{ var VersionFlag Flag = BoolFlag{
Name: "version, v", Name: "version, v",
Usage: "print the version", Usage: "print the version",
} }
@@ -28,7 +28,7 @@ var VersionFlag = BoolFlag{
// HelpFlag prints the help for all commands and subcommands // HelpFlag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand // Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true) // unless HideHelp is set to true)
var HelpFlag = BoolFlag{ var HelpFlag Flag = BoolFlag{
Name: "help, h", Name: "help, h",
Usage: "show help", Usage: "show help",
} }
@@ -37,6 +37,21 @@ var HelpFlag = BoolFlag{
// to display a flag. // to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag var FlagStringer FlagStringFunc = stringifyFlag
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
func (f FlagsByName) Len() int {
return len(f)
}
func (f FlagsByName) Less(i, j int) bool {
return f[i].GetName() < f[j].GetName()
}
func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// Flag is a common interface related to parsing flags in cli. // Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that // For more advanced flag parsing techniques, it is recommended that
// this interface be implemented. // this interface be implemented.
@@ -47,13 +62,29 @@ type Flag interface {
GetName() string GetName() string
} }
func flagSet(name string, flags []Flag) *flag.FlagSet { // errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
type errorableFlag interface {
Flag
ApplyWithError(*flag.FlagSet) error
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError) set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags { for _, f := range flags {
f.Apply(set) //TODO remove in v2 when errorableFlag is removed
if ef, ok := f.(errorableFlag); ok {
if err := ef.ApplyWithError(set); err != nil {
return nil, err
}
} else {
f.Apply(set)
}
} }
return set return set, nil
} }
func eachName(longName string, fn func(string)) { func eachName(longName string, fn func(string)) {
@@ -70,31 +101,24 @@ type Generic interface {
String() string String() string
} }
// GenericFlag is the flag type for types implementing Generic
type GenericFlag struct {
Name string
Value Generic
Usage string
EnvVar string
Hidden bool
}
// String returns the string representation of the generic flag to display the
// help text to the user (uses the String() method of the generic flag to show
// the value)
func (f GenericFlag) String() string {
return FlagStringer(f)
}
// Apply takes the flagset and calls Set on the generic flag with the value // Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag // provided by the user for parsing by the flag
// Ignores parsing errors
func (f GenericFlag) Apply(set *flag.FlagSet) { func (f GenericFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value val := f.Value
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
val.Set(envVal) if err := val.Set(envVal); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
}
break break
} }
} }
@@ -103,14 +127,11 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
return nil
} }
// GetName returns the name of a flag. // StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
func (f GenericFlag) GetName() string {
return f.Name
}
// StringSlice is an opaque type for []string to satisfy flag.Value
type StringSlice []string type StringSlice []string
// Set appends the string value to the list of values // Set appends the string value to the list of values
@@ -129,31 +150,29 @@ func (f *StringSlice) Value() []string {
return *f return *f
} }
// StringSliceFlag is a string flag that can be specified multiple times on the // Get returns the slice of strings set by this flag
// command-line func (f *StringSlice) Get() interface{} {
type StringSliceFlag struct { return *f
Name string
Value *StringSlice
Usage string
EnvVar string
Hidden bool
}
// String returns the usage
func (f StringSliceFlag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringSliceFlag) Apply(set *flag.FlagSet) { func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &StringSlice{} newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
newVal.Set(s) if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
} }
f.Value = newVal f.Value = newVal
break break
@@ -167,14 +186,11 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
} }
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
return nil
} }
// GetName returns the name of a flag. // IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
func (f StringSliceFlag) GetName() string {
return f.Name
}
// IntSlice is an opaque type for []int to satisfy flag.Value
type IntSlice []int type IntSlice []int
// Set parses the value into an integer and appends it to the list of values // Set parses the value into an integer and appends it to the list of values
@@ -189,7 +205,7 @@ func (f *IntSlice) Set(value string) error {
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string { func (f *IntSlice) String() string {
return fmt.Sprintf("%d", *f) return fmt.Sprintf("%#v", *f)
} }
// Value returns the slice of ints set by this flag // Value returns the slice of ints set by this flag
@@ -197,33 +213,28 @@ func (f *IntSlice) Value() []int {
return *f return *f
} }
// IntSliceFlag is an int flag that can be specified multiple times on the // Get returns the slice of ints set by this flag
// command-line func (f *IntSlice) Get() interface{} {
type IntSliceFlag struct { return *f
Name string
Value *IntSlice
Usage string
EnvVar string
Hidden bool
}
// String returns the usage
func (f IntSliceFlag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntSliceFlag) Apply(set *flag.FlagSet) { func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &IntSlice{} newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
err := newVal.Set(s) if err := newVal.Set(s); err != nil {
if err != nil { return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
fmt.Fprintf(ErrWriter, err.Error())
} }
} }
f.Value = newVal f.Value = newVal
@@ -238,38 +249,96 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
} }
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
return nil
} }
// GetName returns the name of the flag. // Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
func (f IntSliceFlag) GetName() string { type Int64Slice []int64
return f.Name
}
// BoolFlag is a switch that defaults to false // Set parses the value into an integer and appends it to the list of values
type BoolFlag struct { func (f *Int64Slice) Set(value string) error {
Name string tmp, err := strconv.ParseInt(value, 10, 64)
Usage string if err != nil {
EnvVar string return err
Destination *bool }
Hidden bool *f = append(*f, tmp)
return nil
} }
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string { func (f *Int64Slice) String() string {
return FlagStringer(f) return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *Int64Slice) Value() []int64 {
return *f
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &Int64Slice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolFlag) Apply(set *flag.FlagSet) { func (f BoolFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
val := false val := false
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValBool, err := strconv.ParseBool(envVal) if envVal == "" {
if err == nil { val = false
val = envValBool break
} }
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break break
} }
} }
@@ -282,40 +351,35 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
} }
set.Bool(name, val, f.Usage) set.Bool(name, val, f.Usage)
}) })
}
// GetName returns the name of the flag. return nil
func (f BoolFlag) GetName() string {
return f.Name
}
// BoolTFlag this represents a boolean flag that is true by default, but can
// still be set to false by --some-flag=false
type BoolTFlag struct {
Name string
Usage string
EnvVar string
Destination *bool
Hidden bool
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolTFlag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolTFlag) Apply(set *flag.FlagSet) { func (f BoolTFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
val := true val := true
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValBool, err := strconv.ParseBool(envVal) if envVal == "" {
if err == nil { val = false
val = envValBool
break break
} }
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
} }
} }
} }
@@ -327,34 +391,22 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
} }
set.Bool(name, val, f.Usage) set.Bool(name, val, f.Usage)
}) })
}
// GetName returns the name of the flag. return nil
func (f BoolTFlag) GetName() string {
return f.Name
}
// StringFlag represents a flag that takes as string value
type StringFlag struct {
Name string
Value string
Usage string
EnvVar string
Destination *string
Hidden bool
}
// String returns the usage
func (f StringFlag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringFlag) Apply(set *flag.FlagSet) { func (f StringFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal f.Value = envVal
break break
} }
@@ -368,40 +420,28 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
} }
set.String(name, f.Value, f.Usage) set.String(name, f.Value, f.Usage)
}) })
}
// GetName returns the name of the flag. return nil
func (f StringFlag) GetName() string {
return f.Name
}
// IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct {
Name string
Value int
Usage string
EnvVar string
Destination *int
Hidden bool
}
// String returns the usage
func (f IntFlag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntFlag) Apply(set *flag.FlagSet) { func (f IntFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64) envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil { if err != nil {
f.Value = int(envValInt) return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = int(envValInt)
break
} }
} }
} }
@@ -413,40 +453,131 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
} }
set.Int(name, f.Value, f.Usage) set.Int(name, f.Value, f.Usage)
}) })
}
// GetName returns the name of the flag. return nil
func (f IntFlag) GetName() string {
return f.Name
}
// DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct {
Name string
Value time.Duration
Usage string
EnvVar string
Destination *time.Duration
Hidden bool
}
// String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) { // Ignores errors
func (f Int64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal) envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil { if err != nil {
f.Value = envValDuration return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = envValInt
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Int64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f UintFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Uint64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint64(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f DurationFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
break
} }
} }
} }
@@ -458,39 +589,29 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
} }
set.Duration(name, f.Value, f.Usage) set.Duration(name, f.Value, f.Usage)
}) })
}
// GetName returns the name of the flag. return nil
func (f DurationFlag) GetName() string {
return f.Name
}
// Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct {
Name string
Value float64
Usage string
EnvVar string
Destination *float64
Hidden bool
}
// String returns the usage
func (f Float64Flag) String() string {
return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
// Ignores errors
func (f Float64Flag) Apply(set *flag.FlagSet) { func (f Float64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" { if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10) envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil { if err != nil {
f.Value = float64(envValFloat) return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
} }
f.Value = float64(envValFloat)
break
} }
} }
} }
@@ -502,17 +623,15 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
} }
set.Float64(name, f.Value, f.Usage) set.Float64(name, f.Value, f.Usage)
}) })
}
// GetName returns the name of the flag. return nil
func (f Float64Flag) GetName() string {
return f.Name
} }
func visibleFlags(fl []Flag) []Flag { func visibleFlags(fl []Flag) []Flag {
visible := []Flag{} visible := []Flag{}
for _, flag := range fl { for _, flag := range fl {
if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { field := flagValue(flag).FieldByName("Hidden")
if !field.IsValid() || !field.Bool() {
visible = append(visible, flag) visible = append(visible, flag)
} }
} }
@@ -578,13 +697,24 @@ func withEnvHint(envVar, str string) string {
return str + envText return str + envText
} }
func stringifyFlag(f Flag) string { func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f) fv := reflect.ValueOf(f)
for fv.Kind() == reflect.Ptr {
fv = reflect.Indirect(fv)
}
return fv
}
func stringifyFlag(f Flag) string {
fv := flagValue(f)
switch f.(type) { switch f.(type) {
case IntSliceFlag: case IntSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(), return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyIntSliceFlag(f.(IntSliceFlag))) stringifyIntSliceFlag(f.(IntSliceFlag)))
case Int64SliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
case StringSliceFlag: case StringSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(), return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag))) stringifyStringSliceFlag(f.(StringSliceFlag)))
@@ -594,9 +724,8 @@ func stringifyFlag(f Flag) string {
needsPlaceholder := false needsPlaceholder := false
defaultValueString := "" defaultValueString := ""
val := fv.FieldByName("Value")
if val.IsValid() { if val := fv.FieldByName("Value"); val.IsValid() {
needsPlaceholder = true needsPlaceholder = true
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
@@ -630,6 +759,17 @@ func stringifyIntSliceFlag(f IntSliceFlag) string {
return stringifySliceFlag(f.Usage, f.Name, defaultVals) return stringifySliceFlag(f.Usage, f.Name, defaultVals)
} }
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifyStringSliceFlag(f StringSliceFlag) string { func stringifyStringSliceFlag(f StringSliceFlag) string {
defaultVals := []string{} defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {

627
vendor/github.com/urfave/cli/flag_generated.go generated vendored Normal file
View File

@@ -0,0 +1,627 @@
package cli
import (
"flag"
"strconv"
"time"
)
// WARNING: This file is generated!
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolFlag) GetName() string {
return f.Name
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// GlobalBool looks up the value of a global BoolFlag, returns
// false if not found
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// BoolTFlag is a flag with type bool that is true by default
type BoolTFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolTFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolTFlag) GetName() string {
return f.Name
}
// BoolT looks up the value of a local BoolTFlag, returns
// false if not found
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// GlobalBoolT looks up the value of a global BoolTFlag, returns
// false if not found
func (c *Context) GlobalBoolT(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBoolT(name, fs)
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value time.Duration
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f DurationFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f DurationFlag) GetName() string {
return f.Name
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// GlobalDuration looks up the value of a global DurationFlag, returns
// 0 if not found
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value float64
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Float64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Float64Flag) GetName() string {
return f.Name
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// GlobalFloat64 looks up the value of a global Float64Flag, returns
// 0 if not found
func (c *Context) GlobalFloat64(name string) float64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value Generic
}
// String returns a readable representation of this value
// (for usage defaults)
func (f GenericFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f GenericFlag) GetName() string {
return f.Name
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalGeneric looks up the value of a global GenericFlag, returns
// nil if not found
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int64
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64Flag) GetName() string {
return f.Name
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
return lookupInt64(name, c.flagSet)
}
// GlobalInt64 looks up the value of a global Int64Flag, returns
// 0 if not found
func (c *Context) GlobalInt64(name string) int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64(name, fs)
}
return 0
}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntFlag) GetName() string {
return f.Name
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// GlobalInt looks up the value of a global IntFlag, returns
// 0 if not found
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *IntSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntSliceFlag) GetName() string {
return f.Name
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
// nil if not found
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *Int64Slice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64SliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64SliceFlag) GetName() string {
return f.Name
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
return lookupInt64Slice(name, c.flagSet)
}
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
// nil if not found
func (c *Context) GlobalInt64Slice(name string) []int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringFlag) GetName() string {
return f.Name
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// GlobalString looks up the value of a global StringFlag, returns
// "" if not found
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *StringSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringSliceFlag) GetName() string {
return f.Name
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
// nil if not found
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint64
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Uint64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Uint64Flag) GetName() string {
return f.Name
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
return lookupUint64(name, c.flagSet)
}
// GlobalUint64 looks up the value of a global Uint64Flag, returns
// 0 if not found
func (c *Context) GlobalUint64(name string) uint64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint64(name, fs)
}
return 0
}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f UintFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f UintFlag) GetName() string {
return f.Name
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
return lookupUint(name, c.flagSet)
}
// GlobalUint looks up the value of a global UintFlag, returns
// 0 if not found
func (c *Context) GlobalUint(name string) uint {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint(name, fs)
}
return 0
}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}

146
vendor/github.com/urfave/cli/help.go generated vendored
View File

@@ -13,27 +13,31 @@ import (
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var AppHelpTemplate = `NAME: var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE: USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
{{if .Version}}{{if not .HideVersion}}
VERSION: VERSION:
{{.Version}} {{.Version}}{{end}}{{end}}{{if .Description}}
{{end}}{{end}}{{if len .Authors}}
AUTHOR(S): DESCRIPTION:
{{range .Authors}}{{.}}{{end}} {{.Description}}{{end}}{{if len .Authors}}
{{end}}{{if .VisibleCommands}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}} COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}} {{.Name}}:{{end}}{{range .VisibleCommands}}
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}} {{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{end}}{{if .Copyright}} {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT: COPYRIGHT:
{{.Copyright}} {{.Copyright}}{{end}}
{{end}}
` `
// CommandHelpTemplate is the text template for the command help topic. // CommandHelpTemplate is the text template for the command help topic.
@@ -43,7 +47,7 @@ var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY: CATEGORY:
{{.Category}}{{end}}{{if .Description}} {{.Category}}{{end}}{{if .Description}}
@@ -60,14 +64,14 @@ OPTIONS:
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME: var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE: USAGE:
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}} COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}} {{.Name}}:{{end}}{{range .VisibleCommands}}
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
{{end}}{{if .VisibleFlags}} {{end}}{{if .VisibleFlags}}
OPTIONS: OPTIONS:
{{range .VisibleFlags}}{{.}} {{range .VisibleFlags}}{{.}}
@@ -108,17 +112,43 @@ var helpSubcommand = Command{
// Prints help for the App or Command // Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{}) type helpPrinter func(w io.Writer, templ string, data interface{})
// Prints help for the App or Command with custom template function.
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
// HelpPrinter is a function that writes the help output. If not set a default // HelpPrinter is a function that writes the help output. If not set a default
// is used. The function signature is: // is used. The function signature is:
// func(w io.Writer, templ string, data interface{}) // func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp var HelpPrinter helpPrinter = printHelp
// HelpPrinterCustom is same as HelpPrinter but
// takes a custom function for template function map.
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
// VersionPrinter prints the version for the App // VersionPrinter prints the version for the App
var VersionPrinter = printVersion var VersionPrinter = printVersion
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
func ShowAppHelpAndExit(c *Context, exitCode int) {
ShowAppHelp(c)
os.Exit(exitCode)
}
// ShowAppHelp is an action that displays the help. // ShowAppHelp is an action that displays the help.
func ShowAppHelp(c *Context) { func ShowAppHelp(c *Context) (err error) {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) if c.App.CustomAppHelpTemplate == "" {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
return
}
customAppData := func() map[string]interface{} {
if c.App.ExtraInfo == nil {
return nil
}
return map[string]interface{}{
"ExtraInfo": c.App.ExtraInfo,
}
}
HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData())
return nil
} }
// DefaultAppComplete prints the list of subcommands as the default app completion method // DefaultAppComplete prints the list of subcommands as the default app completion method
@@ -133,6 +163,12 @@ func DefaultAppComplete(c *Context) {
} }
} }
// ShowCommandHelpAndExit - exits with code after showing help
func ShowCommandHelpAndExit(c *Context, command string, code int) {
ShowCommandHelp(c, command)
os.Exit(code)
}
// ShowCommandHelp prints help for the given command // ShowCommandHelp prints help for the given command
func ShowCommandHelp(ctx *Context, command string) error { func ShowCommandHelp(ctx *Context, command string) error {
// show the subcommand help for a command with subcommands // show the subcommand help for a command with subcommands
@@ -143,7 +179,11 @@ func ShowCommandHelp(ctx *Context, command string) error {
for _, c := range ctx.App.Commands { for _, c := range ctx.App.Commands {
if c.HasName(command) { if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) if c.CustomHelpTemplate != "" {
HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil)
} else {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
}
return nil return nil
} }
} }
@@ -186,12 +226,17 @@ func ShowCommandCompletions(ctx *Context, command string) {
} }
} }
func printHelp(out io.Writer, templ string, data interface{}) { func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"join": strings.Join, "join": strings.Join,
} }
if customFunc != nil {
for key, value := range customFunc {
funcMap[key] = value
}
}
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data) err := t.Execute(w, data)
if err != nil { if err != nil {
@@ -205,10 +250,14 @@ func printHelp(out io.Writer, templ string, data interface{}) {
w.Flush() w.Flush()
} }
func printHelp(out io.Writer, templ string, data interface{}) {
printHelpCustom(out, templ, data, nil)
}
func checkVersion(c *Context) bool { func checkVersion(c *Context) bool {
found := false found := false
if VersionFlag.Name != "" { if VersionFlag.GetName() != "" {
eachName(VersionFlag.Name, func(name string) { eachName(VersionFlag.GetName(), func(name string) {
if c.GlobalBool(name) || c.Bool(name) { if c.GlobalBool(name) || c.Bool(name) {
found = true found = true
} }
@@ -219,8 +268,8 @@ func checkVersion(c *Context) bool {
func checkHelp(c *Context) bool { func checkHelp(c *Context) bool {
found := false found := false
if HelpFlag.Name != "" { if HelpFlag.GetName() != "" {
eachName(HelpFlag.Name, func(name string) { eachName(HelpFlag.GetName(), func(name string) {
if c.GlobalBool(name) || c.Bool(name) { if c.GlobalBool(name) || c.Bool(name) {
found = true found = true
} }
@@ -239,7 +288,7 @@ func checkCommandHelp(c *Context, name string) bool {
} }
func checkSubcommandHelp(c *Context) bool { func checkSubcommandHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") { if c.Bool("h") || c.Bool("help") {
ShowSubcommandHelp(c) ShowSubcommandHelp(c)
return true return true
} }
@@ -247,20 +296,43 @@ func checkSubcommandHelp(c *Context) bool {
return false return false
} }
func checkCompletions(c *Context) bool { func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { if !a.EnableBashCompletion {
ShowCompletions(c) return false, arguments
return true
} }
return false pos := len(arguments) - 1
lastArg := arguments[pos]
if lastArg != "--"+BashCompletionFlag.GetName() {
return false, arguments
}
return true, arguments[:pos]
}
func checkCompletions(c *Context) bool {
if !c.shellComplete {
return false
}
if args := c.Args(); args.Present() {
name := args.First()
if cmd := c.App.Command(name); cmd != nil {
// let the command handle the completion
return false
}
}
ShowCompletions(c)
return true
} }
func checkCommandCompletions(c *Context, name string) bool { func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { if !c.shellComplete {
ShowCommandCompletions(c, name) return false
return true
} }
return false ShowCommandCompletions(c, name)
return true
} }