github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "strconv" 9 10 "github.com/juju/errors" 11 "github.com/juju/names" 12 "github.com/juju/version" 13 "gopkg.in/macaroon-bakery.v1/httpbakery" 14 "gopkg.in/macaroon.v1" 15 16 "github.com/juju/juju/api/addresser" 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/firewaller" 22 "github.com/juju/juju/api/imagemetadata" 23 "github.com/juju/juju/api/instancepoller" 24 "github.com/juju/juju/api/keyupdater" 25 "github.com/juju/juju/api/provisioner" 26 "github.com/juju/juju/api/reboot" 27 "github.com/juju/juju/api/unitassigner" 28 "github.com/juju/juju/api/uniter" 29 "github.com/juju/juju/api/upgrader" 30 "github.com/juju/juju/apiserver/params" 31 "github.com/juju/juju/network" 32 ) 33 34 // Login authenticates as the entity with the given name and password 35 // or macaroons. Subsequent requests on the state will act as that entity. 36 // This method is usually called automatically by Open. The machine nonce 37 // should be empty unless logging in as a machine agent. 38 func (st *state) Login(tag names.Tag, password, nonce string, ms []macaroon.Slice) error { 39 err := st.loginV3(tag, password, nonce, ms) 40 return errors.Trace(err) 41 } 42 43 // loginV2 is retained for testing logins from older clients. 44 func (st *state) loginV2(tag names.Tag, password, nonce string, ms []macaroon.Slice) error { 45 return st.loginForVersion(tag, password, nonce, ms, 2) 46 } 47 48 func (st *state) loginV3(tag names.Tag, password, nonce string, ms []macaroon.Slice) error { 49 return st.loginForVersion(tag, password, nonce, ms, 3) 50 } 51 52 func (st *state) loginForVersion(tag names.Tag, password, nonce string, macaroons []macaroon.Slice, vers int) error { 53 var result params.LoginResultV1 54 request := ¶ms.LoginRequest{ 55 AuthTag: tagToString(tag), 56 Credentials: password, 57 Nonce: nonce, 58 Macaroons: macaroons, 59 } 60 if tag == nil { 61 // Add any macaroons from the cookie jar that might work for 62 // authenticating the login request. 63 request.Macaroons = append(request.Macaroons, 64 httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL)..., 65 ) 66 } 67 err := st.APICall("Admin", vers, "", "Login", request, &result) 68 if err != nil { 69 return errors.Trace(err) 70 } 71 if result.DischargeRequired != nil { 72 // The result contains a discharge-required 73 // macaroon. We discharge it and retry 74 // the login request with the original macaroon 75 // and its discharges. 76 if result.DischargeRequiredReason == "" { 77 result.DischargeRequiredReason = "no reason given for discharge requirement" 78 } 79 if err := st.bakeryClient.HandleError(st.cookieURL, &httpbakery.Error{ 80 Message: result.DischargeRequiredReason, 81 Code: httpbakery.ErrDischargeRequired, 82 Info: &httpbakery.ErrorInfo{ 83 Macaroon: result.DischargeRequired, 84 MacaroonPath: "/", 85 }, 86 }); err != nil { 87 return errors.Trace(err) 88 } 89 // Add the macaroons that have been saved by HandleError to our login request. 90 request.Macaroons = httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL) 91 result = params.LoginResultV1{} // zero result 92 err = st.APICall("Admin", vers, "", "Login", request, &result) 93 if err != nil { 94 return errors.Trace(err) 95 } 96 if result.DischargeRequired != nil { 97 return errors.Errorf("login with discharged macaroons failed: %s", result.DischargeRequiredReason) 98 } 99 } 100 101 if result.UserInfo != nil { 102 // This was a macaroon based user authentication. 103 tag, err = names.ParseTag(result.UserInfo.Identity) 104 if err != nil { 105 return errors.Trace(err) 106 } 107 } 108 servers := params.NetworkHostsPorts(result.Servers) 109 err = st.setLoginResult(tag, result.ModelTag, result.ControllerTag, servers, result.Facades) 110 if err != nil { 111 return errors.Trace(err) 112 } 113 st.serverVersion, err = version.Parse(result.ServerVersion) 114 if err != nil { 115 return errors.Trace(err) 116 } 117 return nil 118 } 119 120 func (st *state) setLoginResult(tag names.Tag, modelTag, controllerTag string, servers [][]network.HostPort, facades []params.FacadeVersions) error { 121 st.authTag = tag 122 st.modelTag = modelTag 123 st.controllerTag = controllerTag 124 125 hostPorts, err := addAddress(servers, st.addr) 126 if err != nil { 127 if clerr := st.Close(); clerr != nil { 128 err = errors.Annotatef(err, "error closing state: %v", clerr) 129 } 130 return err 131 } 132 st.hostPorts = hostPorts 133 134 st.facadeVersions = make(map[string][]int, len(facades)) 135 for _, facade := range facades { 136 st.facadeVersions[facade.Name] = facade.Versions 137 } 138 139 st.setLoggedIn() 140 return nil 141 } 142 143 // AuthTag returns the tag of the authorized user of the state API connection. 144 func (st *state) AuthTag() names.Tag { 145 return st.authTag 146 } 147 148 // slideAddressToFront moves the address at the location (serverIndex, addrIndex) to be 149 // the first address of the first server. 150 func slideAddressToFront(servers [][]network.HostPort, serverIndex, addrIndex int) { 151 server := servers[serverIndex] 152 hostPort := server[addrIndex] 153 // Move the matching address to be the first in this server 154 for ; addrIndex > 0; addrIndex-- { 155 server[addrIndex] = server[addrIndex-1] 156 } 157 server[0] = hostPort 158 for ; serverIndex > 0; serverIndex-- { 159 servers[serverIndex] = servers[serverIndex-1] 160 } 161 servers[0] = server 162 } 163 164 // addAddress appends a new server derived from the given 165 // address to servers if the address is not already found 166 // there. 167 func addAddress(servers [][]network.HostPort, addr string) ([][]network.HostPort, error) { 168 for i, server := range servers { 169 for j, hostPort := range server { 170 if hostPort.NetAddr() == addr { 171 slideAddressToFront(servers, i, j) 172 return servers, nil 173 } 174 } 175 } 176 host, portString, err := net.SplitHostPort(addr) 177 if err != nil { 178 return nil, err 179 } 180 port, err := strconv.Atoi(portString) 181 if err != nil { 182 return nil, err 183 } 184 result := make([][]network.HostPort, 0, len(servers)+1) 185 result = append(result, network.NewHostPorts(port, host)) 186 result = append(result, servers...) 187 return result, nil 188 } 189 190 // Client returns an object that can be used 191 // to access client-specific functionality. 192 func (st *state) Client() *Client { 193 frontend, backend := base.NewClientFacade(st, "Client") 194 return &Client{ClientFacade: frontend, facade: backend, st: st} 195 } 196 197 // UnitAssigner returns a version of the state that provides functionality 198 // required by the unitassigner worker. 199 func (st *state) UnitAssigner() unitassigner.API { 200 return unitassigner.New(st) 201 } 202 203 // Provisioner returns a version of the state that provides functionality 204 // required by the provisioner worker. 205 func (st *state) Provisioner() *provisioner.State { 206 return provisioner.NewState(st) 207 } 208 209 // Uniter returns a version of the state that provides functionality 210 // required by the uniter worker. 211 func (st *state) Uniter() (*uniter.State, error) { 212 unitTag, ok := st.authTag.(names.UnitTag) 213 if !ok { 214 return nil, errors.Errorf("expected UnitTag, got %T %v", st.authTag, st.authTag) 215 } 216 return uniter.NewState(st, unitTag), nil 217 } 218 219 // Firewaller returns a version of the state that provides functionality 220 // required by the firewaller worker. 221 func (st *state) Firewaller() *firewaller.State { 222 return firewaller.NewState(st) 223 } 224 225 // Upgrader returns access to the Upgrader API 226 func (st *state) Upgrader() *upgrader.State { 227 return upgrader.NewState(st) 228 } 229 230 // Reboot returns access to the Reboot API 231 func (st *state) Reboot() (reboot.State, error) { 232 switch tag := st.authTag.(type) { 233 case names.MachineTag: 234 return reboot.NewState(st, tag), nil 235 default: 236 return nil, errors.Errorf("expected names.MachineTag, got %T", tag) 237 } 238 } 239 240 // Addresser returns access to the Addresser API. 241 func (st *state) Addresser() *addresser.API { 242 return addresser.NewAPI(st) 243 } 244 245 // DiscoverSpaces returns access to the DiscoverSpacesAPI. 246 func (st *state) DiscoverSpaces() *discoverspaces.API { 247 return discoverspaces.NewAPI(st) 248 } 249 250 // KeyUpdater returns access to the KeyUpdater API 251 func (st *state) KeyUpdater() *keyupdater.State { 252 return keyupdater.NewState(st) 253 } 254 255 // InstancePoller returns access to the InstancePoller API 256 func (st *state) InstancePoller() *instancepoller.API { 257 return instancepoller.NewAPI(st) 258 } 259 260 // CharmRevisionUpdater returns access to the CharmRevisionUpdater API 261 func (st *state) CharmRevisionUpdater() *charmrevisionupdater.State { 262 return charmrevisionupdater.NewState(st) 263 } 264 265 // Cleaner returns a version of the state that provides access to the cleaner API 266 func (st *state) Cleaner() *cleaner.API { 267 return cleaner.NewAPI(st) 268 } 269 270 // ServerVersion holds the version of the API server that we are connected to. 271 // It is possible that this version is Zero if the server does not report this 272 // during login. The second result argument indicates if the version number is 273 // set. 274 func (st *state) ServerVersion() (version.Number, bool) { 275 return st.serverVersion, st.serverVersion != version.Zero 276 } 277 278 // MetadataUpdater returns access to the imageMetadata API 279 func (st *state) MetadataUpdater() *imagemetadata.Client { 280 return imagemetadata.NewClient(st) 281 }