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 }