3
votes

I need a bit of help with Google Compute Engine API call from within App Engine code. Following is part of code that I use to get a list of compute engine instances (simplified version)

try {
            final AppIdentityService appIdService = AppIdentityServiceFactory
                    .getAppIdentityService();
            AppIdentityService.GetAccessTokenResult result = appIdService
                    .getAccessTokenUncached(Collections
                            .singletonList(ComputeScopes.COMPUTE));
            String accessToken = result.getAccessToken();
            String url = "https://www.googleapis.com/compute/v1/projects/MYPROJECTID/zones/us-central1-b/instances";
            String payload = "";

            // Create HTTPRequest and set headers
            HTTPRequest httpRequest = new HTTPRequest(new URL(url.toString()),
                    HTTPMethod.GET, FetchOptions.Builder.doNotFollowRedirects());

            httpRequest.addHeader(new HTTPHeader("Authorization", "OAuth "
                    + accessToken));

            httpRequest.addHeader(new HTTPHeader("Host", "www.googleapis.com"));
            httpRequest.addHeader(new HTTPHeader("Content-Length", Integer
                    .toString(payload.length())));
            httpRequest.addHeader(new HTTPHeader("Content-Type",
                    "application/json"));
            httpRequest.addHeader(new HTTPHeader("User-Agent",
                    "google-api-java-client/1.0"));
            httpRequest.setPayload(payload.getBytes());

            URLFetchService fetcher = URLFetchServiceFactory
                    .getURLFetchService();
            HTTPResponse httpResponse = fetcher.fetch(httpRequest);

            int responseCode = httpResponse.getResponseCode();
            if ((responseCode == 200) || (responseCode == 204)) {
                String contentStr = new String(httpResponse.getContent());
                return extractIpsAndInstanceNames(contentStr, prefix);
            } else {
                logger.warning("Failed. Response code " + responseCode
                        + " Reason: " + new String(httpResponse.getContent()));
            }

As you can see I am using AppIdentity to obtain access token. Then use it in request header in API call.

Basically every time the call fails with following error

Failed. Response code 404 Reason: {
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "notFound",
    "message": "The resource 'projects/MYPROJECTID' was not found"
   }
  ],
  "code": 404,
  "message": "The resource 'projects/MYPROJECTID' was not found"
 }
}

What is interesting is that if I use following webapp https://developers.google.com/compute/docs/reference/latest/instances/list#try-it to make the same API call it succeeds.

So I looked into what data are sent when this web app makes request and copied bearer token string and used it in "Authorization" header. Strangely enough request now finished successfully without changing anything else. Basically that app uses user consent Oauth2 type of token - so for me it looks like there is some problem with token obtained via AppIdentity. Could someone point me in right direction? Thanks!

3
I've been seeing the exact same problem with the Go runtime. I'm making a request to list compute images from app engine using the built in app engine service account and I get a 404. Using APIs Explorer, however, works fine.Doug Richardson
Did you have any luck solving it? One more think came to my mind - our App Engine project was created some time ago when it was not possible to have apis in same project - we have activated them later on when it became available. Do you have similar setup?Petr Chudanic
Yes, see answer below.Doug Richardson

3 Answers

3
votes

I ran into the same issue and was able to solve it, or perhaps I should say workaround it, in a way that doesn't make complete sense to me. Hopefully someone with real knowledge on the subject can explain further. This solution is similar to what E. Anderson answered, but different because both App Engine and Compute Engine were in the same project.

Here's what I did:

  1. In app engine running for real (not in the local developer mode) print out the service account email. Using the Go runtime, I used appengine.ServiceAccount(ctx).
  2. In Google Developers Console, go to the Permissions page for your project and add the e-mail address you obtained in the previous step as a member of your project.

Once I did this, I was able to make requests to Compute Engine REST APIs from App Engine. I have no idea why this step was necessary.

1
votes

Are you using an appengine app in the same project as the GCE project you're trying to manage? If not, you'll need to add the service account identity from the AppEngine app to the project team for the GCE project; look under the "Permissions" tab on the cloud console to see whether your AppEngine app has access to the GCE project.

0
votes

To provide a variant on Doug's answer:

  1. Go to https://appengine.google.com, select your application, and go to the Administration > 'Application Settings' page. Here you can find your service account email address. Use this in case you want to add the email address explicitly, i. e. if your compute engine project is different from your app engine project. If that is the case, follow Doug's answer.

  2. At the very bottom of the application settings page, find the section called 'Cloud Integration'. Do the integration -- this will take a minute.

  3. Lo and behold, if you go to https://console.developers.google.com, select your project, and go to the Permissions page for your project (the link is far down on the left side), you will find that your app engine service account now shows up on the list of service accounts, with edit rights. The 404 should be resolved.

If you want your OAuth authorization code to work from the dev server as well, this answer might help: https://stackoverflow.com/a/22723127