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 }