github.com/vanadium-archive/go.jiri@v0.0.0-20160715023856-abfb8b131290/profiles/profilescmdline/manager_cmdline.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package profilescmdline provides a command line driver
     6  // (for v.io/x/lib/cmdline) for implementing jiri 'profile' subcommands.
     7  // The intent is to support project specific instances of such profiles
     8  // for managing software dependencies.
     9  //
    10  // There are two ways of using the cmdline support, one is to read profile
    11  // information, via RegisterReaderCommands and
    12  // RegisterReaderCommandsUsingParent; the other is to manage profile
    13  // installations via the RegisterManagementCommands function. The management
    14  // commands can manage profiles that are linked into the binary itself
    15  // or invoke external commands that implement the profile management. These
    16  // external 'installer' commands are accessed by specifing them as a prefix
    17  // to the profile name. For example myproject::go will invoke the external
    18  // command jiri-profile-myproject with "go" as the profile name. Thus the
    19  // following invocations are equivalent:
    20  // jiri profile install myproject::go
    21  // jiri profile-myproject install go
    22  //
    23  // Regardless of which is used, the profile name, as seen by profile
    24  // database readers will be myproject::go.
    25  package profilescmdline
    26  
    27  import (
    28  	"bytes"
    29  	"flag"
    30  	"fmt"
    31  	"os"
    32  	"strings"
    33  
    34  	"v.io/jiri"
    35  	"v.io/jiri/profiles"
    36  	"v.io/jiri/profiles/profilesmanager"
    37  	"v.io/x/lib/cmdline"
    38  	"v.io/x/lib/lookpath"
    39  )
    40  
    41  // newCmdOSPackages represents the "profile os-packages" command.
    42  func newCmdOSPackages() *cmdline.Command {
    43  	return &cmdline.Command{
    44  		Runner:   jiri.RunnerFunc(runPackages),
    45  		Name:     "os-packages",
    46  		Short:    "List the commands to install the OS packages required by the given profiles",
    47  		Long:     "List or optionally run the commands to install the OS packages required by the given profiles.",
    48  		ArgsName: "<profiles>",
    49  		ArgsLong: "<profiles> is a list of profiles to list OS packages for.",
    50  	}
    51  }
    52  
    53  // newCmdInstall represents the "profile install" command.
    54  func newCmdInstall() *cmdline.Command {
    55  	return &cmdline.Command{
    56  		Runner:   jiri.RunnerFunc(runInstall),
    57  		Name:     "install",
    58  		Short:    "Install the given profiles",
    59  		Long:     "Install the given profiles.",
    60  		ArgsName: "<profiles>",
    61  		ArgsLong: "<profiles> is a list of profiles to install.",
    62  	}
    63  }
    64  
    65  // newCmdUninstall represents the "profile uninstall" command.
    66  func newCmdUninstall() *cmdline.Command {
    67  	return &cmdline.Command{
    68  		Runner:   jiri.RunnerFunc(runUninstall),
    69  		Name:     "uninstall",
    70  		Short:    "Uninstall the given profiles",
    71  		Long:     "Uninstall the given profiles.",
    72  		ArgsName: "<profiles>",
    73  		ArgsLong: "<profiles> is a list of profiles to uninstall.",
    74  	}
    75  }
    76  
    77  // newCmdUpdate represents the "profile update" command.
    78  func newCmdUpdate() *cmdline.Command {
    79  	return &cmdline.Command{
    80  		Runner:   jiri.RunnerFunc(runUpdate),
    81  		Name:     "update",
    82  		Short:    "Install the latest default version of the given profiles",
    83  		Long:     "Install the latest default version of the given profiles.",
    84  		ArgsName: "<profiles>",
    85  		ArgsLong: "<profiles> is a list of profiles to update, if omitted all profiles are updated.",
    86  	}
    87  }
    88  
    89  // newCmdCleanup represents the "profile cleanup" command.
    90  func newCmdCleanup() *cmdline.Command {
    91  	return &cmdline.Command{
    92  		Runner:   jiri.RunnerFunc(runCleanup),
    93  		Name:     "cleanup",
    94  		Short:    "Cleanup the locally installed profiles",
    95  		Long:     "Cleanup the locally installed profiles. This is generally required when recovering from earlier bugs or when preparing for a subsequent change to the profiles implementation.",
    96  		ArgsName: "<profiles>",
    97  		ArgsLong: "<profiles> is a list of profiles to cleanup, if omitted all profiles are cleaned.",
    98  	}
    99  }
   100  
   101  // newCmdAvailable represents the "profile available" command.
   102  func newCmdAvailable() *cmdline.Command {
   103  	return &cmdline.Command{
   104  		Runner: jiri.RunnerFunc(runAvailable),
   105  		Name:   "available",
   106  		Short:  "List the available profiles",
   107  		Long:   "List the available profiles.",
   108  	}
   109  }
   110  
   111  func runUpdate(jirix *jiri.X, args []string) error {
   112  	return updateImpl(jirix, &updateFlags, args)
   113  }
   114  
   115  func runCleanup(jirix *jiri.X, args []string) error {
   116  	return cleanupImpl(jirix, &cleanupFlags, args)
   117  }
   118  
   119  func runPackages(jirix *jiri.X, args []string) error {
   120  	return packagesImpl(jirix, &packagesFlags, args)
   121  }
   122  
   123  func runInstall(jirix *jiri.X, args []string) error {
   124  	return installImpl(jirix, &installFlags, args)
   125  }
   126  
   127  func runUninstall(jirix *jiri.X, args []string) error {
   128  	return uninstallImpl(jirix, &uninstallFlags, args)
   129  }
   130  
   131  func runAvailable(jirix *jiri.X, args []string) error {
   132  	return availableImpl(jirix, &availableFlags, args)
   133  }
   134  
   135  type commonFlagValues struct {
   136  	// The value of --profiles-db
   137  	dbPath string
   138  	// The value of --profiles-dir
   139  	root string
   140  }
   141  
   142  func initCommon(flags *flag.FlagSet, c *commonFlagValues, installer, defaultDBPath, defaultProfilesPath string) {
   143  	RegisterDBPathFlag(flags, &c.dbPath, defaultDBPath)
   144  	flags.StringVar(&c.root, "profiles-dir", defaultProfilesPath, "the directory, relative to JIRI_ROOT, that profiles are installed in")
   145  }
   146  
   147  func (cv *commonFlagValues) args() []string {
   148  	a := append([]string{}, "--profiles-db="+cv.dbPath)
   149  	a = append(a, "--profiles-dir="+cv.root)
   150  	return a
   151  }
   152  
   153  type packagesFlagValues struct {
   154  	commonFlagValues
   155  	// The value of --target and --env
   156  	target profiles.Target
   157  	// Show commands for all required packages, rather than just the missing ones
   158  	allPackages bool
   159  	// Install the required packages
   160  	installPackages bool
   161  }
   162  
   163  func initPackagesCommand(flags *flag.FlagSet, installer, defaultDBPath, defaultProfilesPath string) {
   164  	initCommon(flags, &packagesFlags.commonFlagValues, installer, defaultDBPath, defaultProfilesPath)
   165  	profiles.RegisterTargetAndEnvFlags(flags, &packagesFlags.target)
   166  	flags.BoolVar(&packagesFlags.allPackages, "all", false, "print commands to install all required OS packages, not just those that are missing")
   167  	flags.BoolVar(&packagesFlags.installPackages, "install", false, "install the requested packages. This may need to be run as root.")
   168  	for _, name := range profilesmanager.Managers() {
   169  		profilesmanager.LookupManager(name).AddFlags(flags, profiles.Install)
   170  	}
   171  }
   172  
   173  func (pv *packagesFlagValues) args() []string {
   174  	a := pv.commonFlagValues.args()
   175  	if t := pv.target.String(); t != "" {
   176  		a = append(a, "--target="+t)
   177  	}
   178  	if e := pv.target.CommandLineEnv().String(); e != "" {
   179  		a = append(a, "--target="+e)
   180  	}
   181  	a = append(a, fmt.Sprintf("--%s=%v", "all", pv.allPackages))
   182  	return append(a, fmt.Sprintf("--%s=%v", "install", pv.installPackages))
   183  }
   184  
   185  type installFlagValues struct {
   186  	commonFlagValues
   187  	// The value of --target and --env
   188  	target profiles.Target
   189  	// The value of --force
   190  	force bool
   191  }
   192  
   193  func initInstallCommand(flags *flag.FlagSet, installer, defaultDBPath, defaultProfilesPath string) {
   194  	initCommon(flags, &installFlags.commonFlagValues, installer, defaultDBPath, defaultProfilesPath)
   195  	profiles.RegisterTargetAndEnvFlags(flags, &installFlags.target)
   196  	flags.BoolVar(&installFlags.force, "force", false, "force install the profile even if it is already installed")
   197  	for _, name := range profilesmanager.Managers() {
   198  		profilesmanager.LookupManager(name).AddFlags(flags, profiles.Install)
   199  	}
   200  }
   201  
   202  func (iv *installFlagValues) args() []string {
   203  	a := iv.commonFlagValues.args()
   204  	if t := iv.target.String(); t != "" {
   205  		a = append(a, "--target="+t)
   206  	}
   207  	if e := iv.target.CommandLineEnv().String(); e != "" {
   208  		a = append(a, "--target="+e)
   209  	}
   210  	return append(a, fmt.Sprintf("--%s=%v", "force", iv.force))
   211  }
   212  
   213  type uninstallFlagValues struct {
   214  	commonFlagValues
   215  	// The value of --target
   216  	target profiles.Target
   217  	// The value of --all-targets
   218  	allTargets bool
   219  	// The value of --v
   220  	verbose bool
   221  	// TODO(cnicolaou): add a flag to remove the profile only from the DB.
   222  }
   223  
   224  func initUninstallCommand(flags *flag.FlagSet, installer, defaultDBPath, defaultProfilesPath string) {
   225  	initCommon(flags, &uninstallFlags.commonFlagValues, installer, defaultDBPath, defaultProfilesPath)
   226  	profiles.RegisterTargetFlag(flags, &uninstallFlags.target)
   227  	flags.BoolVar(&uninstallFlags.allTargets, "all-targets", false, "apply to all targets for the specified profile(s)")
   228  	flags.BoolVar(&uninstallFlags.verbose, "v", false, "print more detailed information")
   229  	for _, name := range profilesmanager.Managers() {
   230  		profilesmanager.LookupManager(name).AddFlags(flags, profiles.Uninstall)
   231  	}
   232  }
   233  
   234  func (uv *uninstallFlagValues) args() []string {
   235  	a := uv.commonFlagValues.args()
   236  	if uv.target.String() != "" {
   237  		a = append(a, "--target="+uv.target.String())
   238  	}
   239  	a = append(a, fmt.Sprintf("--%s=%v", "all-targets", uv.allTargets))
   240  	return append(a, fmt.Sprintf("--%s=%v", "v", uv.verbose))
   241  }
   242  
   243  type cleanupFlagValues struct {
   244  	commonFlagValues
   245  	// The value of --gc
   246  	gc bool
   247  	// The value of --rewrite-profiles-db
   248  	rewriteDB bool
   249  	// The value of --rm-all
   250  	rmAll bool
   251  	// The value of --v
   252  	verbose bool
   253  }
   254  
   255  func initCleanupCommand(flags *flag.FlagSet, installer, defaultDBPath, defaultProfilesPath string) {
   256  	initCommon(flags, &cleanupFlags.commonFlagValues, installer, defaultDBPath, defaultProfilesPath)
   257  	flags.BoolVar(&cleanupFlags.gc, "gc", false, "uninstall profile targets that are older than the current default")
   258  	flags.BoolVar(&cleanupFlags.rmAll, "rm-all", false, "remove profiles database and all profile generated output files.")
   259  	flags.BoolVar(&cleanupFlags.rewriteDB, "rewrite-profiles-db", false, "rewrite the profiles database to use the latest schema version")
   260  	flags.BoolVar(&cleanupFlags.verbose, "v", false, "print more detailed information")
   261  }
   262  
   263  func (cv *cleanupFlagValues) args() []string {
   264  	return append(cv.commonFlagValues.args(),
   265  		fmt.Sprintf("--%s=%v", "gc", cv.gc),
   266  		fmt.Sprintf("--%s=%v", "rewrite-profiles-db", cv.rewriteDB),
   267  		fmt.Sprintf("--%s=%v", "v", cv.verbose),
   268  		fmt.Sprintf("--%s=%v", "rm-all", cv.rmAll))
   269  }
   270  
   271  type updateFlagValues struct {
   272  	commonFlagValues
   273  	// The value of --v
   274  	verbose bool
   275  }
   276  
   277  func initUpdateCommand(flags *flag.FlagSet, installer, defaultDBPath, defaultProfilesPath string) {
   278  	initCommon(flags, &updateFlags.commonFlagValues, installer, defaultDBPath, defaultProfilesPath)
   279  	flags.BoolVar(&updateFlags.verbose, "v", false, "print more detailed information")
   280  }
   281  
   282  func (uv *updateFlagValues) args() []string {
   283  	return append(uv.commonFlagValues.args(), fmt.Sprintf("--%s=%v", "v", uv.verbose))
   284  }
   285  
   286  type availableFlagValues struct {
   287  	// The value of --v
   288  	verbose bool
   289  	// The value of --describe
   290  	describe bool
   291  }
   292  
   293  func initAvailableCommand(flags *flag.FlagSet, installer, defaultDBPath, defaultProfilesPath string) {
   294  	flags.BoolVar(&availableFlags.verbose, "v", false, "print more detailed information")
   295  	flags.BoolVar(&availableFlags.describe, "describe", false, "print the profile description")
   296  }
   297  
   298  func (av *availableFlagValues) args() []string {
   299  	return []string{
   300  		fmt.Sprintf("--%s=%v", "v", av.verbose),
   301  		fmt.Sprintf("--%s=%v", "describe", av.describe),
   302  	}
   303  }
   304  
   305  var (
   306  	packagesFlags    packagesFlagValues
   307  	installFlags     installFlagValues
   308  	uninstallFlags   uninstallFlagValues
   309  	cleanupFlags     cleanupFlagValues
   310  	updateFlags      updateFlagValues
   311  	availableFlags   availableFlagValues
   312  	profileInstaller string
   313  	runSubcommands   bool
   314  )
   315  
   316  // RegisterManagementCommands registers the management subcommands:
   317  // uninstall, install, update and cleanup.
   318  func RegisterManagementCommands(parent *cmdline.Command, useSubcommands bool, installer, defaultDBPath, defaultProfilesPath string) {
   319  	cmdOSPackages := newCmdOSPackages()
   320  	cmdInstall := newCmdInstall()
   321  	cmdUninstall := newCmdUninstall()
   322  	cmdUpdate := newCmdUpdate()
   323  	cmdCleanup := newCmdCleanup()
   324  	cmdAvailable := newCmdAvailable()
   325  	initPackagesCommand(&cmdOSPackages.Flags, installer, defaultDBPath, defaultProfilesPath)
   326  	initInstallCommand(&cmdInstall.Flags, installer, defaultDBPath, defaultProfilesPath)
   327  	initUninstallCommand(&cmdUninstall.Flags, installer, defaultDBPath, defaultProfilesPath)
   328  	initUpdateCommand(&cmdUpdate.Flags, installer, defaultDBPath, defaultProfilesPath)
   329  	initCleanupCommand(&cmdCleanup.Flags, installer, defaultDBPath, defaultProfilesPath)
   330  	initAvailableCommand(&cmdAvailable.Flags, installer, defaultDBPath, defaultProfilesPath)
   331  	parent.Children = append(parent.Children, cmdInstall, cmdOSPackages, cmdUninstall, cmdUpdate, cmdCleanup, cmdAvailable)
   332  	profileInstaller = installer
   333  	runSubcommands = useSubcommands
   334  }
   335  
   336  func findProfileSubcommands(jirix *jiri.X) []string {
   337  	if !runSubcommands {
   338  		return nil
   339  	}
   340  	cmds, _ := lookpath.LookPrefix(jirix.Env(), "jiri-profile-", nil)
   341  	return cmds
   342  }
   343  
   344  func allAvailableManagers(jirix *jiri.X) ([]string, error) {
   345  	names := profilesmanager.Managers()
   346  	if profileInstaller != "" {
   347  		return names, nil
   348  	}
   349  	subcommands := findProfileSubcommands(jirix)
   350  	s := jirix.NewSeq()
   351  	for _, sc := range subcommands {
   352  		var out bytes.Buffer
   353  		args := []string{"available"}
   354  		if err := s.Capture(&out, nil).Last(sc, args...); err != nil {
   355  			fmt.Fprintf(jirix.Stderr(), "failed to run %s %s: %v", sc, strings.Join(args, " "), err)
   356  			return nil, err
   357  		}
   358  		mgrs := out.String()
   359  		for _, m := range strings.Split(mgrs, ",") {
   360  			names = append(names, strings.TrimSpace(m))
   361  		}
   362  	}
   363  	return names, nil
   364  }
   365  
   366  // availableProfileManagers creates a profileManager for all available
   367  // profiles, whether in this process or in a sub command.
   368  func availableProfileManagers(jirix *jiri.X, dbpath string, args []string) ([]profileManager, *profiles.DB, error) {
   369  	db := profiles.NewDB()
   370  	if err := db.Read(jirix, dbpath); err != nil {
   371  		fmt.Fprintf(jirix.Stderr(), "Failed to read profiles database %q: %v\n", dbpath, err)
   372  		return nil, nil, err
   373  	}
   374  	mgrs := []profileManager{}
   375  	names := args
   376  	if len(names) == 0 {
   377  		var err error
   378  		names, err = allAvailableManagers(jirix)
   379  		if err != nil {
   380  			return nil, nil, err
   381  		}
   382  	}
   383  	for _, name := range names {
   384  		mgrs = append(mgrs, newProfileManager(name, db))
   385  	}
   386  	return mgrs, db, nil
   387  }
   388  
   389  // installedProfileManagers creates a profileManager for all installed
   390  // profiles, whether in this process or in a sub command.
   391  func installedProfileManagers(jirix *jiri.X, dbpath string, args []string) ([]profileManager, *profiles.DB, error) {
   392  	db := profiles.NewDB()
   393  	if err := db.Read(jirix, dbpath); err != nil {
   394  		fmt.Fprintf(jirix.Stderr(), "Failed to read profiles database %q: %v\n", dbpath, err)
   395  		return nil, nil, err
   396  	}
   397  	mgrs := []profileManager{}
   398  	names := args
   399  	if len(names) == 0 {
   400  		names = db.Names()
   401  	}
   402  	for _, name := range names {
   403  		mgrs = append(mgrs, newProfileManager(name, db))
   404  	}
   405  	return mgrs, db, nil
   406  }
   407  
   408  func targetAtDefaultVersion(mgr profiles.Manager, target profiles.Target) (profiles.Target, error) {
   409  	def := target
   410  	version, err := mgr.VersionInfo().Select(target.Version())
   411  	if err != nil {
   412  		return profiles.Target{}, err
   413  	}
   414  	def.SetVersion(version)
   415  	return def, nil
   416  }
   417  
   418  func writeDB(jirix *jiri.X, db *profiles.DB, installer, path string) error {
   419  	// Do nothing if the installer is not supplied. This will generally
   420  	// happen when/if writeDB is called from the top-level profile driver
   421  	// command rather than from a subcommand.
   422  	if installer == "" {
   423  		return nil
   424  	}
   425  	fi, err := os.Stat(path)
   426  	if err != nil {
   427  		if !os.IsNotExist(err) {
   428  			return err
   429  		}
   430  		// New setup, but the directory doesn't exist yet.
   431  		if err := os.MkdirAll(path, os.FileMode(0755)); err != nil {
   432  			return err
   433  		}
   434  	} else {
   435  		if !fi.IsDir() {
   436  			return fmt.Errorf("%s exists but is not a directory", path)
   437  		}
   438  	}
   439  	// New setup with installers writing their own file in a directory
   440  	return db.Write(jirix, installer, path)
   441  }
   442  
   443  func updateImpl(jirix *jiri.X, cl *updateFlagValues, args []string) error {
   444  	mgrs, db, err := availableProfileManagers(jirix, cl.dbPath, args)
   445  	if err != nil {
   446  		return err
   447  	}
   448  	root := jiri.NewRelPath(cl.root).Join(profileInstaller)
   449  	for _, mgr := range mgrs {
   450  		if err := mgr.update(jirix, cl, root); err != nil {
   451  			return err
   452  		}
   453  	}
   454  	return writeDB(jirix, db, profileInstaller, cl.dbPath)
   455  }
   456  
   457  func cleanupImpl(jirix *jiri.X, cl *cleanupFlagValues, args []string) error {
   458  	count := 0
   459  	if cl.gc {
   460  		count++
   461  	}
   462  	if cl.rewriteDB {
   463  		count++
   464  	}
   465  	if cl.rmAll {
   466  		count++
   467  	}
   468  	if count != 1 {
   469  		fmt.Errorf("exactly one option must be specified")
   470  	}
   471  	mgrs, db, err := installedProfileManagers(jirix, cl.dbPath, args)
   472  	if err != nil {
   473  		return err
   474  	}
   475  	root := jiri.NewRelPath(cl.root).Join(profileInstaller)
   476  	for _, mgr := range mgrs {
   477  		if err := mgr.cleanup(jirix, cl, root); err != nil {
   478  			return err
   479  		}
   480  	}
   481  	if !cl.rmAll {
   482  		return writeDB(jirix, db, profileInstaller, cl.dbPath)
   483  	}
   484  	return nil
   485  }
   486  
   487  func packagesImpl(jirix *jiri.X, cl *packagesFlagValues, args []string) error {
   488  	mgrs, _, err := availableProfileManagers(jirix, cl.dbPath, args)
   489  	if err != nil {
   490  		return err
   491  	}
   492  	cl.target.UseCommandLineEnv()
   493  	root := jiri.NewRelPath(cl.root).Join(profileInstaller)
   494  	s := jirix.NewSeq()
   495  	installPackages := cl.installPackages
   496  	// Never ask a subcommand to install packages.
   497  	cl.installPackages = false
   498  	for _, mgr := range mgrs {
   499  		cmds, err := mgr.packageCmds(jirix, cl, root)
   500  		if err != nil {
   501  			return err
   502  		}
   503  		for _, cmd := range cmds {
   504  			if installPackages {
   505  				if err := s.Verbose(true).Last(cmd[0], cmd[1:]...); err != nil {
   506  					return err
   507  				}
   508  			} else {
   509  				fmt.Fprintf(jirix.Stdout(), "%s\n", strings.TrimSpace(strings.Join(cmd, " ")))
   510  			}
   511  		}
   512  	}
   513  	return nil
   514  }
   515  
   516  func installImpl(jirix *jiri.X, cl *installFlagValues, args []string) error {
   517  	mgrs, db, err := availableProfileManagers(jirix, cl.dbPath, args)
   518  	if err != nil {
   519  		return err
   520  	}
   521  	cl.target.UseCommandLineEnv()
   522  	newMgrs := []profileManager{}
   523  	for _, mgr := range mgrs {
   524  		name := mgr.mgrName()
   525  		if !cl.force {
   526  			installer, profile := profiles.SplitProfileName(name)
   527  			if p := db.LookupProfileTarget(installer, profile, cl.target); p != nil {
   528  				fmt.Fprintf(jirix.Stdout(), "%v %v is already installed as %v\n", name, cl.target, p)
   529  				continue
   530  			}
   531  		}
   532  		newMgrs = append(newMgrs, mgr)
   533  	}
   534  	root := jiri.NewRelPath(cl.root).Join(profileInstaller)
   535  	for _, mgr := range newMgrs {
   536  		if err := mgr.install(jirix, cl, root); err != nil {
   537  			return err
   538  		}
   539  	}
   540  	return writeDB(jirix, db, profileInstaller, cl.dbPath)
   541  }
   542  
   543  func uninstallImpl(jirix *jiri.X, cl *uninstallFlagValues, args []string) error {
   544  	mgrs, db, err := availableProfileManagers(jirix, cl.dbPath, args)
   545  	if err != nil {
   546  		return err
   547  	}
   548  	if cl.allTargets && cl.target.IsSet() {
   549  		fmt.Fprintf(jirix.Stdout(), "ignore target (%v) when used in conjunction with --all-targets\n", cl.target)
   550  	}
   551  	root := jiri.NewRelPath(cl.root).Join(profileInstaller)
   552  	for _, mgr := range mgrs {
   553  		if err := mgr.uninstall(jirix, cl, root); err != nil {
   554  			return err
   555  		}
   556  	}
   557  	return writeDB(jirix, db, profileInstaller, cl.dbPath)
   558  }
   559  
   560  func availableImpl(jirix *jiri.X, cl *availableFlagValues, _ []string) error {
   561  	if profileInstaller == "" {
   562  		subcommands := findProfileSubcommands(jirix)
   563  		if cl.verbose {
   564  			fmt.Fprintf(jirix.Stdout(), "Available Subcommands: %s\n", strings.Join(subcommands, ", "))
   565  		}
   566  		s := jirix.NewSeq()
   567  		args := []string{"available"}
   568  		args = append(args, cl.args()...)
   569  		out := bytes.Buffer{}
   570  		for _, sc := range subcommands {
   571  			if err := s.Capture(&out, nil).Last(sc, args...); err != nil {
   572  				return err
   573  			}
   574  		}
   575  		if s := strings.TrimSpace(out.String()); s != "" {
   576  			fmt.Fprintln(jirix.Stdout(), s)
   577  		}
   578  	}
   579  	mgrs := profilesmanager.Managers()
   580  	if len(mgrs) == 0 {
   581  		return nil
   582  	}
   583  	if cl.verbose {
   584  		scname := ""
   585  		if profileInstaller != "" {
   586  			scname = profileInstaller + ": "
   587  		}
   588  		fmt.Fprintf(jirix.Stdout(), "%sAvailable Profiles:\n", scname)
   589  		for _, name := range mgrs {
   590  			mgr := profilesmanager.LookupManager(name)
   591  			vi := mgr.VersionInfo()
   592  			fmt.Fprintf(jirix.Stdout(), "%s: versions: %s\n", name, vi)
   593  		}
   594  	} else {
   595  		if cl.describe {
   596  			for _, name := range mgrs {
   597  				mgr := profilesmanager.LookupManager(name)
   598  				fmt.Fprintf(jirix.Stdout(), "%s: %s\n", name, strings.Replace(strings.TrimSpace(mgr.Info()), "\n", " ", -1))
   599  			}
   600  		} else {
   601  			fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(mgrs, ", "))
   602  		}
   603  	}
   604  	return nil
   605  }