ПР. Создание триггера от Object Storage

Кратко:

  • Создание триггера на Object Storage с использованием Python и boto3.
  • Создание ключа доступа и секретного ключа для доступа к Object Storage.
  • Выбор способа создания бакета в Object Storage: через консоль управления или с помощью Terraform.
  • Модификация функции my-first-function и загрузка новой версии в облако.
  • Создание триггера my-first-trigger для вызова функции my-trigger-function при создании объекта в бакете BUCKET_NAME.
  • Вызов цепочки событий и запись содержимого переменной event в логи функции my-trigger-function.

Практическая работа. Создание триггера от Object Storage

В предыдущем практическом уроке вы познакомились с созданием одной функции с помощью интерфейса командной строки (yc). В этом уроке мы продолжим разработку этой функции: модифицируем её содержание, добавим переменные окружения и т.д.
Важно выполнить предыдущий практический урок, так как вы будете опираться на знания и результаты, полученные в нём.

Шаг 1. Модификация сервисного аккаунта

Добавление роли сервисному аккаунту
По итогам прохождения предыдущей практической работы у вас есть сервисный аккаунт с именем service-account-for-cf. Для работы с Object Storage добавьте этому сервисному аккаунту роль storage.editor:
yc resource-manager folder add-access-binding $FOLDER_ID \
    --role storage.editor \
    --subject serviceAccount:$SERVICE_ACCOUNT_ID
 
Создание ключа доступа для сервисного аккаунта
Этот этап нужен для получения идентификатора ключа доступа и секретного ключа, которые будут использованы для загрузки файлов в Object Storage, а также в том случае, если на следующем шаге для создания бакета в Object Storage вы планируете использовать Terraform.
Для создания ключа доступа необходимо вызвать следующую команду:
yc iam access-key create --service-account-name service-account-for-cf
В результате вы получите примерно следующее:
access_key:
    id: ajefraollq5puj2tir1o
    service_account_id: ajetdv28pl0a1a8r41f0
    created_at: "2021-08-23T21:13:05.677319393Z"
    key_id: BTPNvWthv0ZX2xVmlPIU
secret: cWLQ0HrTM0k_qAac43cwMNJA8VV_rfTg_kd4xVPi
Где:
  • key_id — идентификатор ключа доступа, ACCESS_KEY.
  • secret — секретный ключ, SECRET_KEY.
Переменные ACCESS_KEY и SECRET_KEY будут использованы для задания соответствующих значений aws_access_key_id и aws_secret_access_key при использовании библиотеки boto3 на следующих этапах.

Шаг 2. Object Storage

Самый простой способ создания бакета в Object Storage — через консоль управления. Более сложный, позволяющий автоматизировать разработку, — использование Terraform. Вы можете выбрать любой из них.
Способ 1. Консоль управления
В консоли управления в вашем рабочем каталоге выберите сервис Object Storage. Нажмите кнопку Создать бакет.
На странице создания бакета:
  1. Введите имя бакета, пусть это будет bucket-for-trigger.
  2. При необходимости ограничьте максимальный размер бакета, установив значение, например, 1 ГБ.
  3. Выберите тип доступа, в нашем уроке установим значения в Публичный во всех случаях.
  4. Выберите класс хранилища, по умолчанию используется Стандартное.
Нажмите кнопку Создать бакет для завершения операции. Далее вы всегда сможете поменять класс хранилища, его размер и настройки доступа.
image
Способ 2. Terraform
Прежде всего необходимо получить OAuth-токен для работы с Yandex Cloud. Для этого можно сделать запрос к сервису Яндекс.OAuth. Подробнее прочитать можно в документации.
Сохраните OAuth-токен в переменную OAuth, но никому не передавайте. Также вам потребуются значения переменных: идентификатор облака — CLOUD_ID и идентификатор каталога FOLDER_ID (сохранен в переменную ранее).
Также на предыдущем шаге вы получили ключ доступа для сервисного аккаунта. Нам потребуется идентификатор ключа доступа ACCESS_KEY и секретный ключ SECRET_KEY.
В файл main.tf, представленный далее, внесём все собранные переменные. Важно: переменная BUCKET_NAME содержит имя создаваемого бакета в Object Storage, куда будем загружать файлы. Допустим, переменная будет равна bucket-for-trigger. Сохраним все значения:
terraform {
  required_providers {
    yandex = {
      source = "yandex-cloud/yandex"
    }
  }
  required_version = ">= 0.13"
}

provider "yandex" {
  token     = "<OAuth>"
  cloud_id  = "<CLOUD_ID>"
  folder_id = "<FOLDER_ID>"
}

resource "yandex_storage_bucket" "bucket" {
  access_key = "<ACCESS_KEY>"
  secret_key = "<SECRET_KEY>"
  bucket = "<BUCKET_NAME>"
}
После внесения правок, находясь в каталоге с файлом main.tf, последовательно выполните следующие команды:
terraform init
terraform plan
terraform apply
Успешное выполнение команд приведёт к созданию бакета bucket-for-trigger в объектном хранилище в вашем рабочем каталоге.

Шаг 3. Модификация функции

В предыдущей практической работе мы создали функцию с именем my-first-function с помощью следующей команды:
yc serverless function create --name my-first-function
При создании функции вы получили URL, по которому можно будет сделать вызов функции http_invoke_url.
Загрузка кода новой версии
Новая версия функции имеет зависимости, которые описаны в файле requirements.txt, а это значит, что для загрузки функции в облако необходимо файлы index.py и requirements.txt заархивировать и получить файл my-first-function.zip.
Новая версия index.py:
import os
import datetime
import boto3
import pytz

ACCESS_KEY = os.getenv("ACCESS_KEY")
SECRET_KEY = os.getenv("SECRET_KEY")
BUCKET_NAME = os.getenv("BUCKET_NAME")
TIME_ZONE = os.getenv("TIME_ZONE", "Europe/Moscow")

TEMP_FILENAME = "/tmp/temp_file"
TEXT_FOR_TEMP_FILE = "This is text file"

def write_temp_file():
    temp_file = open(TEMP_FILENAME, 'w')
    temp_file.write(TEXT_FOR_TEMP_FILE)
    temp_file.close()
    print("\U0001f680 Temp file is written")

def get_now_datetime_str():
    now = datetime.datetime.now(pytz.timezone(TIME_ZONE))    
    return now.strftime('%Y-%m-%d__%H-%M-%S')

def get_s3_instance():
    session = boto3.session.Session()
    return session.client(
        aws_access_key_id=ACCESS_KEY,
        aws_secret_access_key=SECRET_KEY,
        service_name='s3',
        endpoint_url='https://storage.yandexcloud.net'
    )

def upload_dump_to_s3():
    print("\U0001F4C2 Starting upload to Object Storage")
    get_s3_instance().upload_file(
        Filename=TEMP_FILENAME,
        Bucket=BUCKET_NAME,
        Key=f'file-{get_now_datetime_str()}.txt'
    )
    print("\U0001f680 Uploaded")


def remove_temp_files():
    os.remove(TEMP_FILENAME)
    print("\U0001F44D That's all!")

def handler(event, context):
    write_temp_file()
    upload_dump_to_s3()
    remove_temp_files()
    return {
        'statusCode': 200,
        'body': 'File is uploaded',
    }
Первая версия requirements.txt:
boto3==1.13.10
botocore==1.16.10
python-dateutil==2.8.1
pytz==2020.1
Находясь в каталоге с файлом my-first-function.zip вызовите следующую команду, это позволит вам загрузить код функции в облако и создать её версию:
yc serverless function version create \
  --function-name my-first-function \
  --memory 256m \
  --execution-timeout 5s \
  --runtime python37 \
  --entrypoint index.handler \
  --service-account-id $SERVICE_ACCOUNT_ID \
  --source-path my-first-function.zip
Новая версия функции при вызове будет загружать в Object Storage новый файл. Для создания этой версии необходимо подготовить несколько переменных. Переменные ACCESS_KEY и SECRET_KEY вы получили на первом шаге, а значение BUCKET_NAME на втором:
echo "export ACCESS_KEY=<ACCESS_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export SECRET_KEY=<SECRET_KEY>" >> ~/.bashrc && . ~/.bashrc
echo "export BUCKET_NAME=bucket-for-trigger" >> ~/.bashrc && . ~/.bashrc
Определим идентификатор (ID) для последней загруженной версии функции:
yc serverless function version list --function-name my-first-function
Создадим новую версию функции, задав при этом переменные окружения. Для этого выставим значение параметра source-version-id равное полученному ID в следующей команде:
yc serverless function version create \
  --function-name my-first-function \
  --memory 256m \
  --execution-timeout 5s \
  --runtime python37 \
  --entrypoint index.handler \
  --service-account-id $SERVICE_ACCOUNT_ID \
  --source-version-id <ID> \
  --environment ACCESS_KEY=$ACCESS_KEY \
  --environment SECRET_KEY=$SECRET_KEY \
  --environment BUCKET_NAME=$BUCKET_NAME
Успешное выполнение команды приведёт к созданию версии функции.
Вызов функции
Получите список функций и информацию о функции my-first-function:
yc serverless function list

yc serverless function version list --function-name my-first-function
В результате вызова последней команды в столбце FUNCTION ID вы узнаете идентификатор функции и сможете сделать вызов функции с помощью следующей команды:
yc serverless function invoke <идентификатор_функции>
В предыдущей практической работе мы сделали функцию my-first-function публичной с помощью команды:
yc serverless function allow-unauthenticated-invoke my-first-function
Теперь мы можем сделать её вызов в браузере. Получите параметр http_invoke_url для функции my-first-function
yc serverless function get my-first-function
Введите значение параметра http_invoke_url в браузере и наслаждайтесь вызовом вашей функции. Во время её вызова в Object Storage будет создан новый файл.

Шаг 4. Создание триггера

Создание функции
Для создания триггера нам необходима функция, которую триггер будет запускать. Аналогично предыдущему шагу создадим функцию my-trigger-function и её версию на основе файла index.py.
def handler(event, context):
    print("\U0001F4C2 Starting function after trigger")
    print(event)     
    return {
        'statusCode': 200,
        'body': 'File is uploaded',
    }
Находясь в каталоге с файлом index.py, вызовите следующие команды:
yc serverless function create --name my-trigger-function

yc serverless function version create \
  --function-name my-trigger-function \
  --memory 256m \
  --execution-timeout 5s \
  --runtime python37 \
  --entrypoint index.handler \
  --service-account-id $SERVICE_ACCOUNT_ID \
  --source-path index.py

yc serverless function version list --function-name my-trigger-function
Создание триггера
Чтобы создать триггер my-first-trigger, который вызывает функцию my-trigger-function при создании нового объекта в бакете BUCKET_NAME, выполните команду:
yc serverless trigger create object-storage \
  --name my-first-trigger \
  --bucket-id $BUCKET_NAME \
  --events 'create-object' \
  --invoke-function-name my-trigger-function \
  --invoke-function-service-account-id $SERVICE_ACCOUNT_ID
 
Вызов цепочки событий
Чтобы запустить цепочку событий, вызовем первую функцию my-first-function. Получите список функций и информацию о функции my-first-function:
yc serverless function list
yc serverless function version list --function-name my-first-function
В результате вызова последней команды в столбце FUNCTION ID вы узнаете идентификатор функции и сможете сделать вызов функции с помощью команды:
yc serverless function invoke <идентификатор_функции>
После этого вы можете сделать её вызов в браузере. Получите параметр http_invoke_url для функции my-first-function
yc serverless function get my-first-function

 

Введите значение параметра http_invoke_url в браузере. Во время вызова функции в Object Storage будет создан новый объект. Сразу после этого сработает триггер my-first-trigger, который вызовет функцию my-trigger-function. В итоге, наша вторая функция запишет в логи содержание переменной event. Убедиться в этом вы сможете как в UI, так и через CLI.
yc serverless function logs my-trigger-function
На следующем практическом занятии мы создадим навык Алисы с помощью Cloud Functions.