简体   繁体   中英

Modify Google Sheet from AWS Lambda

I'm developing a small project and I'm interested in using Google Drive for ease of use. The main premise of the program would be to insert a new row into a google sheet when the lambda function is activated. I would prefer to use Node.js for this project but am open to Java or Python.

From the tutorial site it is easy to see how this all operates. You make a request, you have OAuth, and then the program does as its told to. However, I'm looking for a way to have my AWS lambda function talk with a folder in my google drive and update a sheet at will.

The code from the tutorial is as follows:

var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');

// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/drive-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
    process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'drive-nodejs-quickstart.json';

// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
  if (err) {
    console.log('Error loading client secret file: ' + err);
    return;
  }
  // Authorize a client with the loaded credentials, then call the
  // Drive API.
  authorize(JSON.parse(content), listFiles);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 *
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  var clientSecret = credentials.installed.client_secret;
  var clientId = credentials.installed.client_id;
  var redirectUrl = credentials.installed.redirect_uris[0];
  var auth = new googleAuth();
  var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, function(err, token) {
    if (err) {
      getNewToken(oauth2Client, callback);
    } else {
      oauth2Client.credentials = JSON.parse(token);
      callback(oauth2Client);
    }
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 *
 * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback to call with the authorized
 *     client.
 */
function getNewToken(oauth2Client, callback) {
  var authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES
  });
  console.log('Authorize this app by visiting this url: ', authUrl);
  var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.question('Enter the code from that page here: ', function(code) {
    rl.close();
    oauth2Client.getToken(code, function(err, token) {
      if (err) {
        console.log('Error while trying to retrieve access token', err);
        return;
      }
      oauth2Client.credentials = token;
      storeToken(token);
      callback(oauth2Client);
    });
  });
}

/**
 * Store token to disk be used in later program executions.
 *
 * @param {Object} token The token to store to disk.
 */
function storeToken(token) {
  try {
    fs.mkdirSync(TOKEN_DIR);
  } catch (err) {
    if (err.code != 'EEXIST') {
      throw err;
    }
  }
  fs.writeFile(TOKEN_PATH, JSON.stringify(token));
  console.log('Token stored to ' + TOKEN_PATH);
}

/**
 * Lists the names and IDs of up to 10 files.
 *
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
function listFiles(auth) {
  var service = google.drive('v3');
  service.files.list({
    auth: auth,
    pageSize: 10,
    fields: "nextPageToken, files(id, name)"
  }, function(err, response) {
    if (err) {
      console.log('The API returned an error: ' + err);
      return;
    }
    var files = response.files;
    if (files.length == 0) {
      console.log('No files found.');
    } else {
      console.log('Files:');
      for (var i = 0; i < files.length; i++) {
        var file = files[i];
        console.log('%s (%s)', file.name, file.id);
      }
    }
  });
}

There has got to be some way where I can give the lambda function special, authorized, access to my google drive folder without the need of selecting an OAuth option (one gmail account over another).

Also, in the developer console, there is an option to whitelist the URLs titled Authorized JavaScript origins . Does anyone know the URL used when making a callout from AWS Lambda?

Since you are open to Python, you can use the following code:

#!/usr/bin/env python

# required layer: pip3 install --upgrade -t ./python google_auth_oauthlib google-api-python-client && zip -r9 layer.zip ./python

import sys
sys.path.append('python')
sys.path.append('../python')
import os
import gspread  # API to handle communication with google spreadsheets
import json
from oauth2client.service_account import ServiceAccountCredentials  # to authenticate
from datetime import date  # to give the desired date format
import logging


# Below for
import boto3
import base64
from botocore.exceptions import ClientError


def get_json_credentials_from_aws_secret_manager():
    secret_name = os.environ['SECRET_NAME']
    region_name = os.environ['REGION_NAME']

    # Create a Secrets Manager client
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )
    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        if e.response['Error']['Code'] == 'DecryptionFailureException':
            # Secrets Manager can't decrypt the protected secret text using the provided KMS key.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        elif e.response['Error']['Code'] == 'InternalServiceErrorException':
            # An error occurred on the server side.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        elif e.response['Error']['Code'] == 'InvalidParameterException':
            # You provided an invalid value for a parameter.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        elif e.response['Error']['Code'] == 'InvalidRequestException':
            # You provided a parameter value that is not valid for the current state of the resource.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        elif e.response['Error']['Code'] == 'ResourceNotFoundException':
            # We can't find the resource that you asked for.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
    else:
        # Decrypts secret using the associated KMS CMK.
        # Depending on whether the secret is a string or binary, one of these fields will be populated.
        if 'SecretString' in get_secret_value_response:
            secret = get_secret_value_response['SecretString']
            return secret
        else:
            decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary'])
            return (decoded_binary_secret)
    return {}

# utility function for one line code
def http_response(STATUS_CODE, DATA):
    return {
        'statusCode': STATUS_CODE,
        'body': DATA
    }


def append_users_count(sheet, users_nb=42):
    current_day = date.today().strftime('%Y-%m-%d')
    values = [current_day, users_nb]
    sheet.append_row(values, value_input_option='USER_ENTERED')
    return "Sucessfully Added Users' Count"


def display_spreadsheet(sheet):
    list_of_hashes = sheet.get_all_records()
    print(list_of_hashes)


def lambda_handler(context, event):
    JSON_CREDENTIALS = json.loads(get_json_credentials_from_aws_secret_manager())
    SCOPES = ["https://spreadsheets.google.com/feeds", 'https://www.googleapis.com/auth/spreadsheets',
             "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive"]
    try:
        credentials = ServiceAccountCredentials.from_json_keyfile_dict(JSON_CREDENTIALS, SCOPES)
        client = gspread.authorize(credentials)
        sheet = client.open(os.environ['SPREADSHEET_NAME']).sheet1
        append_users_count(sheet)
    except Exception as ex:
        error_msg = f'Could not succeed to update the google spreadsheet: {ex}'
        logging.error(error_msg)
        return http_response(e.Code, json.dumps(error_msg))

    success_msg = f"Sucessfully added count of users to google spreadsheet at url {os.environ['SPREADSHEET_URL']}"
    logging.info(success_msg)
    return http_response(200, json.dumps(success_msg))

You'll need a proxy service that has Google credential running inside. This way you don't need to ask user for authentication. The proxy service already has the credential to access. Here's the service that I use to proxy connection to Google API.

https://github.com/dnprock/gapiaccess

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM