OCI – Integração Object Storage com Functions

Um dos recursos que o Object Storage nos fornece é disparar eventos quando ocorre uma interação com ele, nesse artigo mostro como usar essa funcionalidade com o OCI Functions para gerar uma PAR e enviar um e-mail quando um objeto é enviado/alterado, além disso também vamos usar o custom metadata.

Configuração do bucket

Ao criar o Bucket, marque a opção Emit Object Events.

Custom metadata

Antes de criar a FN, um dos pontos que levei em consideração foi como permitir ao usuário passar como parâmetro para ela o endereço de destino e o tempo que o PAR seria válido, a opção mais lógica seria o uso de tags, mas elas não estão disponíveis em nível de Objeto (apenas a nível de bucket), então a saída foi usar metada customizado:

Via oci cli você pode usar o parâmetro –metadata

Function

A function tem 3 partes:

  • create_PAR, essa função recebe como parâmetro o signer, nome do bucket, nome do PAR, o tempo de validade dele e o nome do objeto, ela retorna o PAR gerado.
  • getMetadata, essa é a função que recupera os metadados do objeto e alimenta o envio de e-mail
  • handler, essa função é chamada pelo Evento, chama as outras duas e envia o email
import io
import json
from pickle import OBJ
from fdk import response
import os
import oci
import oci.object_storage
from datetime import datetime, timedelta
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText






def create_PAR(signer, bucket_name, PAR_name, lifetime,OBJ_name):
    client = oci.object_storage.ObjectStorageClient(config={}, signer=signer)
    namespace = client.get_namespace().data
    par_expiration = datetime.utcnow() + timedelta(minutes=lifetime)
    object_storage_endpoint = "https://objectstorage." + signer.region + ".oraclecloud.com"
    par_details = oci.object_storage.models.CreatePreauthenticatedRequestDetails(name=PAR_name, access_type='ObjectRead', time_expires=par_expiration,object_name=OBJ_name)
    par = client.create_preauthenticated_request(namespace_name=namespace, bucket_name=bucket_name, create_preauthenticated_request_details=par_details)
    par_url = object_storage_endpoint + par.data.access_uri
    print (par_url)
    return par_url


def getMetadata(signer, bucket_name,OBJ_name):
 
        signer = oci.auth.signers.get_resource_principals_signer()
        client = oci.object_storage.ObjectStorageClient(config={}, signer=signer)
        namespace = client.get_namespace().data
        meta = client.head_object(namespace_name=namespace, bucket_name=bucket_name, object_name=OBJ_name)
        #print(meta.headers['opc-meta-email'])
        return meta.headers





def handler(ctx, data: io.BytesIO = None):

    resp = None
    #f = open('/home/opc/fn-obj/teste.json')

    try:
        
        #body = json.load(f)
        body = json.loads(data.getvalue())
       
        bucket_name = body["data"]["additionalDetails"]["bucketName"]
        PAR_name =  "par_" + body["data"]["resourceName"] 
        OBJ_name = body["data"]["resourceName"] 
        



        print (bucket_name)
        print (PAR_name)
        
        print (OBJ_name)



        signer = oci.auth.signers.get_resource_principals_signer()
        emailMetadata = getMetadata(signer, bucket_name,OBJ_name)
        #print (emailMetadata)
        #print (emailMetadata['opc-meta-tempopar'])
        if 'opc-meta-tempopar' in emailMetadata:
            lifetime = int(emailMetadata['opc-meta-tempopar'])
        else:
            lifetime=10
        resp = create_PAR(signer, bucket_name, PAR_name, lifetime,OBJ_name)
        
        validade = datetime.utcnow() + timedelta(minutes=lifetime)
       
        mail_content = "Ola, seu PAR para o arquivo " + OBJ_name + " no bucket " +bucket_name+" é o seguinte: \n" + resp + "\nValido até " + str(validade)
        print (mail_content)

 
        #The mail addresses and password
        sender_address = 'EMAIL_ORIGEM'
        sender_pass = 'SENHA'
        receiver_address = emailMetadata['opc-meta-emailto']
        #Setup the MIME
        message = MIMEMultipart()
        message['From'] = sender_address
        message['To'] = receiver_address
        message['Subject'] = 'PAR ' + OBJ_name   #The subject line
        #The body and the attachments for the mail
        message.attach(MIMEText(mail_content, 'plain'))
        #Create SMTP session for sending the mail
        session = smtplib.SMTP('smtp.gmail.com', 587) #use gmail with port
        session.starttls() #enable security
        session.login(sender_address, sender_pass) #login with mail_id and password
        text = message.as_string()
        session.sendmail(sender_address, receiver_address, text)
        session.quit()
        print('Mail Sent')



        return response.Response(
          ctx, response_data=json.dumps(resp),
            headers={"Content-Type": "application/json"}
        )
    except (Exception, ValueError) as e:
        print("Error " + str(e), flush=True)

Comentários:

  • Se a pessoa não entrar o metadado tempopar o PAR vai ter 10 minutos por padrão
  • Ajuste os valores sender_address e sender_pass, nesse exemplo estou usando um gmail como origem mas você pode alterar o servidor de e-mail no session
  • Caso também use gmail, você precisa criar uma senha do tipo APP nas configurações de segurança

Deploy da function

Crie um app para receber sua função:

Siga o Getting Started que ele vai te guiar na criação dos recursos básicos para a fn funcionar, aqui usei a modalidade Cloud Shell

Após isso, coloque o código da função junto com o arquivo yaml de informações e o requirements.txt dentro de um diretório do seu Cloud Shell e faça o deploy:

Dica, nesse momento você pode ativar a opção de log da função para acompanhar o que está acontecendo nela.

Events

Vamos criar uma Rule que vai monitorar os eventos que ocorrem no Bucket, vá em Observability & Management -> Events Service -> Rules

Aqui no meu ambiente, estou monitorando criação (Object – Create) e atualização (Object Update), além disso também coloquei como condição apenas eventos que ocorram no Bucket com nome bucket-fast:

Como ação, coloquei um tópico do tipo e-mail (Tnk-OGG) para validar se o evento está sendo disparado(você pode remover essa ação depois de validar a FN) e a FN que criamos:

Testando

Agora que já temos todos os recursos provisionados, vamos enviar um objeto e colocar os metadados necessários:

Via console, basta cliar em Advanced e entrar os metadados emailto(quem vai receber o e-mail com o PAR) e tempopar (quanto tempo o PAR vai ser válido)

Via oci cli

oci os object put -ns xxxxxx -bn bucket-fast --file history.log --metadata 
'{"emailto":"adriano.tanaka@oracle.com","tempopar":"25"}'

Rule

Aqui podemos ver que a rule foi disparada (Rule has matched event) e que ela executou nossas duas ações.

PAR

E-mail recebido

chevron_left