How do i authenticate to and get catalog from Azure Data Catalog using Rest API in Python - python

I would like to get the name of the catalog from Azure Data Catalog using API. When I tried using the following command to get catalog from Azure Data Catalog
requests.get("https://management.azure.com/subscriptions/{id}/resourceGroups/{group_name}/providers/Microsoft.DataCatalog/catalogs/{catalogname}")
as mentioned in the link https://learn.microsoft.com/en-us/rest/api/datacatalog/data-catalog-data-catalog
It throws the following error
Response [400]
Looks like I have to authenticate first. How do I authenticate prior to getting catalog?

Adding the new answer in python
for getting the auth context in python you could do the following
here is the settings for the parameters we need it while calling graph api.
RESOURCE = "https://graph.microsoft.com" # Add the resource you want the access token for
TENANT = "Your tenant" # Enter tenant name, e.g. contoso.onmicrosoft.com
AUTHORITY_HOST_URL = "https://login.microsoftonline.com"
CLIENT_ID = "Your client id " # copy the Application ID of your app from your Azure portal
CLIENT_SECRET = "Your client secret" # copy the value of key you generated when setting up the application
# These settings are for the Microsoft Graph API Call
API_VERSION = 'v1.0'
here is the code for logging in
AUTHORITY_URL = config.AUTHORITY_HOST_URL + '/' + config.TENANT
REDIRECT_URI = 'http://localhost:{}/getAToken'.format(PORT)
TEMPLATE_AUTHZ_URL = ('https://login.microsoftonline.com/{}/oauth2/authorize?' +
'response_type=code&client_id={}&redirect_uri={}&' +
'state={}&resource={}')
def login():
auth_state = str(uuid.uuid4())
flask.session['state'] = auth_state
authorization_url = TEMPLATE_AUTHZ_URL.format(
config.TENANT,
config.CLIENT_ID,
REDIRECT_URI,
auth_state,
config.RESOURCE)
resp = flask.Response(status=307)
resp.headers['location'] = authorization_url
return resp
here is how you can retrieve the token
auth_context = adal.AuthenticationContext(AUTHORITY_URL)
token_response = auth_context.acquire_token_with_authorization_code(code, REDIRECT_URI, config.RESOURCE,
config.CLIENT_ID, config.CLIENT_SECRET)
and then you can create a endpoint for your azure data catalog api. Here is the http header for the same-
http_headers = {'Authorization': 'Bearer ' + token_response['accessToken'],
'User-Agent': 'adal-python-sample',
'Accept': 'application/json',
'Content-Type': 'application/json',
'client-request-id': str(uuid.uuid4())}
and then finally you can call the api. Here endpoint being the data catalog API URL.
data = requests.get(endpoint, headers=http_headers, stream=False).json()
Hope it helps.

To call a Data Catalog REST operation, create an instance of AuthenticationContext and call AcquireToken. AuthenticationContext is part of the Active Directory Authentication Library NuGet package. To install the Active Directory Authentication Library NuGet package in Visual Studio,run
"Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory"
from the NuGet Package Manager Console.
Here is the code to get the token for the same.
static async Task<AuthenticationResult> AccessToken()
{
if (authResult == null)
{
//Resource Uri for Data Catalog API
string resourceUri = "https://api.azuredatacatalog.com";
//To learn how to register a client app and get a Client ID, see https://msdn.microsoft.com/en-us/library/azure/mt403303.aspx#clientID
string clientId = clientIDFromAzureAppRegistration;
//A redirect uri gives AAD more details about the specific application that it will authenticate.
//Since a client app does not have an external service to redirect to, this Uri is the standard placeholder for a client app.
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
// Create an instance of AuthenticationContext to acquire an Azure access token
// OAuth2 authority Uri
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
// AcquireToken takes a Client Id that Azure AD creates when you register your client app.
authResult = await authContext.AcquireTokenAsync(resourceUri, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always));
}
return authResult;
}
Here is the sample code to get the data asset base on id
// The Get Data Asset operation retrieves data asset by Id
static JObject GetDataAsset(string assetUrl)
{
string fullUri = string.Format("{0}?api-version=2016-03-30", assetUrl);
//Create a GET WebRequest as a Json content type
HttpWebRequest request = WebRequest.Create(fullUri) as HttpWebRequest;
request.KeepAlive = true;
request.Method = "GET";
request.Accept = "application/json;adc.metadata=full";
try
{
var response = SetRequestAndGetResponse(request);
using (var reader = new StreamReader(response.GetResponseStream()))
{
var itemPayload = reader.ReadToEnd();
Console.WriteLine(itemPayload);
return JObject.Parse(itemPayload);
}
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.Status);
if (ex.Response != null)
{
// can use ex.Response.Status, .StatusDescription
if (ex.Response.ContentLength != 0)
{
using (var stream = ex.Response.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
Console.WriteLine(reader.ReadToEnd());
}
}
}
}
}
return null;
}
Here is how you can set the request, token and get the response.
static HttpWebResponse SetRequestAndGetResponse(HttpWebRequest request, string payload = null)
{
while (true)
{
//To authorize the operation call, you need an access token which is part of the Authorization header
request.Headers.Add("Authorization", AccessToken().Result.CreateAuthorizationHeader());
//Set to false to be able to intercept redirects
request.AllowAutoRedirect = false;
if (!string.IsNullOrEmpty(payload))
{
byte[] byteArray = Encoding.UTF8.GetBytes(payload);
request.ContentLength = byteArray.Length;
request.ContentType = "application/json";
//Write JSON byte[] into a Stream
request.GetRequestStream().Write(byteArray, 0, byteArray.Length);
}
else
{
request.ContentLength = 0;
}
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
// Requests to **Azure Data Catalog (ADC)** may return an HTTP 302 response to indicate
// redirection to a different endpoint. In response to a 302, the caller must re-issue
// the request to the URL specified by the Location response header.
if (response.StatusCode == HttpStatusCode.Redirect)
{
string redirectedUrl = response.Headers["Location"];
HttpWebRequest nextRequest = WebRequest.Create(redirectedUrl) as HttpWebRequest;
nextRequest.Method = request.Method;
request = nextRequest;
}
else
{
return response;
}
}
}
Basically you need to get the bearer token and pass it as a request parameter to get the catalog using azure data catalog api.
for further code sample, please browse below code repository.
https://github.com/Azure-Samples/data-catalog-dotnet-get-started
Hope it helps.

Related

Azure AD bearer token from React to Flask API

I've setup my project, i.e. I have created a front-end in React, and a back-end in Flask.
In my front-end I call my back-end with a post method with the following code:
function POST(path, data) {
return fetch(`${fetchUrl}${path}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + RequestAccessToken(),
},
body: JSON.stringify(data)
}
)
}
Where RequestTokenAccess():
const { instance, accounts, inProgress } = useMsal();
const [accessToken, setAccessToken] = useState(null);
const name = accounts[0] && accounts[0].name;
function RequestAccessToken() {
const request = {
...loginRequest,
account: accounts[0]
};
instance.acquireTokenSilent(request).then((response) => {
setAccessToken(response.accessToken);
}).catch((e) => {
instance.acquireTokenPopup(request).then((response) => {
setAccessToken(response.accessToken);
});
});
}
And then just the following to actually make the call to the back-end:
const [data, setData] = useState()
function fetchData(e) {
e?.preventDefault();
POST('/my_app', { data: data }).then(
async (response) => {
const json = await response.json()
setData(json.return_data)
}
)
}
So for the front-end everything is working. I can get a MS Login that authorizes me so I can actually se the front-end, and I can also get a token from the RequestAccessToken function, which is given as a header to the back-end call. So everything seems to be set on the front-end part. However, the back-end calls also need to be secure is my guess, but I am not sure how that works.
Basically my app.py file looks something like:
from flask import Flask, request, jsonify
from my_app_func import MyAppFunc
app = Flask(__name__)
#app.post("/api/my_app")
def my_app():
data = request.json.get("data")
return_data = MyAppFunction(data)
return return_data
So basically, what do I need in order secure back-end calls ? I have the token as a Bearer Token in the post call. But what is the next step ? What do I actually do with it ?
I also have the same question, but couldn't find answer. Below is what works for me:
If you want to validate the user from flask, you can send the token along with your request from react.
Then within flask, validate the user by making a request to microsoft graph api.
Here is one example how to do this:
https://github.com/Azure-Samples/ms-identity-python-flask-webapp-call-graph
Another question for you is why you can directly concatenate RequestAccessToken() as a string? isn't it only call the setAccessToken? I ask because in my react app, I don't know how to export the token so that other function can use it. I ended up using the MSAL.js v2, not the one for react.
You have to register another app on the portal azure and and give permissions to the api and configure that in the another app in portal azure . Try to do something in that space.

API POST request - Python example to Google Script - 'Unexpected end of JSON input'

I'm trying to connect a Google spreadsheet to snov.io service using an API POST request, they have provided a Python code as example and I have tried to run the google script below based on it, but without success. Any ideas? Thank you :)
Python (provided by the company)
def get_access_token():
params = {
'grant_type':'client_credentials',
'client_id':'c57a0459f6t141659ea75cccb393c111',
'client_secret': '77cbf92b71553e85ce3bfd505214f40b'
}
res = requests.post('https://api.snov.io/v1/oauth/access_token', data=params)
resText = res.text.encode('ascii','ignore')
return json.loads(resText)['access_token']
Google Script (Error:SyntaxError:'Unexpected end of JSON input')
var params = {
'grant_type':'client_credentials',
'client_id':'c57a0459f6t141659ea75cccb393c111',
'client_secret': '77cbf92b71553e85ce3bfd505214f40b',
'method':'post'
}
var url = 'https://api.snov.io/v1/oauth/access_token';
var response = UrlFetchApp.fetch(url,params)
var json = response.getContentText();
obj = JSON.parse(json);
token = obj.result.access_token;
return token
In order to achieve the same request with your python script, how about modifying your Google Apps Script as follows?
Modified script:
From:
var params = {
'grant_type':'client_credentials',
'client_id':'c57a0459f6t141659ea75cccb393c111',
'client_secret': '77cbf92b71553e85ce3bfd505214f40b',
'method':'post'
}
var url = 'https://api.snov.io/v1/oauth/access_token';
var response = UrlFetchApp.fetch(url,params)
To:
var params = {
'grant_type': 'client_credentials',
'client_id': 'c57a0459f6t141659ea75cccb393c111',
'client_secret': '77cbf92b71553e85ce3bfd505214f40b',
}
var url = 'https://api.snov.io/v1/oauth/access_token';
var response = UrlFetchApp.fetch(url, {method: "post", payload: params});
In this case, even when method: "post" is removed, the same request is achieved.
Reference:
fetch(url, params)

Download File from Azure Blob Storage Using React

I am trying to download a file stored in Azure Blob Storage with my react app using SAS and am running into issues. I have a working version that relies on having the flask app download the file, then sending the blob to the react app to be downloaded again (obviously not ideal). Here's the current implementation:
flask endpoint:
from azure.storage.blob import BlobServiceClient
blobService = BlobServiceClient(account_url="https://<account_name>.blob.core.windows.net/", credential=<blob_key>)
#app.route('/download')
def downloadFile():
filename = request.args.get('filename')
blob_client = blobService.get_blob_client(container='<container_name>', blob=filename)
blobObject = blob_client.download_blob()
fileObject = io.BytesIO(blobObject.readall())
return send_file(fileObject, attachment_filename=filename, as_attachment=True)
get request in react:
const getFile = (e) => {
e.preventDefault();
const filename = e.currentTarget.getAttribute('name');
axios({
url: `${serverUrl}/download?filename=${filename}`,
method: 'GET',
responseType: 'blob',
})
.then(({ data }) => {
const link = document.createElement('a');
const url = URL.createObjectURL(new Blob([data]));
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(() => {
_isMounted.current && setDisplayError(true);
});
};
I would like to be able to just have my react app download the file direct from blob storage, but am running into authentication issues, along with another issue where clicking the download button navigates the browser to the url rather than just downloading the file at the location while staying on the current page. Here is the new code with the issues.
new flask endpoint:
from azure.storage.blob._shared_access_signature import BlobSharedAccessSignature
signatureService = BlobSharedAccessSignature('<account_name>', account_key='<azure_key>')
#app.route('/download')
def downloadFile():
filename = request.args.get('filename')
expiration = datetime.datetime.today() + datetime.timedelta(minutes=5)
container = '<container_name>'
key = signatureService.generate_blob(container, filename, permission='read', expiry=expiration)
data = {
'container': container,
'key': key
}
return app.response_class(
response=jsonifyWithNumPy(data),
status=200,
mimetype='application/json'
)
get request in react:
const getFile = (e) => {
e.preventDefault();
const filename = e.currentTarget.getAttribute('name');
axios
.get(`${serverUrl}/download?filename=${filename}`)
.then(({data}) => {
const url = `https://<account_name>.blob.core.windows.net/${data.container}/${filename}?${data.key}`
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(() => {
_isMounted.current && setDisplayError(true);
});
};
This is the error I get when I follow the URL generated by the above react code:
<Error>
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:48c49456-001e-008b-263c-3625fd000000 Time:2021-04-20T23:23:26.1909093Z</Message>
<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>
</Error>
I tried your code to create a blob SAS key and get the same error, just try the code below that works for me to create a blob SAS key and access a blob successfully:
from azure.storage.blob import generate_blob_sas,BlobSasPermissions
from datetime import datetime, timedelta
account = ''
container = ''
blob = ''
account_key = ''
permission=BlobSasPermissions(read=True)
exp = datetime.utcnow() + timedelta(hours=1)
key = generate_blob_sas(account_name=account,container_name=container,blob_name=blob,account_key=account_key,permission=permission,expiry=exp)

How do I link my site running on Google App Engine to use Google+ login? [duplicate]

I am building an app on Google App Engine using Flask. I am implementing Google+ login from the server-side flow described in https://developers.google.com/+/web/signin/server-side-flow. Before switching to App Engine, I had a very similar flow working. Perhaps I have introduced an error since then. Or maybe it is an issue with my implementation in App Engine.
I believe the url redirected to by the Google login flow should have a GET argument set "gplus_id", however, I am not receiving this parameter.
I have a login button created by:
(function() {
var po = document.createElement('script');
po.type = 'text/javascript'; po.async = true;
po.src = 'https://plus.google.com/js/client:plusone.js?onload=render';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();
function render() {
gapi.signin.render('gplusBtn', {
'callback': 'onSignInCallback',
'clientid': '{{ CLIENT_ID }}',
'cookiepolicy': 'single_host_origin',
'requestvisibleactions': 'http://schemas.google.com/AddActivity',
'scope': 'https://www.googleapis.com/auth/plus.login',
'accesstype': 'offline',
'width': 'iconOnly'
});
}
In the javascript code for the page I have a function to initiate the flow:
var helper = (function() {
var authResult = undefined;
return {
onSignInCallback: function(authResult) {
if (authResult['access_token']) {
// The user is signed in
this.authResult = authResult;
helper.connectServer();
} else if (authResult['error']) {
// There was an error, which means the user is not signed in.
// As an example, you can troubleshoot by writing to the console:
console.log('GPlus: There was an error: ' + authResult['error']);
}
console.log('authResult', authResult);
},
connectServer: function() {
$.ajax({
type: 'POST',
url: window.location.protocol + '//' + window.location.host + '/connect?state={{ STATE }}',
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
// After we load the Google+ API, send login data.
gapi.client.load('plus','v1',helper.otherLogin);
},
processData: false,
data: this.authResult.code,
error: function(e) {
console.log("connectServer: error: ", e);
}
});
}
}
})();
/**
* Calls the helper method that handles the authentication flow.
*
* #param {Object} authResult An Object which contains the access token and
* other authentication information.
*/
function onSignInCallback(authResult) {
helper.onSignInCallback(authResult);
}
This initiates the flow at "/connect" (See step 8. referenced in the above doc):
#app.route('/connect', methods=['GET', 'POST'])
def connect():
# Ensure that this is no request forgery going on, and that the user
# sending us this connect request is the user that was supposed to.
if request.args.get('state', '') != session.get('state', ''):
response = make_response(json.dumps('Invalid state parameter.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Normally the state would be a one-time use token, however in our
# simple case, we want a user to be able to connect and disconnect
# without reloading the page. Thus, for demonstration, we don't
# implement this best practice.
session.pop('state')
gplus_id = request.args.get('gplus_id')
code = request.data
try:
# Upgrade the authorization code into a credentials object
oauth_flow = client.flow_from_clientsecrets('client_secrets.json', scope='')
oauth_flow.redirect_uri = 'postmessage'
credentials = oauth_flow.step2_exchange(code)
except client.FlowExchangeError:
app.logger.debug("connect: Failed to upgrade the authorization code")
response = make_response(
json.dumps('Failed to upgrade the authorization code.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Check that the access token is valid.
access_token = credentials.access_token
url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s'
% access_token)
h = httplib2.Http()
result = json.loads(h.request(url, 'GET')[1])
# If there was an error in the access token info, abort.
if result.get('error') is not None:
response = make_response(json.dumps(result.get('error')), 500)
response.headers['Content-Type'] = 'application/json'
return response
# Verify that the access token is used for the intended user.
if result['user_id'] != gplus_id:
response = make_response(
json.dumps("Token's user ID doesn't match given user ID."), 401)
response.headers['Content-Type'] = 'application/json'
return response
...
However, the flow stops at if result['user_id'] != gplus_id:, saying "Token's user ID doesn't match given user ID.". result['user_id'] is a valid users ID, but gplus_id is None.
The line gplus_id = request.args.get('gplus_id') is expecting the GET args to contain 'gplus_id', but they only contain 'state'. Is this a problem with my javascript connectServer function? Should I include 'gplus_id' there? Surely I don't know it at that point. Or something else?
Similar to this question, I believe this is an issue with incomplete / not up to date / inconsistent documentation.
Where https://developers.google.com/+/web/signin/server-side-flow suggests that gplus_id will be returned in the GET arguments, this is not the case for the flow I was using.
I found my answer in https://github.com/googleplus/gplus-quickstart-python/blob/master/signin.py, which includes this snippet:
# An ID Token is a cryptographically-signed JSON object encoded in base 64.
# Normally, it is critical that you validate an ID Token before you use it,
# but since you are communicating directly with Google over an
# intermediary-free HTTPS channel and using your Client Secret to
# authenticate yourself to Google, you can be confident that the token you
# receive really comes from Google and is valid. If your server passes the
# ID Token to other components of your app, it is extremely important that
# the other components validate the token before using it.
gplus_id = credentials.id_token['sub']

How to create Google Compute Engine instance with particular service account setup on it?

When you create an instance A in Google Compute Engine, it'll get predefined, "default" service account attached to it (this basically means, that you can query google API from A, being authenticated with 'default' service account).
What I'd like to do, is to setup GCE instance with service account, that's different than a default one. This should be conceptually possible, given GCE API, but fails with exception:
{
"name": "operation-1400060483459-4f958fbc7d7b9-cd817778-b80d1cad",
"operationType": "insert",
"status": "DONE",
"user": "some_name#developer.gserviceaccount.com",
"error": {
"errors": [ {
"code": "SERVICE_ACCOUNT_ACCESS_DENIED",
"message": "The user does not have access to service account 'some_name#developer.gserviceaccount.com'"
} ] } }
Here's my code in python, which setups the instance:
discovery_service = discovery.build('compute',
config['compute_api_version'],
http=SignedJwtAssertionCredentials(
service_account_name="some_name#developer.gserviceaccount.com",
private_key=key_data,
scope='https://www.googleapis.com/auth/compute')
.authorize(httplib2.Http()))
instance = {}
# sets instance configuration details here
# ...
# ...
instance['serviceAccounts'] = [{
'email': "some_name#developer.gserviceaccount.com",
'scopes': ['https://www.googleapis.com/auth/devstorage.full_control',
'https://www.googleapis.com/auth/compute',
'https://www.googleapis.com/auth/userinfo.email', ]
}]
discovery_service.instances().insert(project=project, zone=zone, body=instance)
The weirdest part of it, is that exception says "The user does not have access to service account 'some_name#developer.gserviceaccount.com'", but the "user" it refers to is the 'some_name#developer.gserviceaccount.com' itself! Which means 'some_name#developer.gserviceaccount.com' does not have access to 'some_name#developer.gserviceaccount.com', which makes no sense.
I believe you'll need to create a new service account to use the API from a non-GCE instance. The service account you're referencing works within a GCE instance only.
To do that go to the Cloud Console > Project > APIs & Auth > Credentials.
Create new Client ID
Service Account
Download the .p12 file and load that as the private key. (See example below)
Also you'll need to create an instance from a boot disk which is typically created from one of the GCE supplied images.
Here's an example using JSON Web Tokens that worked for me. It was adapted from the docs located here: https://cloud.google.com/compute/docs/tutorials/python-guide#addinganinstance.
from apiclient import discovery
from oauth2client.file import Storage
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
import os.path
INSTANCE_NAME = 'my-instance'
API_VERSION = 'v1'
GCE_URL = 'https://www.googleapis.com/compute/%s/projects/' % (API_VERSION)
PROJECT_ID = '***'
SERVICE_ACOUNT_CLIENT_ID = '***.apps.googleusercontent.com'
SERVICE_ACCOUNT_EMAIL_ADDRESS = '***#developer.gserviceaccount.com'
GCE_SCOPE = 'https://www.googleapis.com/auth/compute'
ZONE = 'us-central1-a'
DEFAULT_SERVICE_EMAIL = 'default'
DEFAULT_SCOPES = ['https://www.googleapis.com/auth/devstorage.full_control',
'https://www.googleapis.com/auth/compute']
SOURCE_IMAGE_URL = 'projects/ubuntu-os-cloud/global/images/ubuntu-1410-utopic-v20141217'
def main():
f = file('private-key.p12', 'rb')
oauth_key_data = f.read()
f.close()
http = httplib2.Http()
oauth_storage = Storage('compute-creds.dat')
oauth_credentials = oauth_storage.get()
if oauth_credentials is None or oauth_credentials.invalid:
oauth_credentials = SignedJwtAssertionCredentials(
service_account_name=SERVICE_ACCOUNT_EMAIL_ADDRESS,
private_key=oauth_key_data,
scope=GCE_SCOPE)
oauth_storage.put(oauth_credentials)
else:
oauth_credentials.refresh(http)
http = oauth_credentials.authorize(http)
gce_service = discovery.build('compute', 'v1', http=http)
project_url = '%s%s' % (GCE_URL, PROJECT_ID)
image_url = '%s%s/global/images/%s' % (
GCE_URL, 'ubuntu-os-cloud', 'ubuntu-1410-utopic-v20141217')
machine_type_url = '%s/zones/%s/machineTypes/%s' % (
project_url, ZONE, 'n1-standard-1')
network_url = '%s/global/networks/%s' % (project_url, 'default')
instance = {
'name': INSTANCE_NAME,
'machineType': machine_type_url,
'disks': [{
'autoDelete': 'true',
'boot': 'true',
'type': 'PERSISTENT',
'initializeParams' : {
'diskName': INSTANCE_NAME,
'sourceImage': SOURCE_IMAGE_URL
}
}],
'networkInterfaces': [{
'accessConfigs': [{
'type': 'ONE_TO_ONE_NAT',
'name': 'External NAT'
}],
'network': network_url
}],
'serviceAccounts': [{
'email': DEFAULT_SERVICE_EMAIL,
'scopes': DEFAULT_SCOPES
}]
}
# Create the instance
request = gce_service.instances().insert(
project=PROJECT_ID, body=instance, zone=ZONE)
response = request.execute(http=http)
response = _blocking_call(gce_service, http, response)
print response
def _blocking_call(gce_service, auth_http, response):
"""Blocks until the operation status is done for the given operation."""
status = response['status']
while status != 'DONE' and response:
operation_id = response['name']
# Identify if this is a per-zone resource
if 'zone' in response:
zone_name = response['zone'].split('/')[-1]
request = gce_service.zoneOperations().get(
project=PROJECT_ID,
operation=operation_id,
zone=zone_name)
else:
request = gce_service.globalOperations().get(
project=PROJECT_ID, operation=operation_id)
response = request.execute(http=auth_http)
if response:
status = response['status']
return response
main()
FYI: in GCE you usually get two default service accounts:
-compute#developer.gserviceaccount.com
#cloudservices.gserviceaccount.com
Note the different Email suffix (developer.gserviceaccount.com vs. cloudservices.gserviceaccount.com). It appears that using your own service account, EVEN if it has the Owner role, does not grant you access to the <number>#cloudservices.gserviceaccount.com account, only to the 1st one (<number>-compute#developer.gserviceaccount.com).
In my case, I got the aforementioned error when trying to create an instance with my own service account while specifing that the instance will use the 2nd service account from above. Once I fixed the call to request that the instance will use the 1st account, it worked.

Categories