Index: impl/prod/context.go |
diff --git a/impl/prod/context.go b/impl/prod/context.go |
index 73fec355fd739518a432bc310b43ae6acb7344b6..bccbf7934cd648ad1cb748ec7fad3b0d3f46b38e 100644 |
--- a/impl/prod/context.go |
+++ b/impl/prod/context.go |
@@ -5,9 +5,14 @@ |
package prod |
import ( |
+ "fmt" |
"net/http" |
+ "net/http/cookiejar" |
+ "net/url" |
+ "strings" |
"github.com/luci/gae/service/info" |
+ "github.com/luci/gae/service/urlfetch" |
"golang.org/x/net/context" |
gOAuth "golang.org/x/oauth2/google" |
"google.golang.org/appengine" |
@@ -29,6 +34,9 @@ var ( |
// anyway). |
func AEContext(c context.Context) context.Context { |
aeCtx, _ := c.Value(prodContextKey).(context.Context) |
+ if aeCtx == nil { |
+ return nil |
+ } |
if deadline, ok := c.Deadline(); ok { |
aeCtx, _ = context.WithDeadline(aeCtx, deadline) |
} |
@@ -39,6 +47,9 @@ func AEContext(c context.Context) context.Context { |
// Context that's not part of a transaction. |
func AEContextNoTxn(c context.Context) context.Context { |
aeCtx, _ := c.Value(prodContextNoTxnKey).(context.Context) |
+ if aeCtx == nil { |
+ return nil |
+ } |
aeCtx, err := appengine.Namespace(aeCtx, info.Get(c).GetNamespace()) |
if err != nil { |
panic(err) |
@@ -81,27 +92,68 @@ func Use(c context.Context, r *http.Request) context.Context { |
// |
// docs: https://cloud.google.com/appengine/docs/go/tools/remoteapi |
// |
-// If client is nil, this will use a default Google OAuth2 client. Otherwise the |
-// client must be configured to have the following OAuth2 scopes: |
-// "https://www.googleapis.com/auth/appengine.apis" |
-// "https://www.googleapis.com/auth/userinfo.email" |
-// "https://www.googleapis.com/auth/cloud.platform" |
-func UseRemote(c context.Context, host string, client *http.Client) (context.Context, error) { |
- err := error(nil) |
+// inOutCtx will be replaced with the new, derived context, if err is nil, |
+// otherwise it's unchanged and continues to be safe-to-use. |
+// |
+// If client is nil, this will use create a new client, and will try to be |
+// clever about it: |
+// * If you're creating a remote context FROM AppEngine, this will use |
+// urlfetch.Transport. This can be used to allow app-to-app remote_api |
+// control. |
+// |
+// * If host starts with "localhost", this will create a regular http.Client |
+// with a cookiejar, and call the _ah/login API to log in as an admin with |
+// the user "admin@example.com". |
+// |
+// * Otherwise, it will create a Google OAuth2 client with the following scopes: |
+// - "https://www.googleapis.com/auth/appengine.apis" |
+// - "https://www.googleapis.com/auth/userinfo.email" |
+// - "https://www.googleapis.com/auth/cloud.platform" |
+func UseRemote(inOutCtx *context.Context, host string, client *http.Client) (err error) { |
if client == nil { |
- client, err = gOAuth.DefaultClient(context.Background(), |
- "https://www.googleapis.com/auth/appengine.apis", |
- "https://www.googleapis.com/auth/userinfo.email", |
- "https://www.googleapis.com/auth/cloud.platform", |
- ) |
- if err != nil { |
- return nil, err |
+ if strings.HasPrefix(host, "localhost") { |
+ transp := http.DefaultTransport |
+ if aeCtx := AEContextNoTxn(*inOutCtx); aeCtx != nil { |
+ transp = urlfetch.Get(aeCtx) |
+ } |
+ |
+ client = &http.Client{Transport: transp} |
+ client.Jar, err = cookiejar.New(nil) |
+ if err != nil { |
+ return |
+ } |
+ u := fmt.Sprintf("http://%s/_ah/login?%s", host, url.Values{ |
+ "email": {"admin@example.com"}, |
+ "admin": {"True"}, |
+ "action": {"Login"}, |
+ }.Encode()) |
+ |
+ var rsp *http.Response |
+ rsp, err = client.Get(u) |
+ if err != nil { |
+ return |
+ } |
+ defer rsp.Body.Close() |
+ } else { |
+ aeCtx := AEContextNoTxn(*inOutCtx) |
+ if aeCtx == nil { |
+ aeCtx = context.Background() |
+ } |
+ client, err = gOAuth.DefaultClient(aeCtx, |
+ "https://www.googleapis.com/auth/appengine.apis", |
+ "https://www.googleapis.com/auth/userinfo.email", |
+ "https://www.googleapis.com/auth/cloud.platform", |
+ ) |
+ if err != nil { |
+ return |
+ } |
} |
} |
aeCtx, err := remote_api.NewRemoteContext(host, client) |
if err != nil { |
- return nil, err |
+ return |
} |
- return setupAECtx(c, aeCtx), nil |
+ *inOutCtx = setupAECtx(*inOutCtx, aeCtx) |
+ return nil |
} |