Usa Cloud Tasks para activar Cloud Functions

En este instructivo, se muestra cómo usar Cloud Tasks dentro de una aplicación de Google App Engine para activar una función de Cloud Functions y enviar un correo electrónico programado.

Objetivos

  • Comprende el código en cada uno de los componentes.
  • Crear una cuenta de SendGrid
  • Descargue el código fuente
  • Implementa una función de Cloud para recibir solicitudes de Cloud Tasks y enviar un correo electrónico a través de la API de SendGrid.
  • Crea una cola de Cloud Tasks.
  • Crea una cuenta de servicio para autenticar tus solicitudes de Cloud Tasks.
  • Implementa el código de cliente que permite a un usuario enviar un correo electrónico.

Costos

Cloud Tasks, Cloud Functions y App Engine tienen un nivel gratuito, por lo que, siempre que ejecutes el instructivo dentro del nivel gratuito de los productos determinados, no deberías generar costos adicionales. Para obtener más información, consulta Precios.

Antes de comenzar

  1. Selecciona o crea un proyecto de GCP.

    Ir a la página App Engine

  2. Inicializa una aplicación de App Engine en tu proyecto:

    1. En la página Bienvenido a App Engine, haz clic en Crear aplicación.

    2. Selecciona una región para tu aplicación. Esta ubicación servirá como parámetro LOCATION_ID para tus solicitudes de Cloud Tasks, así que anótala. Ten en cuenta que hay dos ubicaciones, llamadas europe-west y us-central en los comandos de App Engine, que se llaman europe-west1 y us-central1 en los comandos de Cloud Tasks.

    3. Selecciona Node.js para el idioma y Standard para el entorno.

    4. Si aparece la ventana emergente Habilitar facturación, selecciona tu cuenta de facturación. Si actualmente no tienes una cuenta de facturación, haz clic en Crear cuenta de facturación y sigue el asistente.

    5. En la página Comenzar, haz clic en Siguiente. Te encargarás de esto más tarde.

  3. Habilita las API de Cloud Functions y Cloud Tasks.

    Habilita las API

  4. Instala y, luego, inicializa el SDK de Cloud.

Comprende el código

En esta sección, se explica el código de la app y su funcionamiento.

Crea la tarea

La página de índice se entrega con controladores en app.yaml. Las variables necesarias para la creación de tareas se pasan como variables de entorno.

runtime: nodejs10

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 crea el extremo /send-email. Este extremo maneja los envíos de formularios desde la página de índice y pasa esos datos al código de creación de tareas.

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 crea la tarea y la envía a la cola de Cloud Tasks. El código compila la tarea de la siguiente manera:

  • Especifica el tipo de destino como HTTP Request.

  • Especifica el HTTP method que se usará y el URL del destino.

  • Configurar el encabezado Content-Type en application/json para que las aplicaciones posteriores puedan analizar la carga útil estructurada.

  • Agregar un correo electrónico de cuenta de servicio para que Cloud Tasks pueda proporcionar credenciales al destino de la solicitud, lo que requiere autenticación. La cuenta de servicio se crea por separado.

  • Verifica que la entrada del usuario para la fecha esté dentro del máximo de 30 días y agrega a la solicitud 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,
      },
      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;

Crea el correo electrónico

Este código crea la función de Cloud Functions que es el destino de la solicitud de Cloud Task. Usa el cuerpo de la solicitud para construir un correo electrónico y enviarlo a través de la API de 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) {
    const error = new Error(
      'SENDGRID_API_KEY was not provided as environment variable.'
    );
    error.code = 401;
    throw error;
  }
  sendgrid.setApiKey(key);

  // Get the body from the Cloud Task request.
  const {to_email, to_name, from_name} = req.body;
  if (!to_email) {
    const error = new Error('Email address not provided.');
    error.code = 400;
    throw error;
  } else if (!to_name) {
    const error = new Error('Recipient name not provided.');
    error.code = 400;
    throw error;
  } else if (!from_name) {
    const error = new Error('Sender name not provided.');
    error.code = 400;
    throw error;
  }

  // Construct the email request.
  const msg = {
    to: to_email,
    from: 'postcard@example.com',
    subject: 'A Postcard Just for You!',
    html: postcardHTML(to_name, from_name),
  };

  try {
    await sendgrid.send(msg);
    // Send OK to Cloud Task queue to delete task.
    res.status(200).send('Postcard Sent!');
  } catch (error) {
    // Any status code other than 2xx or 503 will trigger the task to retry.
    res.status(error.code).send(error.message);
  }
};

Prepara la aplicación

Configura SendGrid

  1. Crear una cuenta de SendGrid

  2. Crea una clave de API de SendGrid:

    1. Accede a tu cuenta de SendGrid.

    2. En la barra de navegación izquierda, abre Configuración y haz clic en Claves de API.

    3. Haz clic en Crear clave de API y selecciona el acceso restringido. En el encabezado Enviar correo electrónico, selecciona Acceso completo.

    4. Copia la clave de API cuando se muestre (solo verás esto una vez, asegúrate de pegarla en algún lugar para poder usarla más adelante).

Descarga el código fuente

  1. Clona el repositorio de la app de muestra en tu máquina local:

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
    
  2. Ve al directorio que contiene el código de muestra:

    cd cloud-tasks/
    

Implementa la función de Cloud Functions

  1. Navega al directorio function/.

    cd function/
    
  2. Implementa la función y reemplaza sendgrid_api_key por tu clave:

    gcloud functions deploy sendEmail --runtime nodejs8 --trigger-http --set-env-vars SENDGRID_API_KEY=sendgrid_api_key
    

    Este comando usa marcas:

    • --trigger-http para especificar el tipo de activador de Cloud Functions.

    • --set-env-var para establecer tus credenciales de SendGrid

  3. Configura el control de acceso para la función a fin de permitir solo usuarios autenticados.

    1. Selecciona la función sendEmail en la IU de Cloud Functions.

    2. Si no ves la información de permisos para sendEmail, haz clic en MOSTRAR PANEL DE INFORMACIÓN en la esquina superior derecha.

    3. En el encabezado Invocador de Cloud Functions, borra el miembro allUsers.

    4. Haz clic en el botón Agregar miembros que se encuentra arriba.

    5. Establece Nuevos miembros en allAuthenticatedUsers.

    6. Establece la función en Cloud Function Invoker.

    7. Haz clic en GUARDAR.

Crea una cola de Cloud Tasks

  1. Crea una cola con el siguiente comando de gcloud:

    gcloud tasks queues create my-queue
    
  2. Verifica que se haya creado correctamente:

    gcloud tasks queues describe my-queue
    

Crea una cuenta de servicio

La solicitud de Cloud Tasks debe proporcionar credenciales en el encabezado Authorization para que Cloud Function autentique la solicitud. Esta cuenta de servicio permite que Cloud Tasks cree y agregue un token OIDC para ese fin.

  1. En IAM y administración | IU de cuentas de servicio, haz clic en + CREAR CUENTA DE SERVICIO.

  2. Agrega un nombre de cuenta de servicio (nombre visible descriptivo) y selecciona Crear.

  3. Selecciona la función Invocador de Cloud Functions y haz clic en Continuar.

  4. Seleccione Listo.

Implementa el extremo y el creador de tareas en App Engine

  1. Navega al directorio app/:

    cd ../app/
    
  2. Actualiza las variables en app.yaml con tus valores:

    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"

    Para encontrar la ubicación de la cola, usa el siguiente comando:

    gcloud tasks queues describe my-queue
    

    Para encontrar la URL de tu función, usa el siguiente comando:

    gcloud functions describe sendEmail
    
  3. Implementa la aplicación en el entorno estándar de App Engine con el siguiente comando:

    gcloud app deploy
    
  4. Abre la aplicación para enviar una postal como un correo electrónico:

    gcloud app browse
    

Limpia

Una vez que hayas terminado el instructivo, puedes limpiar los recursos que creaste en Google Cloud para que no ocupen la cuota y no se te cobre en el futuro. En las siguientes secciones, se describe cómo borrar o desactivar estos recursos.

Borrar recursos

Una vez que completes el instructivo, puedes limpiar los recursos que creaste en GCP para que no consuman tu cuota y no se facturen en el futuro. En las secciones siguientes, se describe cómo borrar o desactivar estos recursos.

Borra la función de Cloud Functions

  1. Ve a la página de Cloud Functions en Cloud Console.

    Ir a la página de Cloud Functions

  2. Haz clic en las casillas de verificación junto a tus funciones.

  3. Haz clic en el botón Borrar en la parte superior de la página y confirma la eliminación.

Borra la cola de Cloud Task

  1. Abre la página de listas de tareas en cola de Cloud Tasks en Console.

    Ir a la página de listas de tareas en cola de Cloud Tasks

  2. Selecciona la cola que deseas borrar y haz clic en Borrar cola.

  3. Confirma la acción.

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, sigue estos pasos:

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a la página Administrar recursos

  2. En la lista de proyectos, selecciona el proyecto que deseas borrar y haz clic en Borrar .
  3. En el cuadro de diálogo, escribe el ID del proyecto y haz clic en Cerrar para borrar el proyecto.

Qué sigue