Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package prod | 5 package prod |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | 8 "fmt" |
| 9 "net/http" | 9 "net/http" |
| 10 "net/http/cookiejar" | 10 "net/http/cookiejar" |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 25 "https://www.googleapis.com/auth/cloud.platform", | 25 "https://www.googleapis.com/auth/cloud.platform", |
| 26 } | 26 } |
| 27 | 27 |
| 28 type key int | 28 type key int |
| 29 | 29 |
| 30 var ( | 30 var ( |
| 31 prodStateKey = "contains the current *prodState" | 31 prodStateKey = "contains the current *prodState" |
| 32 probeCacheKey = "contains the current *infoProbeCache" | 32 probeCacheKey = "contains the current *infoProbeCache" |
| 33 ) | 33 ) |
| 34 | 34 |
| 35 // AEContext retrieves the raw "google.golang.org/appengine" compatible Context. | 35 // getAEContext retrieves the raw "google.golang.org/appengine" compatible |
| 36 // Context. | |
| 36 // | 37 // |
| 37 // It also transfers deadline of `c` to AE context, since deadline is used for | 38 // This is an independent Context chain from `c`. In an attempt to maintain user |
| 38 // RPCs. Doesn't transfer cancelation ability though (since it's ignored by GAE | 39 // expectations, the deadline of `c` is transferred to the returned Context, |
| 39 // anyway). | 40 // RPCs. Cancelation is not transferred. |
| 40 func AEContext(c context.Context) context.Context { | 41 func getAEContext(c context.Context) context.Context { |
| 41 ps := getProdState(c) | 42 ps := getProdState(c) |
| 42 return ps.context(c) | 43 return ps.context(c) |
| 43 } | 44 } |
| 44 | 45 |
| 45 func setupAECtx(c, aeCtx context.Context) context.Context { | 46 func setupAECtx(c context.Context) context.Context { |
| 47 » // Retain the base AppEngine Context. This will allow us to switch betwe en | |
| 48 » // transactional and non-transactional Contexts as needed. | |
| 46 c = withProdState(c, prodState{ | 49 c = withProdState(c, prodState{ |
| 47 » » ctx: aeCtx, | 50 » » ctx: c, |
| 48 » » noTxnCtx: aeCtx, | 51 » » noTxnCtx: c, |
| 49 }) | 52 }) |
| 50 return useModule(useMail(useUser(useURLFetch(useRDS(useMC(useTQ(useGI(us eLogging(c))))))))) | 53 return useModule(useMail(useUser(useURLFetch(useRDS(useMC(useTQ(useGI(us eLogging(c))))))))) |
| 51 } | 54 } |
| 52 | 55 |
| 53 // Use adds production implementations for all the gae services to the | 56 // Use adds production implementations for all the gae services to the |
| 54 // context. | 57 // context. The implementations are all backed by the real AppEngine SDK |
| 58 // functionality, | |
| 55 // | 59 // |
| 56 // The services added are: | 60 // The services added are: |
| 57 // - github.com/luci-go/common/logging | 61 // - github.com/luci-go/common/logging |
| 58 // - github.com/luci/gae/service/datastore | 62 // - github.com/luci/gae/service/datastore |
| 59 // - github.com/luci/gae/service/info | 63 // - github.com/luci/gae/service/info |
| 60 // - github.com/luci/gae/service/mail | 64 // - github.com/luci/gae/service/mail |
| 61 // - github.com/luci/gae/service/memcache | 65 // - github.com/luci/gae/service/memcache |
| 62 // - github.com/luci/gae/service/module | 66 // - github.com/luci/gae/service/module |
| 63 // - github.com/luci/gae/service/taskqueue | 67 // - github.com/luci/gae/service/taskqueue |
| 64 // - github.com/luci/gae/service/urlfetch | 68 // - github.com/luci/gae/service/urlfetch |
| 65 // - github.com/luci/gae/service/user | 69 // - github.com/luci/gae/service/user |
| 66 // | 70 // |
| 67 // These can be retrieved with the <service>.Get functions. | 71 // In addition, Use installs Google AppEngine SDK Context values into the |
| 68 // | 72 // returned Context, allowing it to be used with raw Google AppEngine SDK APIs. |
| 69 // The implementations are all backed by the real appengine SDK functionality, | 73 // THIS IS VERY DANGEROUS, as the AppEngine SDK is NOT aware of luci/gae |
| 74 // services and DOES NOT coordinate state with them. Dependency on AppEngine SDK | |
| 75 // calls undermines a lot of value that luci/gae adds, and should be used | |
| 76 // CAUTIOUSLY and EXPERTLY. Some examples of pitfalls: | |
|
iannucci
2016/10/28 18:40:05
as I mentioned on the other CL, I would stress tha
| |
| 77 //» - AppEngine SDK is not aware of the luci/gae transaction state of the | |
| 78 //» Context. AppEngine calls to a Context that is in a luci/gae transactio n | |
| 79 //» will not operate on that transaction, and luci/gae calls to a Context that | |
| 80 //» is in an AppEngine SDK transaction state will not operate on that | |
| 81 //» transaction. | |
| 82 //» - luci/gae filters are completely bypassed when accessing AppEngine SDK | |
| 83 //» directly. This means that operations such as `dscache` will not be | |
| 84 //» consulted or updated, leading to potential inconsistent state and memo ry | |
| 85 //» corruption issues. | |
|
iannucci
2016/10/28 18:40:05
data corruption. not really memory corruption.
| |
| 70 func Use(c context.Context, r *http.Request) context.Context { | 86 func Use(c context.Context, r *http.Request) context.Context { |
| 71 » return setupAECtx(c, appengine.NewContext(r)) | 87 » return setupAECtx(appengine.WithContext(c, r)) |
|
iannucci
2016/10/28 18:40:05
why is setupAECtx still needed at all?
dnj
2016/10/29 00:10:59
UseRemote still uses it.
| |
| 72 } | 88 } |
| 73 | 89 |
| 74 // UseRemote is the same as Use, except that it lets you attach a context to | 90 // UseRemote is the same as Use, except that it lets you attach a context to |
| 75 // a remote host using the Remote API feature. See the docs for the | 91 // a remote host using the Remote API feature. See the docs for the |
| 76 // prerequisites. | 92 // prerequisites. |
| 77 // | 93 // |
| 78 // docs: https://cloud.google.com/appengine/docs/go/tools/remoteapi | 94 // docs: https://cloud.google.com/appengine/docs/go/tools/remoteapi |
| 79 // | 95 // |
| 80 // inOutCtx will be replaced with the new, derived context, if err is nil, | 96 // If client is nil, http.DefaultClient will be used. |
| 81 // otherwise it's unchanged and continues to be safe-to-use. | 97 func UseRemote(host string, client *http.Client) (context.Context, error) { |
| 82 // | |
| 83 // If client is nil, this will use create a new client, and will try to be | |
| 84 // clever about it: | |
| 85 // * If you're creating a remote context FROM AppEngine, this will use | |
| 86 // urlfetch.Transport. This can be used to allow app-to-app remote_api | |
| 87 // control. | |
| 88 // | |
| 89 // * If host starts with "localhost", this will create a regular http.Client | |
| 90 // with a cookiejar, and call the _ah/login API to log in as an admin with | |
| 91 // the user "admin@example.com". | |
| 92 // | |
| 93 // * Otherwise, it will create a Google OAuth2 client with the following scope s: | |
| 94 // - "https://www.googleapis.com/auth/appengine.apis" | |
| 95 // - "https://www.googleapis.com/auth/userinfo.email" | |
| 96 // - "https://www.googleapis.com/auth/cloud.platform" | |
| 97 func UseRemote(inOutCtx *context.Context, host string, client *http.Client) (err error) { | |
| 98 if client == nil { | 98 if client == nil { |
| 99 » » aeCtx := AEContext(*inOutCtx) | 99 » » client = http.DefaultClient |
| 100 | |
| 101 » » if strings.HasPrefix(host, "localhost") { | |
| 102 » » » transp := http.DefaultTransport | |
| 103 » » » if aeCtx != nil { | |
| 104 » » » » transp = urlfetch.Get(*inOutCtx) | |
| 105 » » » } | |
| 106 | |
| 107 » » » client = &http.Client{Transport: transp} | |
| 108 » » » client.Jar, err = cookiejar.New(nil) | |
| 109 » » » if err != nil { | |
| 110 » » » » return | |
| 111 » » » } | |
| 112 » » » u := fmt.Sprintf("http://%s/_ah/login?%s", host, url.Val ues{ | |
| 113 » » » » "email": {"admin@example.com"}, | |
| 114 » » » » "admin": {"True"}, | |
| 115 » » » » "action": {"Login"}, | |
| 116 » » » }.Encode()) | |
| 117 | |
| 118 » » » var rsp *http.Response | |
| 119 » » » rsp, err = client.Get(u) | |
| 120 » » » if err != nil { | |
| 121 » » » » return | |
| 122 » » » } | |
| 123 » » » defer rsp.Body.Close() | |
| 124 » » } else { | |
| 125 » » » if aeCtx == nil { | |
| 126 » » » » aeCtx = context.Background() | |
| 127 » » » } | |
| 128 » » » client, err = gOAuth.DefaultClient(aeCtx, RemoteAPIScope s...) | |
| 129 » » » if err != nil { | |
| 130 » » » » return | |
| 131 » » » } | |
| 132 » » } | |
| 133 } | 100 } |
| 134 | 101 |
| 135 » aeCtx, err := remote_api.NewRemoteContext(host, client) | 102 » c, err := remote_api.NewRemoteContext(host, client) |
| 136 if err != nil { | 103 if err != nil { |
| 137 » » return | 104 » » return nil, err |
| 138 } | 105 } |
| 139 » *inOutCtx = setupAECtx(*inOutCtx, aeCtx) | 106 » return setupAECtx(c), nil |
| 140 » return nil | 107 } |
| 108 | |
| 109 // RemoteAPIClientForHost generates an authenticated HTTP client suitable for | |
| 110 // use with UseRemote for the specified host. | |
| 111 // | |
| 112 //» - If you're creating a remote context FROM AppEngine, this will use | |
| 113 //» urlfetch.Transport. This can be used to allow app-to-app remote_api | |
| 114 //» control. This is detected when a luci/gae urlfetch service is present in | |
| 115 //» the supplied Context. | |
| 116 // | |
| 117 //» - If host starts with "localhost", this will create a regular http.Clien t | |
| 118 //» with a cookiejar, and call the _ah/login API to log in as an admin wit h | |
| 119 //» the user "admin@example.com". | |
| 120 // | |
| 121 //» - Otherwise, it will create a Google OAuth2 client with the following | |
| 122 //» scopes: | |
| 123 //» - "https://www.googleapis.com/auth/appengine.apis" | |
| 124 //» - "https://www.googleapis.com/auth/userinfo.email" | |
| 125 //» - "https://www.googleapis.com/auth/cloud.platform" | |
| 126 func RemoteAPIClientForHost(c context.Context, host string) (*http.Client, error ) { | |
| 127 » aeCtx := getAEContext(c) | |
| 128 | |
| 129 » if strings.HasPrefix(host, "localhost") { | |
| 130 » » transp := http.DefaultTransport | |
| 131 » » if aeCtx != nil { | |
| 132 » » » transp = urlfetch.Get(aeCtx) | |
| 133 » » } | |
| 134 | |
| 135 » » client := &http.Client{Transport: transp} | |
| 136 | |
| 137 » » var err error | |
| 138 » » if client.Jar, err = cookiejar.New(nil); err != nil { | |
| 139 » » » return nil, err | |
| 140 » » } | |
| 141 » » u := fmt.Sprintf("http://%s/_ah/login?%s", host, url.Values{ | |
| 142 » » » "email": {"admin@example.com"}, | |
| 143 » » » "admin": {"True"}, | |
| 144 » » » "action": {"Login"}, | |
| 145 » » }.Encode()) | |
| 146 | |
| 147 » » var rsp *http.Response | |
| 148 » » if rsp, err = client.Get(u); err != nil { | |
| 149 » » » return nil, err | |
| 150 » » } | |
| 151 » » defer rsp.Body.Close() | |
| 152 » » return client, nil | |
| 153 » } | |
| 154 | |
| 155 » if aeCtx == nil { | |
| 156 » » aeCtx = context.Background() | |
| 157 » } | |
| 158 » return gOAuth.DefaultClient(aeCtx, RemoteAPIScopes...) | |
| 141 } | 159 } |
| 142 | 160 |
| 143 // prodState is the current production state. | 161 // prodState is the current production state. |
| 144 type prodState struct { | 162 type prodState struct { |
| 145 // ctx is the current derived GAE context. | 163 // ctx is the current derived GAE context. |
| 146 ctx context.Context | 164 ctx context.Context |
| 147 | 165 |
| 148 // noTxnCtx is a Context maintained alongside ctx. When a transaction is | 166 // noTxnCtx is a Context maintained alongside ctx. When a transaction is |
| 149 // entered, ctx will be updated, but noTxnCtx will not, allowing extra- | 167 // entered, ctx will be updated, but noTxnCtx will not, allowing extra- |
| 150 // transactional Context access. | 168 // transactional Context access. |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 177 aeCtx := ps.ctx | 195 aeCtx := ps.ctx |
| 178 if aeCtx == nil { | 196 if aeCtx == nil { |
| 179 return nil | 197 return nil |
| 180 } | 198 } |
| 181 | 199 |
| 182 if deadline, ok := c.Deadline(); ok { | 200 if deadline, ok := c.Deadline(); ok { |
| 183 aeCtx, _ = context.WithDeadline(aeCtx, deadline) | 201 aeCtx, _ = context.WithDeadline(aeCtx, deadline) |
| 184 } | 202 } |
| 185 return aeCtx | 203 return aeCtx |
| 186 } | 204 } |
| OLD | NEW |