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