简体   繁体   English

从 AWS Lambda 修改 Google Sheet

[英]Modify Google Sheet from AWS Lambda

I'm developing a small project and I'm interested in using Google Drive for ease of use.我正在开发一个小项目,我对使用 Google Drive 的易用性感兴趣。 The main premise of the program would be to insert a new row into a google sheet when the lambda function is activated.该程序的主要前提是在激活 lambda 函数时在谷歌表中插入一个新行。 I would prefer to use Node.js for this project but am open to Java or Python.我更愿意在这个项目中使用 Node.js,但我对 Java 或 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.你提出一个请求,你有 OAuth,然后程序按照它的指示去做。 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.但是,我正在寻找一种方法让我的 AWS lambda 函数与我的谷歌驱动器中的文件夹进行对话并随意更新工作表。

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).必须有某种方式让我可以为 lambda 函数提供特殊的、授权的、访问我的谷歌驱动器文件夹的权限,而无需选择 OAuth 选项(一个 Gmail 帐户而不是另一个)。

Also, in the developer console, there is an option to whitelist the URLs titled Authorized JavaScript origins .此外,在开发者控制台中,有一个选项可以将标题为Authorized JavaScript origins的 URL 列入白名单。 Does anyone know the URL used when making a callout from AWS Lambda?有谁知道从 AWS Lambda 进行调出时使用的 URL?

Since you are open to Python, you can use the following code:由于您对 Python 开放,您可以使用以下代码:

#!/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.您需要一个在内部运行 Google 凭据的代理服务。 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.这是我用来代理与 Google API 的连接的服务。

https://github.com/dnprock/gapiaccess https://github.com/dnprock/gapiaccess

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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