Acione funções do Cloud Run através do Cloud Tasks

Este tutorial mostra como usar o Cloud Tasks numa aplicação do App Engine para acionar uma função do Cloud Run e enviar um email agendado.

Objetivos

  • Compreenda o código em cada um dos componentes.
  • Crie uma conta do SendGrid.
  • Transfira o código-fonte.
  • Implemente uma função do Cloud Run para receber pedidos do Cloud Tasks e enviar um email através da API SendGrid.
  • Crie uma fila do Cloud Tasks.
  • Crie uma conta de serviço para autenticar os seus pedidos do Cloud Tasks.
  • Implemente o código do cliente que permite a um utilizador enviar um email.

Custos

O Cloud Tasks, as funções do Cloud Run e o App Engine têm um nível gratuito. Por isso, desde que esteja a executar o tutorial no nível gratuito dos produtos indicados, não deve resultar em custos adicionais. Para mais informações, consulte a secção Preços.

Antes de começar

  1. Selecione ou crie um Google Cloud projeto.

    Aceda à página do App Engine

  2. Inicialize uma aplicação do App Engine no seu projeto:

    1. Na página Bem-vindo ao App Engine, clique em Criar aplicação.

    2. Selecione uma região para a sua aplicação. Esta localização vai servir como o parâmetro LOCATION_ID para os seus pedidos do Cloud Tasks, por isso, tome nota. Tenha em atenção que duas localizações, denominadas europe-west e us-central nos comandos do App Engine, são denominadas, respetivamente, europe-west1 e us-central1 nos comandos do Cloud Tasks.

    3. Selecione Node.js para o idioma e Standard para o ambiente.

    4. Se for apresentado o pop-up Ativar faturação, selecione a sua conta de faturação. Se não tiver atualmente uma conta de faturação, clique em Criar conta de faturação e siga o assistente.

    5. Na página Começar, clique em Seguinte. Vai tratar disto mais tarde.

  3. Ative as APIs Cloud Run Functions e Cloud Tasks.

    Ative as APIs

  4. Instale e inicialize a CLI gcloud.

Compreender o código

Esta secção explica o código da app e como funciona.

Criar a tarefa

A página de índice é publicada através de controladores no app.yaml. As variáveis necessárias para a criação de tarefas são transmitidas como variáveis de ambiente.

runtime: nodejs16

env_variables:
  QUEUE_NAME: "my-queue"
  QUEUE_LOCATION: "us-central1"
  FUNCTION_URL: "https://<region>-<project_id>.cloudfunctions.net/sendEmail"
  SERVICE_ACCOUNT_EMAIL: "<member>@<project_id>.iam.gserviceaccount.com"

# Handlers for serving the index page.
handlers:
  - url: /static
    static_dir: static
  - url: /
    static_files: index.html
    upload: index.html

Este código cria o ponto final /send-email. Este ponto final processa os envios de formulários da página de índice e transmite esses dados para o código de criação de tarefas.

app.post('/send-email', (req, res) => {
  // Set the task payload to the form submission.
  const {to_name, from_name, to_email, date} = req.body;
  const payload = {to_name, from_name, to_email};

  createHttpTaskWithToken(
    process.env.GOOGLE_CLOUD_PROJECT,
    QUEUE_NAME,
    QUEUE_LOCATION,
    FUNCTION_URL,
    SERVICE_ACCOUNT_EMAIL,
    payload,
    date
  );

  res.status(202).send('📫 Your postcard is in the mail! 💌');
});

Este código cria efetivamente a tarefa e envia-a para a fila do Cloud Tasks. O código cria a tarefa:

  • Especificar o tipo de destino como HTTP Request.

  • Especificar o HTTP method a usar e o URL do destino.

  • Definir o cabeçalho Content-Type como application/json para que as aplicações a jusante possam analisar a carga útil estruturada.

  • Adicionar um email de conta de serviço para que o Cloud Tasks possa fornecer credenciais ao destino do pedido, o que requer autenticação. A conta de serviço é criada separadamente.

  • Verificar se a entrada do utilizador para a data está dentro do máximo de 30 dias e adicioná-la ao pedido como campo scheduleTime.

const MAX_SCHEDULE_LIMIT = 30 * 60 * 60 * 24; // Represents 30 days in seconds.

const createHttpTaskWithToken = async function (
  project = 'my-project-id', // Your GCP Project id
  queue = 'my-queue', // Name of your Queue
  location = 'us-central1', // The GCP region of your queue
  url = 'https://example.com/taskhandler', // The full url path that the request will be sent to
  email = '<member>@<project-id>.iam.gserviceaccount.com', // Cloud IAM service account
  payload = 'Hello, World!', // The task HTTP request body
  date = new Date() // Intended date to schedule task
) {
  // Imports the Google Cloud Tasks library.
  const {v2beta3} = require('@google-cloud/tasks');

  // Instantiates a client.
  const client = new v2beta3.CloudTasksClient();

  // Construct the fully qualified queue name.
  const parent = client.queuePath(project, location, queue);

  // Convert message to buffer.
  const convertedPayload = JSON.stringify(payload);
  const body = Buffer.from(convertedPayload).toString('base64');

  const task = {
    httpRequest: {
      httpMethod: 'POST',
      url,
      oidcToken: {
        serviceAccountEmail: email,
        audience: url,
      },
      headers: {
        'Content-Type': 'application/json',
      },
      body,
    },
  };

  const convertedDate = new Date(date);
  const currentDate = new Date();

  // Schedule time can not be in the past.
  if (convertedDate < currentDate) {
    console.error('Scheduled date in the past.');
  } else if (convertedDate > currentDate) {
    const date_diff_in_seconds = (convertedDate - currentDate) / 1000;
    // Restrict schedule time to the 30 day maximum.
    if (date_diff_in_seconds > MAX_SCHEDULE_LIMIT) {
      console.error('Schedule time is over 30 day maximum.');
    }
    // Construct future date in Unix time.
    const date_in_seconds =
      Math.min(date_diff_in_seconds, MAX_SCHEDULE_LIMIT) + Date.now() / 1000;
    // Add schedule time to request in Unix time using Timestamp structure.
    // https://googleapis.dev/nodejs/tasks/latest/google.protobuf.html#.Timestamp
    task.scheduleTime = {
      seconds: date_in_seconds,
    };
  }

  try {
    // Send create task request.
    const [response] = await client.createTask({parent, task});
    console.log(`Created task ${response.name}`);
    return response.name;
  } catch (error) {
    // Construct error for Stackdriver Error Reporting
    console.error(Error(error.message));
  }
};

module.exports = createHttpTaskWithToken;

Criar o email

Este código cria a função do Cloud Run que é o destino do pedido do Cloud Tasks. Usa o corpo do pedido para criar um email e enviá-lo através da API SendGrid.

const sendgrid = require('@sendgrid/mail');

/**
 * Responds to an HTTP request from Cloud Tasks and sends an email using data
 * from the request body.
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to_email Email address of the recipient.
 * @param {string} req.body.to_name Name of the recipient.
 * @param {string} req.body.from_name Name of the sender.
 * @param {object} res Cloud Function response context.
 */
exports.sendEmail = async (req, res) => {
  // Get the SendGrid API key from the environment variable.
  const key = process.env.SENDGRID_API_KEY;
  if (!key) {