github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/controller/controller.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"encoding/json"
     8  
     9  	"github.com/juju/errors"
    10  	"gopkg.in/juju/names.v2"
    11  	"gopkg.in/macaroon.v2-unstable"
    12  
    13  	"github.com/juju/juju/api"
    14  	"github.com/juju/juju/api/base"
    15  	"github.com/juju/juju/api/common"
    16  	"github.com/juju/juju/api/common/cloudspec"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/core/model"
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/permission"
    21  )
    22  
    23  // Client provides methods that the Juju client command uses to interact
    24  // with the Juju controller.
    25  type Client struct {
    26  	base.ClientFacade
    27  	facade base.FacadeCaller
    28  	*common.ControllerConfigAPI
    29  	*common.ModelStatusAPI
    30  	*cloudspec.CloudSpecAPI
    31  }
    32  
    33  // NewClient creates a new `Client` based on an existing authenticated API
    34  // connection.
    35  func NewClient(st base.APICallCloser) *Client {
    36  	frontend, backend := base.NewClientFacade(st, "Controller")
    37  	return &Client{
    38  		ClientFacade:        frontend,
    39  		facade:              backend,
    40  		ControllerConfigAPI: common.NewControllerConfig(backend),
    41  		ModelStatusAPI:      common.NewModelStatusAPI(backend),
    42  	}
    43  }
    44  
    45  // AllModels allows controller administrators to get the list of all the
    46  // models in the controller.
    47  func (c *Client) AllModels() ([]base.UserModel, error) {
    48  	var models params.UserModelList
    49  	err := c.facade.FacadeCall("AllModels", nil, &models)
    50  	if err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  	result := make([]base.UserModel, len(models.UserModels))
    54  	for i, usermodel := range models.UserModels {
    55  		owner, err := names.ParseUserTag(usermodel.OwnerTag)
    56  		if err != nil {
    57  			return nil, errors.Annotatef(err, "OwnerTag %q at position %d", usermodel.OwnerTag, i)
    58  		}
    59  		modelType := model.ModelType(usermodel.Type)
    60  		if modelType == "" {
    61  			modelType = model.IAAS
    62  		}
    63  		result[i] = base.UserModel{
    64  			Name:           usermodel.Name,
    65  			UUID:           usermodel.UUID,
    66  			Type:           modelType,
    67  			Owner:          owner.Id(),
    68  			LastConnection: usermodel.LastConnection,
    69  		}
    70  	}
    71  	return result, nil
    72  }
    73  
    74  // CloudSpec returns a CloudSpec for the specified model.
    75  func (c *Client) CloudSpec(modelTag names.ModelTag) (environs.CloudSpec, error) {
    76  	api := cloudspec.NewCloudSpecAPI(c.facade, modelTag)
    77  	return api.CloudSpec()
    78  }
    79  
    80  // ModelConfig returns all model settings for the
    81  // controller model.
    82  func (c *Client) ModelConfig() (map[string]interface{}, error) {
    83  	result := params.ModelConfigResults{}
    84  	err := c.facade.FacadeCall("ModelConfig", nil, &result)
    85  	values := make(map[string]interface{})
    86  	for name, val := range result.Config {
    87  		values[name] = val.Value
    88  	}
    89  	return values, err
    90  }
    91  
    92  // HostedConfig contains the model config and the cloud spec for that
    93  // model such that direct access to the provider can be used.
    94  type HostedConfig struct {
    95  	Name      string
    96  	Owner     names.UserTag
    97  	Config    map[string]interface{}
    98  	CloudSpec environs.CloudSpec
    99  	Error     error
   100  }
   101  
   102  // HostedModelsConfig returns all model settings for the
   103  // controller model.
   104  func (c *Client) HostedModelConfigs() ([]HostedConfig, error) {
   105  	result := params.HostedModelConfigsResults{}
   106  	err := c.facade.FacadeCall("HostedModelConfigs", nil, &result)
   107  	if err != nil {
   108  		return nil, errors.Trace(err)
   109  	}
   110  	// If we get to here, we have some values. Each value may or
   111  	// may not have an error, but it should at least have a name
   112  	// and owner.
   113  	hostedConfigs := make([]HostedConfig, len(result.Models))
   114  	for i, modelConfig := range result.Models {
   115  		hostedConfigs[i].Name = modelConfig.Name
   116  		tag, err := names.ParseUserTag(modelConfig.OwnerTag)
   117  		if err != nil {
   118  			hostedConfigs[i].Error = errors.Trace(err)
   119  			continue
   120  		}
   121  		hostedConfigs[i].Owner = tag
   122  		if modelConfig.Error != nil {
   123  			hostedConfigs[i].Error = errors.Trace(modelConfig.Error)
   124  			continue
   125  		}
   126  		hostedConfigs[i].Config = modelConfig.Config
   127  		spec, err := c.MakeCloudSpec(modelConfig.CloudSpec)
   128  		if err != nil {
   129  			hostedConfigs[i].Error = errors.Trace(err)
   130  			continue
   131  		}
   132  		hostedConfigs[i].CloudSpec = spec
   133  	}
   134  	return hostedConfigs, err
   135  }
   136  
   137  // DestroyControllerParams controls the behaviour of destroying the controller.
   138  type DestroyControllerParams struct {
   139  	// DestroyModels controls whether or not all hosted models should be
   140  	// destroyed. If this is false, and there are non-empty hosted models,
   141  	// an error with the code params.CodeHasHostedModels will be returned.
   142  	DestroyModels bool
   143  
   144  	// DestroyStorage controls whether or not storage in the model (and
   145  	// hosted models, if DestroyModels is true) should be destroyed.
   146  	//
   147  	// This is ternary: nil, false, or true. If nil and there is persistent
   148  	// storage in the model (or hosted models), an error with the code
   149  	// params.CodeHasPersistentStorage will be returned.
   150  	DestroyStorage *bool
   151  }
   152  
   153  // DestroyController puts the controller model into a "dying" state,
   154  // and removes all non-manager machine instances.
   155  func (c *Client) DestroyController(args DestroyControllerParams) error {
   156  	if c.BestAPIVersion() < 4 {
   157  		if args.DestroyStorage == nil || !*args.DestroyStorage {
   158  			return errors.New("this Juju controller requires DestroyStorage to be true")
   159  		}
   160  		args.DestroyStorage = nil
   161  	}
   162  	return c.facade.FacadeCall("DestroyController", params.DestroyControllerArgs{
   163  		DestroyModels:  args.DestroyModels,
   164  		DestroyStorage: args.DestroyStorage,
   165  	}, nil)
   166  }
   167  
   168  // ListBlockedModels returns a list of all models within the controller
   169  // which have at least one block in place.
   170  func (c *Client) ListBlockedModels() ([]params.ModelBlockInfo, error) {
   171  	result := params.ModelBlockInfoList{}
   172  	err := c.facade.FacadeCall("ListBlockedModels", nil, &result)
   173  	return result.Models, err
   174  }
   175  
   176  // RemoveBlocks removes all the blocks in the controller.
   177  func (c *Client) RemoveBlocks() error {
   178  	args := params.RemoveBlocksArgs{All: true}
   179  	return c.facade.FacadeCall("RemoveBlocks", args, nil)
   180  }
   181  
   182  // WatchAllModels returns an AllWatcher, from which you can request
   183  // the Next collection of Deltas (for all models).
   184  func (c *Client) WatchAllModels() (*api.AllWatcher, error) {
   185  	var info params.AllWatcherId
   186  	if err := c.facade.FacadeCall("WatchAllModels", nil, &info); err != nil {
   187  		return nil, err
   188  	}
   189  	return api.NewAllModelWatcher(c.facade.RawAPICaller(), &info.AllWatcherId), nil
   190  }
   191  
   192  // GrantController grants a user access to the controller.
   193  func (c *Client) GrantController(user, access string) error {
   194  	return c.modifyControllerUser(params.GrantControllerAccess, user, access)
   195  }
   196  
   197  // RevokeController revokes a user's access to the controller.
   198  func (c *Client) RevokeController(user, access string) error {
   199  	return c.modifyControllerUser(params.RevokeControllerAccess, user, access)
   200  }
   201  
   202  func (c *Client) modifyControllerUser(action params.ControllerAction, user, access string) error {
   203  	var args params.ModifyControllerAccessRequest
   204  
   205  	if !names.IsValidUser(user) {
   206  		return errors.Errorf("invalid username: %q", user)
   207  	}
   208  	userTag := names.NewUserTag(user)
   209  
   210  	args.Changes = []params.ModifyControllerAccess{{
   211  		UserTag: userTag.String(),
   212  		Action:  action,
   213  		Access:  access,
   214  	}}
   215  
   216  	var result params.ErrorResults
   217  	err := c.facade.FacadeCall("ModifyControllerAccess", args, &result)
   218  	if err != nil {
   219  		return errors.Trace(err)
   220  	}
   221  	if len(result.Results) != len(args.Changes) {
   222  		return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results))
   223  	}
   224  
   225  	return result.Combine()
   226  }
   227  
   228  // GetControllerAccess returns the access level the user has on the controller.
   229  func (c *Client) GetControllerAccess(user string) (permission.Access, error) {
   230  	if !names.IsValidUser(user) {
   231  		return "", errors.Errorf("invalid username: %q", user)
   232  	}
   233  	entities := params.Entities{Entities: []params.Entity{{names.NewUserTag(user).String()}}}
   234  	var results params.UserAccessResults
   235  	err := c.facade.FacadeCall("GetControllerAccess", entities, &results)
   236  	if err != nil {
   237  		return "", errors.Trace(err)
   238  	}
   239  	if len(results.Results) != 1 {
   240  		return "", errors.Errorf("expected 1 result, got %d", len(results.Results))
   241  	}
   242  	if err := results.Results[0].Error; err != nil {
   243  		return "", errors.Trace(err)
   244  	}
   245  	return permission.Access(results.Results[0].Result.Access), nil
   246  }
   247  
   248  // ConfigSet updates the passed controller configuration values. Any
   249  // settings that aren't passed will be left with their previous
   250  // values.
   251  func (c *Client) ConfigSet(values map[string]interface{}) error {
   252  	if c.BestAPIVersion() < 5 {
   253  		return errors.Errorf("this controller version doesn't support updating controller config")
   254  	}
   255  	return errors.Trace(
   256  		c.facade.FacadeCall("ConfigSet", params.ControllerConfigSet{Config: values}, nil),
   257  	)
   258  }
   259  
   260  // MigrationSpec holds the details required to start the migration of
   261  // a single model.
   262  type MigrationSpec struct {
   263  	ModelUUID            string
   264  	TargetControllerUUID string
   265  	TargetAddrs          []string
   266  	TargetCACert         string
   267  	TargetUser           string
   268  	TargetPassword       string
   269  	TargetMacaroons      []macaroon.Slice
   270  }
   271  
   272  // Validate performs sanity checks on the migration configuration it
   273  // holds.
   274  func (s *MigrationSpec) Validate() error {
   275  	if !names.IsValidModel(s.ModelUUID) {
   276  		return errors.NotValidf("model UUID")
   277  	}
   278  	if !names.IsValidModel(s.TargetControllerUUID) {
   279  		return errors.NotValidf("controller UUID")
   280  	}
   281  	if len(s.TargetAddrs) < 1 {
   282  		return errors.NotValidf("empty target API addresses")
   283  	}
   284  	if !names.IsValidUser(s.TargetUser) {
   285  		return errors.NotValidf("target user")
   286  	}
   287  	if s.TargetPassword == "" && len(s.TargetMacaroons) == 0 {
   288  		return errors.NotValidf("missing authentication secrets")
   289  	}
   290  	return nil
   291  }
   292  
   293  // InitiateMigration attempts to start a migration for the specified
   294  // model, returning the migration's ID.
   295  //
   296  // The API server supports starting multiple migrations in one request
   297  // but we don't need that at the client side yet (and may never) so
   298  // this call just supports starting one migration at a time.
   299  func (c *Client) InitiateMigration(spec MigrationSpec) (string, error) {
   300  	if err := spec.Validate(); err != nil {
   301  		return "", errors.Annotatef(err, "client-side validation failed")
   302  	}
   303  
   304  	macsJSON, err := macaroonsToJSON(spec.TargetMacaroons)
   305  	if err != nil {
   306  		return "", errors.Annotatef(err, "client-side validation failed")
   307  	}
   308  
   309  	args := params.InitiateMigrationArgs{
   310  		Specs: []params.MigrationSpec{{
   311  			ModelTag: names.NewModelTag(spec.ModelUUID).String(),
   312  			TargetInfo: params.MigrationTargetInfo{
   313  				ControllerTag: names.NewControllerTag(spec.TargetControllerUUID).String(),
   314  				Addrs:         spec.TargetAddrs,
   315  				CACert:        spec.TargetCACert,
   316  				AuthTag:       names.NewUserTag(spec.TargetUser).String(),
   317  				Password:      spec.TargetPassword,
   318  				Macaroons:     macsJSON,
   319  			},
   320  		}},
   321  	}
   322  	response := params.InitiateMigrationResults{}
   323  	if err := c.facade.FacadeCall("InitiateMigration", args, &response); err != nil {
   324  		return "", errors.Trace(err)
   325  	}
   326  	if len(response.Results) != 1 {
   327  		return "", errors.New("unexpected number of results returned")
   328  	}
   329  	result := response.Results[0]
   330  	if result.Error != nil {
   331  		return "", errors.Trace(result.Error)
   332  	}
   333  	return result.MigrationId, nil
   334  }
   335  
   336  func macaroonsToJSON(macs []macaroon.Slice) (string, error) {
   337  	if len(macs) == 0 {
   338  		return "", nil
   339  	}
   340  	out, err := json.Marshal(macs)
   341  	if err != nil {
   342  		return "", errors.Annotate(err, "marshalling macaroons")
   343  	}
   344  	return string(out), nil
   345  }