| 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" |
| 11 "net/url" | 11 "net/url" |
| 12 "strings" | 12 "strings" |
| 13 | 13 |
| 14 "github.com/luci/gae/service/info" | |
| 15 "github.com/luci/gae/service/urlfetch" | 14 "github.com/luci/gae/service/urlfetch" |
| 16 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 17 gOAuth "golang.org/x/oauth2/google" | 16 gOAuth "golang.org/x/oauth2/google" |
| 18 "google.golang.org/appengine" | 17 "google.golang.org/appengine" |
| 19 "google.golang.org/appengine/remote_api" | 18 "google.golang.org/appengine/remote_api" |
| 20 ) | 19 ) |
| 21 | 20 |
| 22 // RemoteAPIScopes is the set of OAuth2 scopes needed for Remote API access. | 21 // RemoteAPIScopes is the set of OAuth2 scopes needed for Remote API access. |
| 23 var RemoteAPIScopes = []string{ | 22 var RemoteAPIScopes = []string{ |
| 24 "https://www.googleapis.com/auth/appengine.apis", | 23 "https://www.googleapis.com/auth/appengine.apis", |
| 25 "https://www.googleapis.com/auth/userinfo.email", | 24 "https://www.googleapis.com/auth/userinfo.email", |
| 26 "https://www.googleapis.com/auth/cloud.platform", | 25 "https://www.googleapis.com/auth/cloud.platform", |
| 27 } | 26 } |
| 28 | 27 |
| 29 type key int | 28 type key int |
| 30 | 29 |
| 31 var ( | 30 var ( |
| 32 » prodContextKey key | 31 » prodStateKey = "contains the current *prodState" |
| 33 » prodContextNoTxnKey key = 1 | 32 » probeCacheKey = "contains the current *infoProbeCache" |
| 34 » probeCacheKey key = 2 | |
| 35 ) | 33 ) |
| 36 | 34 |
| 37 // AEContext retrieves the raw "google.golang.org/appengine" compatible Context. | 35 // AEContext retrieves the raw "google.golang.org/appengine" compatible Context. |
| 38 // | 36 // |
| 39 // It also transfers deadline of `c` to AE context, since deadline is used for | 37 // It also transfers deadline of `c` to AE context, since deadline is used for |
| 40 // RPCs. Doesn't transfer cancelation ability though (since it's ignored by GAE | 38 // RPCs. Doesn't transfer cancelation ability though (since it's ignored by GAE |
| 41 // anyway). | 39 // anyway). |
| 42 func AEContext(c context.Context) context.Context { | 40 func AEContext(c context.Context) context.Context { |
| 43 » aeCtx, _ := c.Value(prodContextKey).(context.Context) | 41 » ps := getProdState(c) |
| 44 » if aeCtx == nil { | 42 » return ps.context(c) |
| 45 » » return nil | |
| 46 » } | |
| 47 » if deadline, ok := c.Deadline(); ok { | |
| 48 » » aeCtx, _ = context.WithDeadline(aeCtx, deadline) | |
| 49 » } | |
| 50 » return aeCtx | |
| 51 } | |
| 52 | |
| 53 // AEContextNoTxn retrieves the raw "google.golang.org/appengine" compatible | |
| 54 // Context that's not part of a transaction. | |
| 55 func AEContextNoTxn(c context.Context) context.Context { | |
| 56 » aeCtx, _ := c.Value(prodContextNoTxnKey).(context.Context) | |
| 57 » if aeCtx == nil { | |
| 58 » » return nil | |
| 59 » } | |
| 60 | |
| 61 » if ns, has := info.Get(c).GetNamespace(); has { | |
| 62 » » var err error | |
| 63 » » aeCtx, err = appengine.Namespace(aeCtx, ns) | |
| 64 » » if err != nil { | |
| 65 » » » panic(err) | |
| 66 » » } | |
| 67 » } | |
| 68 » if deadline, ok := c.Deadline(); ok { | |
| 69 » » aeCtx, _ = context.WithDeadline(aeCtx, deadline) | |
| 70 » } | |
| 71 » return aeCtx | |
| 72 } | 43 } |
| 73 | 44 |
| 74 func setupAECtx(c, aeCtx context.Context) context.Context { | 45 func setupAECtx(c, aeCtx context.Context) context.Context { |
| 75 » c = context.WithValue(c, prodContextKey, aeCtx) | 46 » c = withProdState(c, prodState{ |
| 76 » c = context.WithValue(c, prodContextNoTxnKey, aeCtx) | 47 » » ctx: aeCtx, |
| 48 » » noTxnCtx: aeCtx, |
| 49 » }) |
| 77 return useModule(useMail(useUser(useURLFetch(useRDS(useMC(useTQ(useGI(us
eLogging(c))))))))) | 50 return useModule(useMail(useUser(useURLFetch(useRDS(useMC(useTQ(useGI(us
eLogging(c))))))))) |
| 78 } | 51 } |
| 79 | 52 |
| 80 // Use adds production implementations for all the gae services to the | 53 // Use adds production implementations for all the gae services to the |
| 81 // context. | 54 // context. |
| 82 // | 55 // |
| 83 // The services added are: | 56 // The services added are: |
| 84 // - github.com/luci-go/common/logging | 57 // - github.com/luci-go/common/logging |
| 85 // - github.com/luci/gae/service/datastore | 58 // - github.com/luci/gae/service/datastore |
| 86 // - github.com/luci/gae/service/info | 59 // - github.com/luci/gae/service/info |
| (...skipping 29 matching lines...) Expand all Loading... |
| 116 // * If host starts with "localhost", this will create a regular http.Client | 89 // * If host starts with "localhost", this will create a regular http.Client |
| 117 // with a cookiejar, and call the _ah/login API to log in as an admin with | 90 // with a cookiejar, and call the _ah/login API to log in as an admin with |
| 118 // the user "admin@example.com". | 91 // the user "admin@example.com". |
| 119 // | 92 // |
| 120 // * Otherwise, it will create a Google OAuth2 client with the following scope
s: | 93 // * Otherwise, it will create a Google OAuth2 client with the following scope
s: |
| 121 // - "https://www.googleapis.com/auth/appengine.apis" | 94 // - "https://www.googleapis.com/auth/appengine.apis" |
| 122 // - "https://www.googleapis.com/auth/userinfo.email" | 95 // - "https://www.googleapis.com/auth/userinfo.email" |
| 123 // - "https://www.googleapis.com/auth/cloud.platform" | 96 // - "https://www.googleapis.com/auth/cloud.platform" |
| 124 func UseRemote(inOutCtx *context.Context, host string, client *http.Client) (err
error) { | 97 func UseRemote(inOutCtx *context.Context, host string, client *http.Client) (err
error) { |
| 125 if client == nil { | 98 if client == nil { |
| 99 aeCtx := AEContext(*inOutCtx) |
| 100 |
| 126 if strings.HasPrefix(host, "localhost") { | 101 if strings.HasPrefix(host, "localhost") { |
| 127 transp := http.DefaultTransport | 102 transp := http.DefaultTransport |
| 128 » » » if aeCtx := AEContextNoTxn(*inOutCtx); aeCtx != nil { | 103 » » » if aeCtx != nil { |
| 129 transp = urlfetch.Get(*inOutCtx) | 104 transp = urlfetch.Get(*inOutCtx) |
| 130 } | 105 } |
| 131 | 106 |
| 132 client = &http.Client{Transport: transp} | 107 client = &http.Client{Transport: transp} |
| 133 client.Jar, err = cookiejar.New(nil) | 108 client.Jar, err = cookiejar.New(nil) |
| 134 if err != nil { | 109 if err != nil { |
| 135 return | 110 return |
| 136 } | 111 } |
| 137 u := fmt.Sprintf("http://%s/_ah/login?%s", host, url.Val
ues{ | 112 u := fmt.Sprintf("http://%s/_ah/login?%s", host, url.Val
ues{ |
| 138 "email": {"admin@example.com"}, | 113 "email": {"admin@example.com"}, |
| 139 "admin": {"True"}, | 114 "admin": {"True"}, |
| 140 "action": {"Login"}, | 115 "action": {"Login"}, |
| 141 }.Encode()) | 116 }.Encode()) |
| 142 | 117 |
| 143 var rsp *http.Response | 118 var rsp *http.Response |
| 144 rsp, err = client.Get(u) | 119 rsp, err = client.Get(u) |
| 145 if err != nil { | 120 if err != nil { |
| 146 return | 121 return |
| 147 } | 122 } |
| 148 defer rsp.Body.Close() | 123 defer rsp.Body.Close() |
| 149 } else { | 124 } else { |
| 150 aeCtx := AEContextNoTxn(*inOutCtx) | |
| 151 if aeCtx == nil { | 125 if aeCtx == nil { |
| 152 aeCtx = context.Background() | 126 aeCtx = context.Background() |
| 153 } | 127 } |
| 154 client, err = gOAuth.DefaultClient(aeCtx, RemoteAPIScope
s...) | 128 client, err = gOAuth.DefaultClient(aeCtx, RemoteAPIScope
s...) |
| 155 if err != nil { | 129 if err != nil { |
| 156 return | 130 return |
| 157 } | 131 } |
| 158 } | 132 } |
| 159 } | 133 } |
| 160 | 134 |
| 161 aeCtx, err := remote_api.NewRemoteContext(host, client) | 135 aeCtx, err := remote_api.NewRemoteContext(host, client) |
| 162 if err != nil { | 136 if err != nil { |
| 163 return | 137 return |
| 164 } | 138 } |
| 165 *inOutCtx = setupAECtx(*inOutCtx, aeCtx) | 139 *inOutCtx = setupAECtx(*inOutCtx, aeCtx) |
| 166 return nil | 140 return nil |
| 167 } | 141 } |
| 142 |
| 143 // prodState is the current production state. |
| 144 type prodState struct { |
| 145 // ctx is the current derived GAE context. |
| 146 ctx context.Context |
| 147 |
| 148 // noTxnCtx is a Context maintained alongside ctx. When a transaction is |
| 149 // entered, ctx will be updated, but noTxnCtx will not, allowing extra- |
| 150 // transactional Context access. |
| 151 noTxnCtx context.Context |
| 152 |
| 153 // inTxn if true if this is in a transaction, false otherwise. |
| 154 inTxn bool |
| 155 } |
| 156 |
| 157 func getProdState(c context.Context) prodState { |
| 158 if v := c.Value(&prodStateKey).(*prodState); v != nil { |
| 159 return *v |
| 160 } |
| 161 return prodState{} |
| 162 } |
| 163 |
| 164 func withProdState(c context.Context, ps prodState) context.Context { |
| 165 return context.WithValue(c, &prodStateKey, &ps) |
| 166 } |
| 167 |
| 168 // context returns the current AppEngine-bound Context. Prior to returning, |
| 169 // the deadline from "c" (if any) is applied. |
| 170 // |
| 171 // Note that this does not (currently) apply any other Done state or propagate |
| 172 // cancellation from "c". |
| 173 // |
| 174 // Tracking at: |
| 175 // https://github.com/luci/gae/issues/59 |
| 176 func (ps *prodState) context(c context.Context) context.Context { |
| 177 aeCtx := ps.ctx |
| 178 if aeCtx == nil { |
| 179 return nil |
| 180 } |
| 181 |
| 182 if deadline, ok := c.Deadline(); ok { |
| 183 aeCtx, _ = context.WithDeadline(aeCtx, deadline) |
| 184 } |
| 185 return aeCtx |
| 186 } |
| OLD | NEW |