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 }