Muitos clientes do Looker querem capacitar os usuários a ir além da geração de relatórios sobre dados no data warehouse e, na verdade, gravar e atualizar esse data warehouse.
Com a API Action, o Looker oferece suporte a esse caso de uso para qualquer data warehouse ou destino. Esta página de documentação orienta os clientes que usam a infraestrutura Google Cloud a implantar uma solução no Cloud Run functions para gravar no BigQuery. Nesta página, abordamos os seguintes tópicos:
Considerações sobre a solução
Use esta lista de considerações para validar se essa solução atende às suas necessidades.
- Funções do Cloud Run
- Por que usar as funções do Cloud Run? Como a oferta "sem servidor" do Google, as funções do Cloud Run são uma ótima opção para facilitar as operações e a manutenção. Uma consideração a ser lembrada é que a latência, principalmente para invocações a frio, pode ser maior do que com uma solução que depende de um servidor dedicado.
- Linguagem e ambiente de execução : as funções do Cloud Run oferecem suporte a várias linguagens e ambientes de execução. Esta página de documentação se concentra em um exemplo em JavaScript e Node.js. No entanto, os conceitos podem ser traduzidos diretamente para as outras linguagens e ambientes de execução com suporte.
- BigQuery
- Por que o BigQuery? Embora esta página de documentação suponha que você já esteja usando o BigQuery, ele é uma ótima opção para um data warehouse em geral. Lembre-se das seguintes considerações:
- API BigQuery Storage Write:o BigQuery oferece várias interfaces para atualizar dados no data warehouse, incluindo, por exemplo, instruções da linguagem de manipulação de dados (DML) em jobs baseados em SQL. No entanto, a melhor opção para gravações de alto volume é a API BigQuery Storage Write.
- Anexar em vez de atualizar:mesmo que essa solução apenas anexe linhas, e não as atualize, você sempre poderá derivar tabelas de "estado atual" no momento da consulta de um registro somente de anexação, simulando atualizações.
- Por que o BigQuery? Embora esta página de documentação suponha que você já esteja usando o BigQuery, ele é uma ótima opção para um data warehouse em geral. Lembre-se das seguintes considerações:
- Serviços de suporte
- Secret Manager: Secret Manager contém valores secretos para garantir que eles não sejam armazenados em lugares muito acessíveis, como diretamente na configuração da função.
- Identity and Access Management (IAM): o IAM autoriza a função a acessar o secret necessário no ambiente de execução e a gravar na tabela do BigQuery pretendida.
- Cloud Build:embora o Cloud Build não seja discutido em detalhes nesta página, as funções do Cloud Run o usam em segundo plano, e você pode usar o Cloud Build para automatizar atualizações implantadas continuamente nas funções a partir de mudanças no código-fonte em um repositório Git.
- Autenticação de ações e usuários
- Conta de serviço do Cloud Run : a maneira principal e mais fácil de usar as ações do Looker para integração com os recursos próprios da sua organização é autenticar as solicitações como provenientes da instância do Looker usando o mecanismo de autenticação baseado em token da API Action do Looker e, em seguida, autorizar a função a atualizar dados no BigQuery usando uma conta de serviço.
- OAuth:outra opção, não abordada nesta página, seria usar o recurso OAuth da API Action do Looker. Essa abordagem é mais complexa e geralmente não é necessária, mas pode ser usada se você precisar definir o acesso dos usuários finais para gravar na tabela usando o IAM, em vez de usar o acesso deles no Looker ou a lógica ad hoc no código da função.
Tutorial do código de demonstração
Temos um único arquivo contendo toda a lógica da nossa ação de demonstração disponível no GitHub. Nesta seção, vamos explicar os principais elementos do código.
Código de configuração
A primeira seção tem algumas constantes de demonstração que identificam a tabela em que a ação será gravada. Na seção Guia de implantação, mais adiante nesta página, você receberá instruções para substituir o ID do projeto pelo seu, que será a única modificação necessária no código.
/*** Demo constants */
const projectId = "your-project-id"
const datasetId = "demo_dataset"
const tableId = "demo_table"
A próxima seção declara e inicializa algumas dependências de código que sua ação vai usar. Fornecemos um exemplo que acessa o Secret Manager "no código" usando o módulo Node.js do Secret Manager. No entanto, também é possível eliminar essa dependência de código usando o recurso integrado das funções do Cloud Run para recuperar um secret durante a inicialização.
/*** Code Dependencies ***/
const crypto = require("crypto")
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager')
const secrets = new SecretManagerServiceClient()
const BigqueryStorage = require('@google-cloud/bigquery-storage')
const BQSManagedWriter = BigqueryStorage.managedwriter
Observe que as dependências referenciadas @google-cloud também são declaradas no arquivo package.json para permitir que as dependências sejam pré-carregadas e disponibilizadas para o ambiente de execução do Node.js. crypto é um módulo integrado do Node.js e não é declarado em package.json.
Processamento e roteamento de solicitações HTTP
A principal interface que o código expõe ao ambiente de execução das funções do Cloud Run é uma função JavaScript exportada que segue as convenções do servidor da Web Node.js Express. Em particular, a função recebe dois argumentos: o primeiro representa a solicitação HTTP, da qual você pode ler vários parâmetros e valores de solicitação; e o segundo representa um objeto de resposta, ao qual você emite os dados de resposta. Embora o nome da função possa ser qualquer um, você precisará fornecer o nome para as funções do Cloud Run mais tarde, conforme detalhado na seção Guia de implantação.
/*** Entry-point for requests ***/
exports.httpHandler = async function httpHandler(req,res) {
A primeira seção da função httpHandler declara as várias rotas que nossa ação vai reconhecer, espelhando de perto os endpoints necessários da API Action para uma única ação e as funções que vão processar cada rota, definidas mais adiante no arquivo.
Embora alguns exemplos de ações + funções do Cloud Run implantem uma função separada para cada rota para se alinhar individualmente ao roteamento padrão das funções do Cloud Run, as funções são capazes de aplicar "sub-roteamento" adicional no código, conforme demonstrado aqui. Isso é uma questão de preferência, mas fazer esse roteamento adicional no código minimiza o número de funções que precisamos implantar e nos ajuda a manter um único estado de código coerente em todos os endpoints das ações.
const routes = {
"/": [hubListing],
"/status": [hubStatus], // Debugging endpoint. Not required.
"/action-0/form": [
requireInstanceAuth,
action0Form
],
"/action-0/execute": [
requireInstanceAuth,
processRequestBody,
action0Execute
]
}
O restante da função do gerenciador HTTP implementa o processamento da solicitação HTTP em relação às declarações de rota anteriores e conecta os valores de retorno desses gerenciadores ao objeto de resposta.
try {
const routeHandlerSequence = routes[req.path] || [routeNotFound]
for(let handler of routeHandlerSequence) {
let handlerResponse = await handler(req)
if (!handlerResponse) continue
return res
.status(handlerResponse.status || 200)
.json(handlerResponse.body || handlerResponse)
}
}
catch(err) {
console.error(err)
res.status(500).json("Unhandled error. See logs for details.")
}
}
Com o gerenciador HTTP e as declarações de rota fora do caminho, vamos analisar os três principais endpoints de ação que precisamos implementar:
Endpoint da lista de ações
Quando um administrador do Looker conecta uma instância do Looker a um servidor de ações pela primeira vez, o Looker chama o URL fornecido, conhecido como "endpoint da lista de ações", para receber informações sobre as ações disponíveis no servidor.
Nas declarações de rota que mostramos anteriormente, disponibilizamos esse endpoint no caminho raiz (/) no URL da nossa função e indicamos que ele seria processado pela função hubListing.
Como você pode ver na definição de função a seguir, não há muito "código" nela. Ela apenas retorna os mesmos dados JSON todas as vezes. Uma coisa a observar é que ela inclui dinamicamente o próprio URL em alguns campos, permitindo que a instância do Looker envie solicitações posteriores para a mesma função.
async function hubListing(req){
return {
integrations: [
{
name: "demo-bq-insert",
label: "Demo BigQuery Insert",
supported_action_types: ["cell", "query", "dashboard"],
form_url:`${process.env.CALLBACK_URL_PREFIX}/action-0/form`,
url: `${process.env.CALLBACK_URL_PREFIX}/action-0/execute`,
icon_data_uri: "data:image/png;base64,...",
supported_formats:["inline_json"],
supported_formattings:["unformatted"],
required_fields:[
// You can use this to make your action available
// for specific queries/fields
// {tag:"user_id"}
],
params: [
// You can use this to require parameters, either
// from the Action's administrative configuration,
// or from the invoking user's user attributes.
// A common use case might be to have the Looker
// instance pass along the user's identification to
// allow you to conditionally authorize the action:
{name: "email", label: "Email", user_attribute_name: "email", required: true}
]
}
]
}
}
Para fins de demonstração, nosso código não exigiu autenticação para recuperar essa listagem. No entanto, se você considerar os metadados da ação confidenciais, também poderá exigir autenticação para essa rota, conforme mostrado na próxima seção.
Nossa função do Cloud Run também pode expor e processar várias ações, o que explica nossa convenção de rota de /action-X/.... No entanto, nossa função de demonstração do Cloud Run vai implementar apenas uma ação.
Endpoint do formulário de ação
Embora nem todos os casos de uso exijam um formulário, ter um se encaixa bem no caso de uso de gravações de banco de dados, já que os usuários podem inspecionar dados no Looker e fornecer valores a serem inseridos no banco de dados. Como nossa lista de ações forneceu um parâmetro form_url, o Looker vai invocar esse endpoint do formulário de ação quando um usuário começar a interagir com a ação, para determinar quais dados adicionais capturar do usuário.
Nas declarações de rota, disponibilizamos esse endpoint no caminho /action-0/form e associamos dois gerenciadores a ele: requireInstanceAuth e action0Form.
Configuramos nossas declarações de rota para permitir vários gerenciadores como esse porque algumas lógicas podem ser reutilizadas para vários endpoints.
Por exemplo, podemos ver que requireInstanceAuth é usado para várias rotas. Usamos esse gerenciador sempre que queremos exigir que uma solicitação tenha vindo da nossa instância do Looker. O gerenciador recupera o valor do token esperado do Secret Manager e rejeita todas as solicitações que não têm esse valor de token esperado.
async function requireInstanceAuth(req) {
const lookerSecret = await getLookerSecret()
if(!lookerSecret){return}
const expectedAuthHeader = `Token token="${lookerSecret}"`
if(!timingSafeEqual(req.headers.authorization,expectedAuthHeader)){
return {
status:401,
body: {error: "Looker instance authentication is required"}
}
}
return
function timingSafeEqual(a, b) {
if(typeof a !== "string"){return}
if(typeof b !== "string"){return}
var aLen = Buffer.byteLength(a)
var bLen = Buffer.byteLength(b)
const bufA = Buffer.allocUnsafe(aLen)
bufA.write(a)
const bufB = Buffer.allocUnsafe(aLen) //Yes, aLen
bufB.write(b)
return crypto.timingSafeEqual(bufA, bufB) && aLen === bLen;
}
}
Usamos uma implementação timingSafeEqual, em vez da verificação de igualdade padrão (==), para evitar a divulgação de informações de tempo de canal lateral que permitiriam que um invasor descobrisse rapidamente o valor do nosso secret.
Supondo que uma solicitação passe na verificação de autenticação da instância, ela será processada pelo gerenciador action0Form.
async function action0Form(req){
return [
{name: "choice", label: "Choose", type:"select", options:[
{name:"Yes", label:"Yes"},
{name:"No", label:"No"},
{name:"Maybe", label:"Maybe"}
]},
{name: "note", label: "Note", type: "textarea"}
]
}
Embora nosso exemplo de demonstração seja muito estático, o código do formulário pode ser mais interativo para determinados casos de uso. Por exemplo, dependendo da seleção de um usuário em um menu suspenso inicial, diferentes campos podem ser exibidos.
Endpoint de execução de ação
O endpoint de execução de ação é onde a maior parte da lógica de qualquer ação reside e onde vamos abordar a lógica específica do caso de uso de inserção do BigQuery.
Nas declarações de rota, disponibilizamos esse endpoint no caminho /action-0/execute e associamos três gerenciadores a ele: requireInstanceAuth, processRequestBody e action0Execute.
Já abordamos requireInstanceAuth, e o gerenciador processRequestBody fornece principalmente um pré-processamento não interessante para transformar determinados campos inconvenientes no corpo da solicitação do Looker em um formato mais conveniente, mas você pode consultar o arquivo de código completo.
A função action0Execute começa mostrando exemplos de extração de informações de várias partes da solicitação de ação que podem ser úteis. Na prática, observe que os elementos de solicitação que nosso código se refere como formParams e actionParams podem conter campos diferentes, dependendo do que você declara nos endpoints de listagem e formulário.
async function action0Execute (req){
try{
// Prepare some data that we will insert
const scheduledPlanId = req.body.scheduled_plan && req.body.scheduled_plan.scheduled_plan_id
const formParams = req.body.form_params || {}
const actionParams = req.body.data || {}
const queryData = req.body.attachment.data //If using a standard "push" action
/*In case any fields require datatype-specific preparation, check this example:
https://github.com/googleapis/nodejs-bigquery-storage/blob/main/samples/append_rows_proto2.js
*/
const newRow = {
invoked_at: new Date(),
invoked_by: actionParams.email,
scheduled_plan_id: scheduledPlanId || null,
query_result_size: queryData.length,
choice: formParams.choice,
note: formParams.note,
}
Em seguida, o código faz a transição para um código padrão do BigQuery para inserir os dados. As APIs BigQuery Storage Write oferecem outras variações mais complexas que são mais adequadas para uma conexão de streaming persistente ou inserções em massa de muitos registros. No entanto, para responder a interações de usuários individuais no contexto de uma função do Cloud Run, essa é a variação mais direta.
await bigqueryConnectAndAppend(newRow)
...
async function bigqueryConnectAndAppend(row){
let writerClient
try{
const destinationTablePath = `projects/${projectId}/datasets/${datasetId}/tables/${tableId}`
const streamId = `${destinationTablePath}/streams/_default`
writerClient = new BQSManagedWriter.WriterClient({projectId})
const writeMetadata = await writerClient.getWriteStream({
streamId,
view: 'FULL',
})
const protoDescriptor = BigqueryStorage.adapt.convertStorageSchemaToProto2Descriptor(
writeMetadata.tableSchema,
'root'
)
const connection = await writerClient.createStreamConnection({
streamId,
destinationTablePath,
})
const writer = new BQSManagedWriter.JSONWriter({
streamId,
connection,
protoDescriptor,
})
let result
if(row){
// The API expects an array of rows, so wrap the single row in an array
const rowsToAppend = [row]
result = await writer.appendRows(rowsToAppend).getResult()
}
return {
streamId: connection.getStreamId(),
protoDescriptor,
result
}
}
catch (e) {throw e}
finally{
if(writerClient){writerClient.close()}
}
}
O código de demonstração também inclui um endpoint de "status" para fins de solução de problemas, mas esse endpoint não é necessário para a integração da API Action.
Guia de implantação
Por fim, vamos fornecer um guia detalhado para implantar a demonstração, abrangendo pré-requisitos, implantação de funções do Cloud Run, configuração do BigQuery e configuração do Looker.
Pré-requisitos do projeto e do serviço
Antes de começar a configurar qualquer detalhe, revise esta lista para entender quais serviços e políticas a solução vai precisar:
- Um novo projeto:você vai precisar de um novo projeto para hospedar os recursos do nosso exemplo.
- Serviços:ao usar o BigQuery e as funções do Cloud Run pela primeira vez na UI do console do Cloud, você vai receber uma solicitação para ativar as APIs necessárias para os serviços necessários, incluindo BigQuery, Artifact Registry, Cloud Build, Cloud Functions, Cloud Logging, Pub/Sub, Administrador do Cloud Run e Secret Manager.
- Política para invocações não autenticadas: esse caso de uso exige que implantemos funções do Cloud Run que "permitam acesso público", já que vamos processar a autenticação de solicitações recebidas no nosso código de acordo com a API Action, em vez de usar o IAM. Embora isso seja permitido por padrão, a política da organização geralmente restringe esse uso. Especificamente, a política
constraints/iam.allowedPolicyMemberDomainsrestringe quem pode receber permissões do IAM, e talvez seja necessário ajustá-la para permitir o principalallUserspara acesso não autenticado. Consulte este guia, Como criar serviços públicos do Cloud Run quando o compartilhamento restrito de domínio é aplicado, para mais informações se você não conseguir permitir o acesso público. - Outras políticas: lembre-se de que outras Google Cloud restrições de política da organização também podem impedir a implantação de serviços que, de outra forma, são permitidos por padrão.
Como implantar a função do Cloud Run
Depois de criar um novo projeto, siga estas etapas para implantar a função do Cloud Run
- Em Cloud Run functions, clique em Criar função.
- Escolha um nome para sua função (por exemplo, "demo-bq-insert-action").
- Em Configurações de acionador :
- O tipo de acionador já deve ser "HTTPS".
- Defina Autenticação como Permitir invocações não autenticadas.
- Copie o valor do URL para a área de transferência.
- Em Configurações de ambiente de execução > Variáveis de ambiente de execução :
- Clique em Adicionar variável.
- Defina o nome da variável como
CALLBACK_URL_PREFIX. - Cole o URL da etapa anterior como o valor.
- Clique em Próxima.
- Clique no arquivo
package.jsone cole o conteúdo. - Clique no arquivo
index.jse cole o conteúdo. - Atribua a variável
projectIdna parte de cima do arquivo ao ID do seu projeto. - Defina o ponto de entrada como
httpHandler. - Clique em Implantar.
- Conceda as permissões solicitadas (se houver) à conta de serviço de build.
- Aguarde até que a implantação seja concluída.
- Se, em etapas futuras, você receber um erro direcionando você para analisar os Google Cloud registros, observe que é possível acessar os registros dessa função na guia Registros desta página.
- Antes de sair da página da função do Cloud Run, na guia Detalhes, localize e anote a conta de serviço que a função tem. Vamos usar isso nas etapas posteriores para garantir que a função tenha as permissões necessárias.
- Teste a implantação da função diretamente no navegador acessando o URL. Você vai encontrar uma resposta JSON contendo a listagem de integração.
- Se você receber um erro 403, sua tentativa de definir Permitir invocações não autenticadas poderá ter falhado silenciosamente como resultado de uma política da organização. Verifique se a função está permitindo invocações não autenticadas, revise a configuração da política da organização e tente atualizar a configuração.
Acesso à tabela de destino do BigQuery
Na prática, a tabela de destino a ser inserida pode residir em um projeto diferente Google Cloud . No entanto, para fins de demonstração, vamos criar uma nova tabela de destino no mesmo projeto. Em ambos os casos, é necessário garantir que a conta de serviço da função do Cloud Run tenha permissões para gravar na tabela.
- Navegue até o console do BigQuery.
Crie a tabela de demonstração:
- Na barra do Explorer, use o menu de reticências ao lado do projeto e selecione Criar conjunto de dados.
- Dê ao conjunto de dados o ID
demo_datasete clique em Criar conjunto de dados. - Use o menu de reticências no conjunto de dados recém-criado e selecione Criar tabela.
- Dê à tabela o nome
demo_table. Em Esquema, selecione Editar como texto, use o esquema a seguir e clique em Criar tabela.
[ {"name":"invoked_at","type":"TIMESTAMP"}, {"name":"invoked_by","type":"STRING"}, {"name":"scheduled_plan_id","type":"STRING"}, {"name":"query_result_size","type":"INTEGER"}, {"name":"choice","type":"STRING"}, {"name":"note","type":"STRING"} ]
Atribuir permissões:
- Na barra Explorer, clique no conjunto de dados.
- Na página conjunto de dados, clique em Compartilhamento > Permissões.
- Clique em Adicionar principal.
- Defina o novo principal como a conta de serviço da função, observada anteriormente nesta página.
- Atribua o papel Editor de dados do BigQuery.
- Clique em Salvar.
Como se conectar ao Looker
Agora que a função está implantada, vamos conectar o Looker a ela.
- Vamos precisar de uma senha secreta para que a ação autentique as solicitações da instância do Looker. Gere uma string aleatória longa e mantenha-a segura. Vamos usá-la nas etapas subsequentes como o valor do secret do Looker.
- No console do Cloud, navegue até o Secret Manager.
- Clique em Criar secret.
- Defina o nome como
LOOKER_SECRET. Esse nome é codificado no código desta demonstração, mas você pode escolher qualquer nome ao trabalhar com seu próprio código. - Defina o valor do secret como o valor do secret que você gerou.
- Clique em Criar secret.
- Na página Secret, clique na guia Permissões.
- Clique em Permitir acesso.
- Defina Novos principais como a conta de serviço da função, observada anteriormente.
- Atribua o papel Acessador de secrets do Secret Manager.
- Clique em Salvar.
- É possível confirmar se a função está acessando o secret acessando a rota
/statusanexada ao URL da função.
- Na instância do Looker:
- Acesse Admin > Plataforma > Ações.
- Acesse a parte de baixo da página para clicar em Adicionar hub de ações.
- Forneça o URL da função (por exemplo, https://your-region-your-project.cloudfunctions.net/demo-bq-insert-action) e confirme clicando em Adicionar hub de ações.
- Agora você vai encontrar uma nova entrada do hub de ações com uma ação chamada Inserção de demonstração do BigQuery.
- Na entrada do hub de ações, clique em Configurar autorização.
- Insira o secret do Looker gerado no campo Token de autorização e clique em Atualizar token.
- Na ação Inserção de demonstração do BigQuery, clique em Ativar.
- Ative a opção Ativado.
- Um teste da ação será executado automaticamente, confirmando que a função está aceitando a solicitação do Looker e respondendo corretamente ao endpoint do formulário.
- Clique em Salvar.
Teste de ponta a ponta
Agora podemos usar nossa nova ação. Essa ação está configurada para funcionar com qualquer consulta. Portanto, escolha qualquer análise (por exemplo, uma análise de atividade do sistema integrada), adicione alguns campos a uma nova consulta, execute-a e escolha Enviar no menu de engrenagem. Você vai encontrar a ação como um dos destinos disponíveis e receberá uma solicitação para algumas entradas de campo:

Ao pressionar Enviar, uma nova linha será inserida na tabela do BigQuery (e o e-mail da sua conta de usuário do Looker será identificado na coluna invoked_by).