github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/api/state.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api 5 6 import ( 7 "net" 8 "net/url" 9 "strconv" 10 11 "github.com/juju/errors" 12 "github.com/juju/version" 13 "gopkg.in/juju/names.v2" 14 "gopkg.in/macaroon-bakery.v1/httpbakery" 15 "gopkg.in/macaroon.v1" 16 17 "github.com/juju/juju/api/base" 18 "github.com/juju/juju/api/charmrevisionupdater" 19 "github.com/juju/juju/api/cleaner" 20 "github.com/juju/juju/api/discoverspaces" 21 "github.com/juju/juju/api/imagemetadata" 22 "github.com/juju/juju/api/instancepoller" 23 "github.com/juju/juju/api/keyupdater" 24 "github.com/juju/juju/api/reboot" 25 "github.com/juju/juju/api/unitassigner" 26 "github.com/juju/juju/api/uniter" 27 "github.com/juju/juju/api/upgrader" 28 "github.com/juju/juju/apiserver/params" 29 "github.com/juju/juju/network" 30 ) 31 32 // Login authenticates as the entity with the given name and password 33 // or macaroons. Subsequent requests on the state will act as that entity. 34 // This method is usually called automatically by Open. The machine nonce 35 // should be empty unless logging in as a machine agent. 36 func (st *state) Login(tag names.Tag, password, nonce string, macaroons []macaroon.Slice) error { 37 var result params.LoginResult 38 request := ¶ms.LoginRequest{ 39 AuthTag: tagToString(tag), 40 Credentials: password, 41 Nonce: nonce, 42 Macaroons: macaroons, 43 } 44 if password == "" { 45 // Add any macaroons from the cookie jar that might work for 46 // authenticating the login request. 47 request.Macaroons = append(request.Macaroons, 48 httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL)..., 49 ) 50 } 51 err := st.APICall("Admin", 3, "", "Login", request, &result) 52 if err != nil { 53 var resp params.RedirectInfoResult 54 if params.IsRedirect(err) { 55 // We've been asked to redirect. Find out the redirection info. 56 // If the rpc packet allowed us to return arbitrary information in 57 // an error, we'd probably put this information in the Login response, 58 // but we can't do that currently. 59 if err := st.APICall("Admin", 3, "", "RedirectInfo", nil, &resp); err != nil { 60 return errors.Annotatef(err, "cannot get redirect addresses") 61 } 62 return &RedirectError{ 63 Servers: params.NetworkHostsPorts(resp.Servers), 64 CACert: resp.CACert, 65 } 66 } 67 return errors.Trace(err) 68 } 69 if result.DischargeRequired != nil { 70 // The result contains a discharge-required 71 // macaroon. We discharge it and retry 72 // the login request with the original macaroon 73 // and its discharges. 74 if result.DischargeRequiredReason == "" { 75 result.DischargeRequiredReason = "no reason given for discharge requirement" 76 } 77 if err := st.bakeryClient.HandleError(st.cookieURL, &httpbakery.Error{ 78 Message: result.DischargeRequiredReason, 79 Code: httpbakery.ErrDischargeRequired, 80 Info: &httpbakery.ErrorInfo{ 81 Macaroon: result.DischargeRequired, 82 MacaroonPath: "/", 83 }, 84 }); err != nil { 85 cause := errors.Cause(err) 86 if httpbakery.IsInteractionError(cause) { 87 // Just inform the user of the reason for the 88 // failure, e.g. because the username/password 89 // they presented was invalid. 90 err = cause.(*httpbakery.InteractionError).Reason 91 } 92 return errors.Trace(err) 93 } 94 // Add the macaroons that have been saved by HandleError to our login request. 95 request.Macaroons = httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL) 96 result = params.LoginResult{} // zero result 97 err = st.APICall("Admin", 3, "", "Login", request, &result) 98 if err != nil { 99 return errors.Trace(err) 100 } 101 if result.DischargeRequired != nil { 102 return errors.Errorf("login with discharged macaroons failed: %s", result.DischargeRequiredReason) 103 } 104 } 105 106 var controllerAccess string 107 var modelAccess string 108 if result.UserInfo != nil { 109 tag, err = names.ParseTag(result.UserInfo.Identity) 110 if err != nil { 111 return errors.Trace(err) 112 } 113 controllerAccess = result.UserInfo.ControllerAccess 114 modelAccess = result.UserInfo.ModelAccess 115 } 116 servers := params.NetworkHostsPorts(result.Servers) 117 if err = st.setLoginResult(loginResultParams{ 118 tag: tag, 119 modelTag: result.ModelTag, 120 controllerTag: result.ControllerTag, 121 servers: servers, 122 facades: result.Facades, 123 modelAccess: modelAccess, 124 controllerAccess: controllerAccess, 125 }); err != nil { 126 return errors.Trace(err) 127 } 128 st.serverVersion, err = version.Parse(result.ServerVersion) 129 if err != nil { 130 return errors.Trace(err) 131 } 132 return nil 133 } 134 135 type loginResultParams struct { 136 tag names.Tag 137 modelTag string 138 controllerTag string 139 modelAccess string 140 controllerAccess string 141 servers [][]network.HostPort 142 facades []params.FacadeVersions 143 } 144 145 func (st *state) setLoginResult(p loginResultParams) error { 146 st.authTag = p.tag 147 var modelTag names.ModelTag 148 if p.modelTag != "" { 149 var err error 150 modelTag, err = names.ParseModelTag(p.modelTag) 151 if err != nil { 152 return errors.Annotatef(err, "invalid model tag in login result") 153 } 154 } 155 if modelTag.Id() != st.modelTag.Id() { 156 return errors.Errorf("mismatched model tag in login result (got %q want %q)", modelTag.Id(), st.modelTag.Id()) 157 } 158 ctag, err := names.ParseControllerTag(p.controllerTag) 159 if err != nil { 160 return errors.Annotatef(err, "invalid controller tag %q returned from login", p.controllerTag) 161 } 162 st.controllerTag = ctag 163 st.controllerAccess = p.controllerAccess 164 st.modelAccess = p.modelAccess 165 166 hostPorts, err := addAddress(p.servers, st.addr) 167 if err != nil { 168 if clerr := st.Close(); clerr != nil { 169 err = errors.Annotatef(err, "error closing state: %v", clerr) 170 } 171 return err 172 } 173 st.hostPorts = hostPorts 174 175 st.facadeVersions = make(map[string][]int, len(p.facades)) 176 for _, facade := range p.facades { 177 st.facadeVersions[facade.Name] = facade.Versions 178 } 179 180 st.setLoggedIn() 181 return nil 182 } 183 184 // AuthTag returns the tag of the authorized user of the state API connection. 185 func (st *state) AuthTag() names.Tag { 186 return st.authTag 187 } 188 189 // ModelAccess returns the access level of authorized user to the model. 190 func (st *state) ModelAccess() string { 191 return st.modelAccess 192 } 193 194 // ControllerAccess returns the access level of authorized user to the model. 195 func (st *state) ControllerAccess() string { 196 return st.controllerAccess 197 } 198 199 // CookieURL returns the URL that HTTP cookies for the API will be 200 // associated with. 201 func (st *state) CookieURL() *url.URL { 202 copy := *st.cookieURL 203 return © 204 } 205 206 // slideAddressToFront moves the address at the location (serverIndex, addrIndex) to be 207 // the first address of the first server. 208 func slideAddressToFront(servers [][]network.HostPort, serverIndex, addrIndex int) { 209 server := servers[serverIndex] 210 hostPort := server[addrIndex] 211 // Move the matching address to be the first in this server 212 for ; addrIndex > 0; addrIndex-- { 213 server[addrIndex] = server[addrIndex-1] 214 } 215 server[0] = hostPort 216 for ; serverIndex > 0; serverIndex-- { 217 servers[serverIndex] = servers[serverIndex-1] 218 } 219 servers[0] = server 220 } 221 222 // addAddress appends a new server derived from the given 223 // address to servers if the address is not already found 224 // there. 225 func addAddress(servers [][]network.HostPort, addr string) ([][]network.HostPort, error) { 226 for i, server := range servers { 227 for j, hostPort := range server { 228 if hostPort.NetAddr() == addr { 229 slideAddressToFront(servers, i, j) 230 return servers, nil 231 } 232 } 233 } 234 host, portString, err := net.SplitHostPort(addr) 235 if err != nil { 236 return nil, err 237 } 238 port, err := strconv.Atoi(portString) 239 if err != nil { 240 return nil, err 241 } 242 result := make([][]network.HostPort, 0, len(servers)+1) 243 result = append(result, network.NewHostPorts(port, host)) 244 result = append(result, servers...) 245 return result, nil 246 } 247 248 // Client returns an object that can be used 249 // to access client-specific functionality. 250 func (st *state) Client() *Client { 251 frontend, backend := base.NewClientFacade(st, "Client") 252 return &Client{ClientFacade: frontend, facade: backend, st: st} 253 } 254 255 // UnitAssigner returns a version of the state that provides functionality 256 // required by the unitassigner worker. 257 func (st *state) UnitAssigner() unitassigner.API { 258 return unitassigner.New(st) 259 } 260 261 // Uniter returns a version of the state that provides functionality 262 // required by the uniter worker. 263 func (st *state) Uniter() (*uniter.State, error) { 264 unitTag, ok := st.authTag.(names.UnitTag) 265 if !ok { 266 return nil, errors.Errorf("expected UnitTag, got %T %v", st.authTag, st.authTag) 267 } 268 return uniter.NewState(st, unitTag), nil 269 } 270 271 // Upgrader returns access to the Upgrader API 272 func (st *state) Upgrader() *upgrader.State { 273 return upgrader.NewState(st) 274 } 275 276 // Reboot returns access to the Reboot API 277 func (st *state) Reboot() (reboot.State, error) { 278 switch tag := st.authTag.(type) { 279 case names.MachineTag: 280 return reboot.NewState(st, tag), nil 281 default: 282 return nil, errors.Errorf("expected names.MachineTag, got %T", tag) 283 } 284 } 285 286 // DiscoverSpaces returns access to the DiscoverSpacesAPI. 287 func (st *state) DiscoverSpaces() *discoverspaces.API { 288 return discoverspaces.NewAPI(st) 289 } 290 291 // KeyUpdater returns access to the KeyUpdater API 292 func (st *state) KeyUpdater() *keyupdater.State { 293 return keyupdater.NewState(st) 294 } 295 296 // InstancePoller returns access to the InstancePoller API 297 func (st *state) InstancePoller() *instancepoller.API { 298 return instancepoller.NewAPI(st) 299 } 300 301 // CharmRevisionUpdater returns access to the CharmRevisionUpdater API 302 func (st *state) CharmRevisionUpdater() *charmrevisionupdater.State { 303 return charmrevisionupdater.NewState(st) 304 } 305 306 // Cleaner returns a version of the state that provides access to the cleaner API 307 func (st *state) Cleaner() *cleaner.API { 308 return cleaner.NewAPI(st) 309 } 310 311 // ServerVersion holds the version of the API server that we are connected to. 312 // It is possible that this version is Zero if the server does not report this 313 // during login. The second result argument indicates if the version number is 314 // set. 315 func (st *state) ServerVersion() (version.Number, bool) { 316 return st.serverVersion, st.serverVersion != version.Zero 317 } 318 319 // MetadataUpdater returns access to the imageMetadata API 320 func (st *state) MetadataUpdater() *imagemetadata.Client { 321 return imagemetadata.NewClient(st) 322 }