| Index: go/src/infra/tools/cipd/apps/cipd/main.go
|
| diff --git a/go/src/infra/tools/cipd/apps/cipd/main.go b/go/src/infra/tools/cipd/apps/cipd/main.go
|
| index b5232dc0da98f9625bd5c9411a6138c732b3bf07..d33f4a841e9b1993a61593dacc715f61e97a06c0 100644
|
| --- a/go/src/infra/tools/cipd/apps/cipd/main.go
|
| +++ b/go/src/infra/tools/cipd/apps/cipd/main.go
|
| @@ -42,6 +42,20 @@ var log = gologger.Get()
|
| ////////////////////////////////////////////////////////////////////////////////
|
| // Common subcommand functions.
|
|
|
| +// pinInfo contains information about single package pin inside some site root,
|
| +// or an error related to it. It is passed through channels when running batch
|
| +// operations and dumped to JSON results file in doneWithPins.
|
| +type pinInfo struct {
|
| + // Pkg is package name. Always set.
|
| + Pkg string `json:"package"`
|
| + // Pin is not nil if pin related operation succeeded. It contains instanceID.
|
| + Pin *common.Pin `json:"pin,omitempty"`
|
| + // Tracking is what ref is being tracked by that package in the site root.
|
| + Tracking string `json:"tracking,omitempty"`
|
| + // Err is not empty if pin related operation failed. Pin is nil in that case.
|
| + Err string `json:"error,omitempty"`
|
| +}
|
| +
|
| // Subcommand is a base of all CIPD subcommands. It defines some common flags,
|
| // such as logging and JSON output parameters.
|
| type Subcommand struct {
|
| @@ -66,14 +80,20 @@ func (c *Subcommand) init(args []string, minPosCount, maxPosCount int) bool {
|
| c.printError(makeCLIError("unexpected arguments %v", args))
|
| return false
|
| }
|
| - if len(args) < minPosCount || len(args) > maxPosCount {
|
| + if len(args) < minPosCount || (maxPosCount >= 0 && len(args) > maxPosCount) {
|
| var err error
|
| if minPosCount == maxPosCount {
|
| err = makeCLIError("expecting %d positional argument, got %d instead", minPosCount, len(args))
|
| } else {
|
| - err = makeCLIError(
|
| - "expecting from %d to %d positional arguments, got %d instead",
|
| - minPosCount, maxPosCount, len(args))
|
| + if maxPosCount >= 0 {
|
| + err = makeCLIError(
|
| + "expecting from %d to %d positional arguments, got %d instead",
|
| + minPosCount, maxPosCount, len(args))
|
| + } else {
|
| + err = makeCLIError(
|
| + "expecting at least %d positional arguments, got %d instead",
|
| + minPosCount, len(args))
|
| + }
|
| }
|
| c.printError(err)
|
| return false
|
| @@ -114,7 +134,7 @@ func (c *Subcommand) printError(err error) {
|
| fmt.Fprintf(os.Stderr, "Bad command line: %s.\n\n", err)
|
| c.Flags.Usage()
|
| } else {
|
| - fmt.Fprintln(os.Stderr, err)
|
| + fmt.Fprintf(os.Stderr, "Error: %s.\n", err)
|
| }
|
| }
|
|
|
| @@ -170,6 +190,21 @@ func (c *Subcommand) done(result interface{}, err error) int {
|
| return 0
|
| }
|
|
|
| +// doneWithPins is a handy shortcut that prints list of pinInfo and deduces
|
| +// process exit code based on presence of errors there.
|
| +func (c *Subcommand) doneWithPins(pins []pinInfo, err error) int {
|
| + if len(pins) == 0 {
|
| + fmt.Println("No packages.")
|
| + } else {
|
| + printPinsAndError(pins)
|
| + }
|
| + ret := c.done(pins, err)
|
| + if hasErrors(pins) && ret == 0 {
|
| + return 1
|
| + }
|
| + return ret
|
| +}
|
| +
|
| // commandLineError is used to tag errors related to CLI.
|
| type commandLineError struct {
|
| error
|
| @@ -421,13 +456,6 @@ type batchOperation struct {
|
| callback func(pkg string) (common.Pin, error)
|
| }
|
|
|
| -// pinOrError is passed through channels and also dumped to JSON results file.
|
| -type pinOrError struct {
|
| - Pkg string `json:"package"`
|
| - Pin *common.Pin `json:"pin,omitempty"`
|
| - Err string `json:"error,omitempty"`
|
| -}
|
| -
|
| // expandPkgDir takes a package name or '<prefix>/' and returns a list
|
| // of matching packages (asking backend if necessary). Doesn't recurse, returns
|
| // only direct children.
|
| @@ -454,7 +482,7 @@ func expandPkgDir(c cipd.Client, packagePrefix string) ([]string, error) {
|
|
|
| // performBatchOperation expands a package prefix into a list of packages and
|
| // calls callback for each of them (concurrently) gathering the results.
|
| -func performBatchOperation(op batchOperation) ([]pinOrError, error) {
|
| +func performBatchOperation(op batchOperation) ([]pinInfo, error) {
|
| pkgs := op.packages
|
| if len(pkgs) == 0 {
|
| var err error
|
| @@ -463,52 +491,57 @@ func performBatchOperation(op batchOperation) ([]pinOrError, error) {
|
| return nil, err
|
| }
|
| }
|
| - return callConcurrently(pkgs, func(pkg string) pinOrError {
|
| + return callConcurrently(pkgs, func(pkg string) pinInfo {
|
| pin, err := op.callback(pkg)
|
| if err != nil {
|
| - return pinOrError{pkg, nil, err.Error()}
|
| + return pinInfo{pkg, nil, "", err.Error()}
|
| }
|
| - return pinOrError{pkg, &pin, ""}
|
| + return pinInfo{pkg, &pin, "", ""}
|
| }), nil
|
| }
|
|
|
| -func callConcurrently(pkgs []string, callback func(pkg string) pinOrError) []pinOrError {
|
| +func callConcurrently(pkgs []string, callback func(pkg string) pinInfo) []pinInfo {
|
| // Push index through channel to make results ordered as 'pkgs'.
|
| ch := make(chan struct {
|
| int
|
| - pinOrError
|
| + pinInfo
|
| })
|
| for idx, pkg := range pkgs {
|
| go func(idx int, pkg string) {
|
| ch <- struct {
|
| int
|
| - pinOrError
|
| + pinInfo
|
| }{idx, callback(pkg)}
|
| }(idx, pkg)
|
| }
|
| - pins := make([]pinOrError, len(pkgs))
|
| + pins := make([]pinInfo, len(pkgs))
|
| for i := 0; i < len(pkgs); i++ {
|
| res := <-ch
|
| - pins[res.int] = res.pinOrError
|
| + pins[res.int] = res.pinInfo
|
| }
|
| return pins
|
| }
|
|
|
| -func printPinsAndError(pins []pinOrError) {
|
| +func printPinsAndError(pins []pinInfo) {
|
| hasPins := false
|
| hasErrors := false
|
| for _, p := range pins {
|
| - if p.Err == "" {
|
| - hasPins = true
|
| - } else {
|
| + if p.Err != "" {
|
| hasErrors = true
|
| + } else if p.Pin != nil {
|
| + hasPins = true
|
| }
|
| }
|
| if hasPins {
|
| - fmt.Println("Instances:")
|
| + fmt.Println("Packages:")
|
| for _, p := range pins {
|
| - if p.Err == "" {
|
| + if p.Err != "" || p.Pin == nil {
|
| + continue
|
| + }
|
| + if p.Tracking == "" {
|
| fmt.Printf(" %s\n", p.Pin)
|
| + } else {
|
| + fmt.Printf(" %s (tracking %q)\n", p.Pin, p.Tracking)
|
| }
|
| }
|
| }
|
| @@ -516,13 +549,13 @@ func printPinsAndError(pins []pinOrError) {
|
| fmt.Fprintln(os.Stderr, "Errors:")
|
| for _, p := range pins {
|
| if p.Err != "" {
|
| - fmt.Fprintf(os.Stderr, " %s\n", p.Err)
|
| + fmt.Fprintf(os.Stderr, " %s: %s.\n", p.Pkg, p.Err)
|
| }
|
| }
|
| }
|
| }
|
|
|
| -func hasErrors(pins []pinOrError) bool {
|
| +func hasErrors(pins []pinInfo) bool {
|
| for _, p := range pins {
|
| if p.Err != "" {
|
| return true
|
| @@ -586,12 +619,15 @@ func buildAndUploadInstance(inputOpts InputOptions, refsOpts RefsOptions, tagsOp
|
| var cmdEnsure = &subcommands.Command{
|
| UsageLine: "ensure [options]",
|
| ShortDesc: "installs, removes and updates packages in one go",
|
| - LongDesc: "Installs, removes and updates packages in one go.",
|
| + LongDesc: "Installs, removes and updates packages in one go.\n\n" +
|
| + "Supposed to be used from scripts and automation. Alternative to 'init', " +
|
| + "'install' and 'remove'. As such, it doesn't try to discover site root " +
|
| + "directory on its own.",
|
| CommandRun: func() subcommands.CommandRun {
|
| c := &ensureRun{}
|
| c.registerBaseFlags()
|
| c.ServiceOptions.registerFlags(&c.Flags)
|
| - c.Flags.StringVar(&c.rootDir, "root", "<path>", "Path to a installation site root directory.")
|
| + c.Flags.StringVar(&c.rootDir, "root", "<path>", "Path to an installation site root directory.")
|
| c.Flags.StringVar(&c.listFile, "list", "<path>", "A file with a list of '<package name> <version>' pairs.")
|
| return c
|
| },
|
| @@ -659,16 +695,10 @@ func (c *resolveRun) Run(a subcommands.Application, args []string) int {
|
| if !c.init(args, 1, 1) {
|
| return 1
|
| }
|
| - pins, err := resolveVersion(args[0], c.version, c.ServiceOptions)
|
| - printPinsAndError(pins)
|
| - ret := c.done(pins, err)
|
| - if hasErrors(pins) && ret == 0 {
|
| - return 1
|
| - }
|
| - return ret
|
| + return c.doneWithPins(resolveVersion(args[0], c.version, c.ServiceOptions))
|
| }
|
|
|
| -func resolveVersion(packagePrefix, version string, serviceOpts ServiceOptions) ([]pinOrError, error) {
|
| +func resolveVersion(packagePrefix, version string, serviceOpts ServiceOptions) ([]pinInfo, error) {
|
| client, err := serviceOpts.makeCipdClient("")
|
| if err != nil {
|
| return nil, err
|
| @@ -714,16 +744,10 @@ func (c *setRefRun) Run(a subcommands.Application, args []string) int {
|
| if len(c.refs) == 0 {
|
| return c.done(nil, makeCLIError("at least one -ref must be provided"))
|
| }
|
| - pins, err := setRef(args[0], c.version, c.RefsOptions, c.ServiceOptions)
|
| - printPinsAndError(pins)
|
| - ret := c.done(pins, err)
|
| - if hasErrors(pins) && ret == 0 {
|
| - return 1
|
| - }
|
| - return ret
|
| + return c.doneWithPins(setRef(args[0], c.version, c.RefsOptions, c.ServiceOptions))
|
| }
|
|
|
| -func setRef(packagePrefix, version string, refsOpts RefsOptions, serviceOpts ServiceOptions) ([]pinOrError, error) {
|
| +func setRef(packagePrefix, version string, refsOpts RefsOptions, serviceOpts ServiceOptions) ([]pinInfo, error) {
|
| client, err := serviceOpts.makeCipdClient("")
|
| if err != nil {
|
| return nil, err
|
| @@ -775,8 +799,9 @@ func setRef(packagePrefix, version string, refsOpts RefsOptions, serviceOpts Ser
|
|
|
| var cmdListPackages = &subcommands.Command{
|
| UsageLine: "ls [-r] [<prefix string>]",
|
| - ShortDesc: "lists matching packages",
|
| - LongDesc: "List packages in the given path to which the user has access, optionally recursively.",
|
| + ShortDesc: "lists matching packages on the server",
|
| + LongDesc: "Queries the backend for a list of packages in the given path to " +
|
| + "which the user has access, optionally recursively.",
|
| CommandRun: func() subcommands.CommandRun {
|
| c := &listPackagesRun{}
|
| c.registerBaseFlags()
|
| @@ -1047,7 +1072,7 @@ var cmdDeploy = &subcommands.Command{
|
| CommandRun: func() subcommands.CommandRun {
|
| c := &deployRun{}
|
| c.registerBaseFlags()
|
| - c.Flags.StringVar(&c.rootDir, "root", "<path>", "Path to a installation site root directory.")
|
| + c.Flags.StringVar(&c.rootDir, "root", "<path>", "Path to an installation site root directory.")
|
| return c
|
| },
|
| }
|
| @@ -1315,6 +1340,12 @@ var application = &subcommands.DefaultApplication{
|
| subcommands.CmdHelp,
|
| cipd_lib.SubcommandVersion,
|
|
|
| + // User friendly subcommands that operates within a site root. Implemented
|
| + // in friendly.go.
|
| + cmdInit,
|
| + cmdInstall,
|
| + cmdInstalled,
|
| +
|
| // Authentication related commands.
|
| authcli.SubcommandInfo(auth.Options{Logger: log}, "auth-info"),
|
| authcli.SubcommandLogin(auth.Options{Logger: log}, "auth-login"),
|
|
|