github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/commands/main.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"strings"
    12  	"text/template"
    13  
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	utilsos "github.com/juju/os"
    18  	"github.com/juju/os/series"
    19  	proxyutils "github.com/juju/proxy"
    20  	"github.com/juju/utils/featureflag"
    21  	"github.com/juju/version"
    22  
    23  	// Import the providers.
    24  	cloudfile "github.com/juju/juju/cloud"
    25  	jujucmd "github.com/juju/juju/cmd"
    26  	"github.com/juju/juju/cmd/juju/action"
    27  	"github.com/juju/juju/cmd/juju/application"
    28  	"github.com/juju/juju/cmd/juju/backups"
    29  	"github.com/juju/juju/cmd/juju/block"
    30  	"github.com/juju/juju/cmd/juju/caas"
    31  	"github.com/juju/juju/cmd/juju/cachedimages"
    32  	"github.com/juju/juju/cmd/juju/charmcmd"
    33  	"github.com/juju/juju/cmd/juju/cloud"
    34  	"github.com/juju/juju/cmd/juju/controller"
    35  	"github.com/juju/juju/cmd/juju/crossmodel"
    36  	"github.com/juju/juju/cmd/juju/firewall"
    37  	"github.com/juju/juju/cmd/juju/gui"
    38  	"github.com/juju/juju/cmd/juju/machine"
    39  	"github.com/juju/juju/cmd/juju/metricsdebug"
    40  	"github.com/juju/juju/cmd/juju/model"
    41  	"github.com/juju/juju/cmd/juju/resource"
    42  	rcmd "github.com/juju/juju/cmd/juju/romulus/commands"
    43  	"github.com/juju/juju/cmd/juju/setmeterstatus"
    44  	"github.com/juju/juju/cmd/juju/space"
    45  	"github.com/juju/juju/cmd/juju/status"
    46  	"github.com/juju/juju/cmd/juju/storage"
    47  	"github.com/juju/juju/cmd/juju/subnet"
    48  	"github.com/juju/juju/cmd/juju/user"
    49  	"github.com/juju/juju/cmd/modelcmd"
    50  	"github.com/juju/juju/feature"
    51  	"github.com/juju/juju/juju"
    52  	"github.com/juju/juju/juju/osenv"
    53  	"github.com/juju/juju/jujuclient"
    54  	_ "github.com/juju/juju/provider/all"
    55  	"github.com/juju/juju/resource/resourceadapters"
    56  	"github.com/juju/juju/utils/proxy"
    57  	jujuversion "github.com/juju/juju/version"
    58  )
    59  
    60  var logger = loggo.GetLogger("juju.cmd.juju.commands")
    61  
    62  func init() {
    63  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
    64  }
    65  
    66  // TODO(ericsnow) Move the following to cmd/juju/main.go:
    67  //  jujuDoc
    68  //  Main
    69  
    70  var jujuDoc = `
    71  juju provides easy, intelligent application orchestration on top of cloud
    72  infrastructure providers such as Amazon EC2, MaaS, OpenStack, Windows, Azure,
    73  or your local machine.
    74  
    75  https://jujucharms.com/
    76  `
    77  
    78  const juju1xCmdName = "juju-1"
    79  
    80  var usageHelp = `
    81  Usage: juju [help] <command>
    82  
    83  Summary:
    84  Juju is model & application management software designed to leverage the power
    85  of existing resource pools, particularly cloud-based ones. It has built-in
    86  support for cloud providers such as Amazon EC2, Google GCE, Microsoft
    87  Azure, OpenStack, and Rackspace. It also works very well with MAAS and
    88  LXD. Juju allows for easy installation and management of workloads on a
    89  chosen resource pool.
    90  
    91  See https://jujucharms.com/docs/stable for documentation.
    92  
    93  Common commands:
    94  
    95      add-cloud           Adds a user-defined cloud to Juju.
    96      add-credential      Adds or replaces credentials for a cloud.
    97      add-model           Adds a hosted model.
    98      add-relation        Adds a relation between two applications.
    99      add-unit            Adds extra units of a deployed application.
   100      add-user            Adds a Juju user to a controller.
   101      bootstrap           Initializes a cloud environment.
   102      controllers         Lists all controllers.
   103      deploy              Deploys a new application.
   104      expose              Makes an application publicly available over the network.
   105      models              Lists models a user can access on a controller.
   106      status              Displays the current status of Juju, applications, and units.
   107      switch              Selects or identifies the current controller and model.
   108  
   109  Example help commands:
   110  
   111      `[1:] + "`juju help`" + `          This help page
   112      ` + "`juju help commands`" + ` Lists all commands
   113      ` + "`juju help deploy`" + `   Shows help for command 'deploy'
   114  `
   115  
   116  var x = []byte("\x96\x8c\x8a\x91\x93\x9a\x9e\x8c\x97\x99\x8a\x9c\x94\x96\x91\x98\xdf\x9e\x92\x9e\x85\x96\x91\x98\xf5")
   117  
   118  // Main registers subcommands for the juju executable, and hands over control
   119  // to the cmd package. This function is not redundant with main, because it
   120  // provides an entry point for testing with arbitrary command line arguments.
   121  // This function returns the exit code, for main to pass to os.Exit.
   122  func Main(args []string) int {
   123  	return main{
   124  		execCommand: exec.Command,
   125  	}.Run(args)
   126  }
   127  
   128  // main is a type that captures dependencies for running the main function.
   129  type main struct {
   130  	// execCommand abstracts away exec.Command.
   131  	execCommand func(command string, args ...string) *exec.Cmd
   132  }
   133  
   134  // Run is the main entry point for the juju client.
   135  func (m main) Run(args []string) int {
   136  	ctx, err := cmd.DefaultContext()
   137  	if err != nil {
   138  		cmd.WriteError(os.Stderr, err)
   139  		return 2
   140  	}
   141  
   142  	// note that this has to come before we init the juju home directory,
   143  	// since it relies on detecting the lack of said directory.
   144  	newInstall := m.maybeWarnJuju1x()
   145  
   146  	if err = juju.InitJujuXDGDataHome(); err != nil {
   147  		cmd.WriteError(ctx.Stderr, err)
   148  		return 2
   149  	}
   150  
   151  	if err := installProxy(); err != nil {
   152  		cmd.WriteError(ctx.Stderr, err)
   153  		return 2
   154  	}
   155  
   156  	if newInstall {
   157  		fmt.Fprintf(ctx.Stderr, "Since Juju %v is being run for the first time, downloading latest cloud information.\n", jujuversion.Current.Major)
   158  		updateCmd := cloud.NewUpdateCloudsCommand()
   159  		if err := updateCmd.Run(ctx); err != nil {
   160  			cmd.WriteError(ctx.Stderr, err)
   161  		}
   162  	}
   163  
   164  	for i := range x {
   165  		x[i] ^= 255
   166  	}
   167  	if len(args) == 2 {
   168  		if args[1] == string(x[0:2]) {
   169  			os.Stdout.Write(x[9:])
   170  			return 0
   171  		}
   172  		if args[1] == string(x[2:9]) {
   173  			os.Stdout.Write(model.ExtractCert())
   174  			return 0
   175  		}
   176  	}
   177  
   178  	jcmd := NewJujuCommand(ctx)
   179  	return cmd.Main(jcmd, ctx, args[1:])
   180  }
   181  
   182  func installProxy() error {
   183  	// Set the default transport to use the in-process proxy
   184  	// configuration.
   185  	if err := proxy.DefaultConfig.Set(proxyutils.DetectProxies()); err != nil {
   186  		return errors.Trace(err)
   187  	}
   188  	if err := proxy.DefaultConfig.InstallInDefaultTransport(); err != nil {
   189  		return errors.Trace(err)
   190  	}
   191  	return nil
   192  }
   193  
   194  func (m main) maybeWarnJuju1x() (newInstall bool) {
   195  	newInstall = !juju2xConfigDataExists()
   196  	if !shouldWarnJuju1x() {
   197  		return newInstall
   198  	}
   199  	ver, exists := m.juju1xVersion()
   200  	if !exists {
   201  		return newInstall
   202  	}
   203  	// TODO (anastasiamac 2016-10-21) Once manual page exists as per
   204  	// https://github.com/juju/docs/issues/1487,
   205  	// link it in the Note below to avoid propose here.
   206  	welcomeMsgTemplate := `
   207  Welcome to Juju {{.CurrentJujuVersion}}. 
   208      See https://jujucharms.com/docs/stable/introducing-2 for more details.
   209  
   210  If you want to use Juju {{.OldJujuVersion}}, run 'juju' commands as '{{.OldJujuCommand}}'. For example, '{{.OldJujuCommand}} bootstrap'.
   211     See https://jujucharms.com/docs/stable/juju-coexist for installation details. 
   212  `[1:]
   213  	t := template.Must(template.New("plugin").Parse(welcomeMsgTemplate))
   214  	var buf bytes.Buffer
   215  	t.Execute(&buf, map[string]interface{}{
   216  		"CurrentJujuVersion": jujuversion.Current,
   217  		"OldJujuVersion":     ver,
   218  		"OldJujuCommand":     juju1xCmdName,
   219  	})
   220  	fmt.Fprintln(os.Stderr, buf.String())
   221  	return newInstall
   222  }
   223  
   224  func (m main) juju1xVersion() (ver string, exists bool) {
   225  	out, err := m.execCommand(juju1xCmdName, "version").Output()
   226  	if err == exec.ErrNotFound {
   227  		return "", false
   228  	}
   229  	ver = "1.x"
   230  	if err == nil {
   231  		v := strings.TrimSpace(string(out))
   232  		// parse so we can drop the series and arch
   233  		bin, err := version.ParseBinary(v)
   234  		if err == nil {
   235  			ver = bin.Number.String()
   236  		}
   237  	}
   238  	return ver, true
   239  }
   240  
   241  func shouldWarnJuju1x() bool {
   242  	// this code only applies to Ubuntu, where we renamed Juju 1.x to juju-1.
   243  	ostype, err := series.GetOSFromSeries(series.MustHostSeries())
   244  	if err != nil || ostype != utilsos.Ubuntu {
   245  		return false
   246  	}
   247  	return osenv.Juju1xEnvConfigExists() && !juju2xConfigDataExists()
   248  }
   249  
   250  func juju2xConfigDataExists() bool {
   251  	_, err := os.Stat(osenv.JujuXDGDataHomeDir())
   252  	return err == nil
   253  }
   254  
   255  // NewJujuCommand ...
   256  func NewJujuCommand(ctx *cmd.Context) cmd.Command {
   257  	jcmd := jujucmd.NewSuperCommand(cmd.SuperCommandParams{
   258  		Name:                "juju",
   259  		Doc:                 jujuDoc,
   260  		MissingCallback:     RunPlugin,
   261  		UserAliasesFilename: osenv.JujuXDGDataHomePath("aliases"),
   262  		FlagKnownAs:         "option",
   263  	})
   264  	jcmd.AddHelpTopic("basics", "Basic Help Summary", usageHelp)
   265  	registerCommands(jcmd, ctx)
   266  	return jcmd
   267  }
   268  
   269  type commandRegistry interface {
   270  	Register(cmd.Command)
   271  	RegisterSuperAlias(name, super, forName string, check cmd.DeprecationCheck)
   272  	RegisterDeprecated(subcmd cmd.Command, check cmd.DeprecationCheck)
   273  }
   274  
   275  // TODO(ericsnow) Factor out the commands and aliases into a static
   276  // registry that can be passed to the supercommand separately.
   277  
   278  // registerCommands registers commands in the specified registry.
   279  func registerCommands(r commandRegistry, ctx *cmd.Context) {
   280  	// Creation commands.
   281  	r.Register(newBootstrapCommand())
   282  	r.Register(application.NewAddRelationCommand())
   283  
   284  	// Cross model relations commands.
   285  	r.Register(crossmodel.NewOfferCommand())
   286  	r.Register(crossmodel.NewRemoveOfferCommand())
   287  	r.Register(crossmodel.NewShowOfferedEndpointCommand())
   288  	r.Register(crossmodel.NewListEndpointsCommand())
   289  	r.Register(crossmodel.NewFindEndpointsCommand())
   290  	r.Register(application.NewConsumeCommand())
   291  	r.Register(application.NewSuspendRelationCommand())
   292  	r.Register(application.NewResumeRelationCommand())
   293  
   294  	// Firewall rule commands.
   295  	r.Register(firewall.NewSetFirewallRuleCommand())
   296  	r.Register(firewall.NewListFirewallRulesCommand())
   297  
   298  	// Destruction commands.
   299  	r.Register(application.NewRemoveRelationCommand())
   300  	r.Register(application.NewRemoveApplicationCommand())
   301  	r.Register(application.NewRemoveUnitCommand())
   302  	r.Register(application.NewRemoveSaasCommand())
   303  
   304  	// Reporting commands.
   305  	r.Register(status.NewStatusCommand())
   306  	r.Register(newSwitchCommand())
   307  	r.Register(status.NewStatusHistoryCommand())
   308  
   309  	// Error resolution and debugging commands.
   310  	r.Register(newDefaultRunCommand(nil))
   311  	r.Register(newSCPCommand(nil))
   312  	r.Register(newSSHCommand(nil, nil))
   313  	r.Register(application.NewResolvedCommand())
   314  	r.Register(newDebugLogCommand(nil))
   315  	r.Register(newDebugHooksCommand(nil))
   316  
   317  	// Configuration commands.
   318  	r.Register(model.NewModelGetConstraintsCommand())
   319  	r.Register(model.NewModelSetConstraintsCommand())
   320  	r.Register(newSyncToolsCommand())
   321  	r.Register(newUpgradeJujuCommand(nil, nil))
   322  	r.Register(application.NewUpgradeCharmCommand())
   323  	r.Register(application.NewSetSeriesCommand())
   324  
   325  	// Charm tool commands.
   326  	r.Register(newHelpToolCommand())
   327  	// TODO (anastasiamac 2017-08-1) This needs to be removed in Juju 3.x
   328  	// lp#1707836
   329  	r.Register(charmcmd.NewSuperCommand())
   330  
   331  	// Manage backups.
   332  	r.Register(backups.NewCreateCommand())
   333  	r.Register(backups.NewDownloadCommand())
   334  	r.Register(backups.NewShowCommand())
   335  	r.Register(backups.NewListCommand())
   336  	r.Register(backups.NewRemoveCommand())
   337  	r.Register(backups.NewRestoreCommand())
   338  	r.Register(backups.NewUploadCommand())
   339  
   340  	// Manage authorized ssh keys.
   341  	r.Register(NewAddKeysCommand())
   342  	r.Register(NewRemoveKeysCommand())
   343  	r.Register(NewImportKeysCommand())
   344  	r.Register(NewListKeysCommand())
   345  
   346  	// Manage users and access
   347  	r.Register(user.NewAddCommand())
   348  	r.Register(user.NewChangePasswordCommand())
   349  	r.Register(user.NewShowUserCommand())
   350  	r.Register(user.NewListCommand())
   351  	r.Register(user.NewEnableCommand())
   352  	r.Register(user.NewDisableCommand())
   353  	r.Register(user.NewLoginCommand())
   354  	r.Register(user.NewLogoutCommand())
   355  	r.Register(user.NewRemoveCommand())
   356  	r.Register(user.NewWhoAmICommand())
   357  
   358  	// Manage cached images
   359  	r.Register(cachedimages.NewRemoveCommand())
   360  	r.Register(cachedimages.NewListCommand())
   361  
   362  	// Manage machines
   363  	r.Register(machine.NewAddCommand())
   364  	r.Register(machine.NewRemoveCommand())
   365  	r.Register(machine.NewListMachinesCommand())
   366  	r.Register(machine.NewShowMachineCommand())
   367  	r.Register(machine.NewUpgradeSeriesCommand())
   368  
   369  	// Manage model
   370  	r.Register(model.NewConfigCommand())
   371  	r.Register(model.NewDefaultsCommand())
   372  	r.Register(model.NewRetryProvisioningCommand())
   373  	r.Register(model.NewDestroyCommand())
   374  	r.Register(model.NewGrantCommand())
   375  	r.Register(model.NewRevokeCommand())
   376  	r.Register(model.NewShowCommand())
   377  	r.Register(model.NewModelCredentialCommand())
   378  	if featureflag.Enabled(feature.Generations) {
   379  		r.Register(model.NewAddGenerationCommand())
   380  		r.Register(model.NewCancelGenerationCommand())
   381  		r.Register(model.NewAdvanceGenerationCommand())
   382  		r.Register(model.NewSwitchGenerationCommand())
   383  	}
   384  
   385  	r.Register(newMigrateCommand())
   386  	r.Register(model.NewExportBundleCommand())
   387  
   388  	if featureflag.Enabled(feature.DeveloperMode) {
   389  		r.Register(model.NewDumpCommand())
   390  		r.Register(model.NewDumpDBCommand())
   391  	}
   392  
   393  	// Manage and control actions
   394  	r.Register(action.NewStatusCommand())
   395  	r.Register(action.NewRunCommand())
   396  	r.Register(action.NewShowOutputCommand())
   397  	r.Register(action.NewListCommand())
   398  	r.Register(action.NewCancelCommand())
   399  
   400  	// Manage controller availability
   401  	r.Register(newEnableHACommand())
   402  
   403  	// Manage and control applications
   404  	r.Register(application.NewAddUnitCommand())
   405  	r.Register(application.NewConfigCommand())
   406  	r.Register(application.NewDeployCommand())
   407  	r.Register(application.NewExposeCommand())
   408  	r.Register(application.NewUnexposeCommand())
   409  	r.Register(application.NewApplicationGetConstraintsCommand())
   410  	r.Register(application.NewApplicationSetConstraintsCommand())
   411  	r.Register(application.NewBundleDiffCommand())
   412  	r.Register(application.NewShowApplicationCommand())
   413  
   414  	// Operation protection commands
   415  	r.Register(block.NewDisableCommand())
   416  	r.Register(block.NewListCommand())
   417  	r.Register(block.NewEnableCommand())
   418  
   419  	// Manage storage
   420  	r.Register(storage.NewAddCommand())
   421  	r.Register(storage.NewListCommand())
   422  	r.Register(storage.NewPoolCreateCommand())
   423  	r.Register(storage.NewPoolListCommand())
   424  	r.Register(storage.NewPoolRemoveCommand())
   425  	r.Register(storage.NewPoolUpdateCommand())
   426  	r.Register(storage.NewShowCommand())
   427  	r.Register(storage.NewRemoveStorageCommandWithAPI())
   428  	r.Register(storage.NewDetachStorageCommandWithAPI())
   429  	r.Register(storage.NewAttachStorageCommandWithAPI())
   430  	r.Register(storage.NewImportFilesystemCommand(storage.NewStorageImporter, nil))
   431  
   432  	// Manage spaces
   433  	r.Register(space.NewAddCommand())
   434  	r.Register(space.NewListCommand())
   435  	r.Register(space.NewReloadCommand())
   436  	if featureflag.Enabled(feature.PostNetCLIMVP) {
   437  		r.Register(space.NewRemoveCommand())
   438  		r.Register(space.NewUpdateCommand())
   439  		r.Register(space.NewRenameCommand())
   440  	}
   441  
   442  	// Manage subnets
   443  	r.Register(subnet.NewAddCommand())
   444  	r.Register(subnet.NewListCommand())
   445  	if featureflag.Enabled(feature.PostNetCLIMVP) {
   446  		r.Register(subnet.NewCreateCommand())
   447  		r.Register(subnet.NewRemoveCommand())
   448  	}
   449  
   450  	// Manage controllers
   451  	r.Register(controller.NewAddModelCommand())
   452  	r.Register(controller.NewDestroyCommand())
   453  	r.Register(controller.NewListModelsCommand())
   454  	r.Register(controller.NewKillCommand())
   455  	r.Register(controller.NewListControllersCommand())
   456  	r.Register(controller.NewRegisterCommand())
   457  	r.Register(controller.NewUnregisterCommand(jujuclient.NewFileClientStore()))
   458  	r.Register(controller.NewEnableDestroyControllerCommand())
   459  	r.Register(controller.NewShowControllerCommand())
   460  	r.Register(controller.NewConfigCommand())
   461  
   462  	// Debug Metrics
   463  	r.Register(metricsdebug.New())
   464  	r.Register(metricsdebug.NewCollectMetricsCommand())
   465  	r.Register(setmeterstatus.New())
   466  
   467  	// Manage clouds and credentials
   468  	r.Register(cloud.NewUpdateCloudsCommand())
   469  	r.Register(cloud.NewListCloudsCommand())
   470  	r.Register(cloud.NewListRegionsCommand())
   471  	r.Register(cloud.NewShowCloudCommand())
   472  	r.Register(cloud.NewAddCloudCommand(&cloudToCommandAdapter{}))
   473  	r.Register(cloud.NewRemoveCloudCommand())
   474  	r.Register(cloud.NewListCredentialsCommand())
   475  	r.Register(cloud.NewDetectCredentialsCommand())
   476  	r.Register(cloud.NewSetDefaultRegionCommand())
   477  	r.Register(cloud.NewSetDefaultCredentialCommand())
   478  	r.Register(cloud.NewAddCredentialCommand())
   479  	r.Register(cloud.NewRemoveCredentialCommand())
   480  	r.Register(cloud.NewUpdateCredentialCommand())
   481  	r.Register(cloud.NewShowCredentialCommand())
   482  	r.Register(model.NewGrantCloudCommand())
   483  	r.Register(model.NewRevokeCloudCommand())
   484  
   485  	// CAAS commands
   486  	r.Register(caas.NewAddCAASCommand(&cloudToCommandAdapter{}))
   487  	r.Register(caas.NewRemoveCAASCommand(&cloudToCommandAdapter{}))
   488  	r.Register(application.NewScaleApplicationCommand())
   489  
   490  	// Manage Application Credential Access
   491  	r.Register(application.NewTrustCommand())
   492  
   493  	// Juju GUI commands.
   494  	r.Register(gui.NewGUICommand())
   495  	r.Register(gui.NewUpgradeGUICommand())
   496  
   497  	// Resource commands
   498  	r.Register(resource.NewUploadCommand(resource.UploadDeps{
   499  		NewClient: func(c *resource.UploadCommand) (resource.UploadClient, error) {
   500  			apiRoot, err := c.NewAPIRoot()
   501  			if err != nil {
   502  				return nil, errors.Trace(err)
   503  			}
   504  			return resourceadapters.NewAPIClient(apiRoot)
   505  		},
   506  		OpenResource: func(s string) (resource.ReadSeekCloser, error) {
   507  			return os.Open(s)
   508  		},
   509  	}))
   510  	r.Register(resource.NewListCommand(resource.ListDeps{
   511  		NewClient: func(c *resource.ListCommand) (resource.ListClient, error) {
   512  			apiRoot, err := c.NewAPIRoot()
   513  			if err != nil {
   514  				return nil, errors.Trace(err)
   515  			}
   516  			return resourceadapters.NewAPIClient(apiRoot)
   517  		},
   518  	}))
   519  	r.Register(resource.NewCharmResourcesCommand(nil))
   520  
   521  	// Commands registered elsewhere.
   522  	for _, newCommand := range registeredCommands {
   523  		command := newCommand()
   524  		r.Register(command)
   525  	}
   526  	for _, newCommand := range registeredEnvCommands {
   527  		command := newCommand()
   528  		r.Register(modelcmd.Wrap(command))
   529  	}
   530  	rcmd.RegisterAll(r)
   531  }
   532  
   533  type cloudToCommandAdapter struct{}
   534  
   535  func (cloudToCommandAdapter) ParseCloudMetadataFile(path string) (map[string]cloudfile.Cloud, error) {
   536  	return cloudfile.ParseCloudMetadataFile(path)
   537  }
   538  func (cloudToCommandAdapter) ParseOneCloud(data []byte) (cloudfile.Cloud, error) {
   539  	return cloudfile.ParseOneCloud(data)
   540  }
   541  func (cloudToCommandAdapter) PublicCloudMetadata(searchPaths ...string) (map[string]cloudfile.Cloud, bool, error) {
   542  	return cloudfile.PublicCloudMetadata(searchPaths...)
   543  }
   544  func (cloudToCommandAdapter) PersonalCloudMetadata() (map[string]cloudfile.Cloud, error) {
   545  	return cloudfile.PersonalCloudMetadata()
   546  }
   547  func (cloudToCommandAdapter) WritePersonalCloudMetadata(cloudsMap map[string]cloudfile.Cloud) error {
   548  	return cloudfile.WritePersonalCloudMetadata(cloudsMap)
   549  }