Conectar o aplicativo externo ao Lakebase usando a API

Este guia mostra como conectar aplicações externas ao Lakebase usando chamadas diretas de API REST. Use essa abordagem quando um SDK do Databricks não estiver disponível para seu idioma (Node.js, Ruby, PHP, Elixir, Rust etc.).

Se o seu idioma tiver suporte ao SDK (Python, Java ou Go), use Conectar aplicativo externo ao Lakebase usando o SDK para um gerenciamento de token mais simples.

Você faz duas chamadas à API para obter credenciais de banco de dados com a rotação de token OAuth. Exemplos são fornecidos para curl e Node.js.

Observação

Autenticação em duas etapas: Essa abordagem requer duas chamadas de API para cada credencial de banco de dados: (1) trocar o segredo do Service Principal por um token OAuth do workspace, (2) trocar o token OAuth por uma credencial de banco de dados. Ambos os tokens expiram após 60 minutos. O SDK manipula a etapa 1 automaticamente.

Pré-requisitos

Você precisa da mesma configuração usada na abordagem do SDK: entidade de serviço, papel do Postgres e detalhes da conexão.

Pré-requisito Detalhe chave Mais informações
Service Principal Segredo OAuth com tempo de vida máximo de 730 dias; habilitar o acesso ao espaço de trabalho. Observe o ID do cliente (UUID) para o role do Postgres e as variáveis de ambiente. Criar principal de serviço
Função Postgres Crie uma função OAuth no Editor de SQL do Lakebase: databricks_create_role('{client-id}', 'SERVICE_PRINCIPAL') e conceda CONNECT, USAGE, SELECT/INSERT/UPDATE/DELETE. Utilize o ID do cliente da etapa 1. Criar função postgres
Detalhes da conexão Do Lakebase Console Connect: nome do ponto de extremidade (projects/.../branches/.../endpoints/...), host, banco de dados (geralmente databricks_postgres). Obter detalhes da conexão

Como funciona

A abordagem manual da API requer duas trocas de token:

Fluxo manual de troca de token de API

Tempo de vida do token:

  • Secreto do Principal do Serviço: Até 730 dias (definido durante a criação)
  • Token OAuth do workspace: 60 minutos (etapa 1)
  • Credencial do banco de dados: 60 minutos (etapa 2)

Escopo de token: As credenciais do banco de dados têm escopo de espaço de trabalho. Embora o endpoint parâmetro seja necessário, o token retornado pode acessar qualquer banco de dados ou projeto no espaço de trabalho para o qual o service principal tem permissões.

Definir variáveis de ambiente

Defina essas variáveis de ambiente antes de executar seu aplicativo:

# Databricks workspace authentication
export DATABRICKS_HOST="https://your-workspace.databricks.com"
export DATABRICKS_CLIENT_ID="<service-principal-client-id>"
export DATABRICKS_CLIENT_SECRET="<your-oauth-secret>"

# Lakebase connection details (from prerequisites)
export ENDPOINT_NAME="projects/<project-id>/branches/<branch-id>/endpoints/<endpoint-id>"
export PGHOST="<endpoint-id>.database.<region>.cloud.databricks.com"
export PGDATABASE="databricks_postgres"
export PGUSER="<service-principal-client-id>"   # Same UUID as client ID
export PGPORT="5432"

Adicionar código de conexão

encurvar

Este exemplo mostra as chamadas de API brutas. Para aplicativos de produção, implemente o cache de token e atualize a lógica.

# Step 1: Get workspace OAuth token
OAUTH_TOKEN=$(curl -s -X POST "${DATABRICKS_HOST}/oidc/v1/token" \
  -u "${DATABRICKS_CLIENT_ID}:${DATABRICKS_CLIENT_SECRET}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=all-apis" \
  | jq -r '.access_token')

echo "Got workspace OAuth token (60-min lifetime)"

# Step 2: Get database credential
PG_TOKEN=$(curl -s -X POST "${DATABRICKS_HOST}/api/2.0/postgres/credentials" \
  -H "Authorization: Bearer ${OAUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"endpoint\": \"${ENDPOINT_NAME}\"}" \
  | jq -r '.token')

echo "Got database credential (60-min lifetime)"

# Step 3: Connect to Postgres
PGPASSWORD="${PG_TOKEN}" psql \
  -h "${PGHOST}" \
  -p "${PGPORT}" \
  -U "${PGUSER}" \
  -d "${PGDATABASE}" \
  -c "SELECT current_user, current_database()"

Node.js

Este exemplo usa node-postgres com uma função de senha assíncrona que manipula a busca e o cache de tokens.

import pg from 'pg';

// Step 1: Fetch workspace OAuth token
async function getWorkspaceToken(host, clientId, clientSecret) {
  const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
  const response = await fetch(`${host}/oidc/v1/token`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: `Basic ${auth}`,
    },
    body: 'grant_type=client_credentials&scope=all-apis',
  });

  if (!response.ok) {
    throw new Error(`OAuth failed: ${response.status}`);
  }

  const data = await response.json();
  return {
    token: data.access_token,
    expires: Date.now() + data.expires_in * 1000,
  };
}

// Step 2: Fetch database credential
async function getPostgresCredential(host, workspaceToken, endpoint) {
  const response = await fetch(`${host}/api/2.0/postgres/credentials`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${workspaceToken}`,
    },
    body: JSON.stringify({ endpoint }),
  });

  if (!response.ok) {
    throw new Error(`Database credential failed: ${response.status}`);
  }

  const data = await response.json();
  return {
    token: data.token,
    expires: new Date(data.expire_time).getTime(),
  };
}

// Simple caching wrapper (production: use more sophisticated caching)
function cached(fetchFn) {
  let cache = null;
  return async (...args) => {
    const now = Date.now();
    if (!cache || now >= cache.expires - 5 * 60 * 1000) {
      // Refresh 5 min early
      const result = await fetchFn(...args);
      cache = result;
    }
    return cache.token;
  };
}

// Create connection pool with async password function
function createPool() {
  const host = process.env.DATABRICKS_HOST;
  const clientId = process.env.DATABRICKS_CLIENT_ID;
  const clientSecret = process.env.DATABRICKS_CLIENT_SECRET;
  const endpoint = process.env.ENDPOINT_NAME;

  const cachedWorkspaceToken = cached(() => getWorkspaceToken(host, clientId, clientSecret));
  const cachedPostgresToken = cached(async () => {
    const workspaceToken = await cachedWorkspaceToken();
    return getPostgresCredential(host, workspaceToken, endpoint);
  });

  return new pg.Pool({
    host: process.env.PGHOST,
    port: process.env.PGPORT,
    database: process.env.PGDATABASE,
    user: process.env.PGUSER,
    password: cachedPostgresToken, // Async function: () => Promise<string>
    ssl: { rejectUnauthorized: true },
    min: 1,
    max: 10,
    idleTimeoutMillis: 900000, // Example: 15 minutes
    connectionTimeoutMillis: 60000, // Example: 60 seconds
  });
}

// Use the pool
const pool = createPool();
const result = await pool.query('SELECT current_user, current_database()');
console.log('Connected as:', result.rows[0].current_user);

Dependências:pg (node-postgres)

Nota: Node-postgres (pg) aceita uma função assíncrona como a senha. A função é chamada sempre que uma nova conexão é criada, garantindo tokens novos.

Executar e verificar a conexão

encurvar

Execute o script bash com variáveis de ambiente carregadas:

export $(cat .env | xargs)
bash connect.sh

Saída esperada:

Got workspace OAuth token (60-min lifetime)
Got database credential (60-min lifetime)
     current_user      | current_database
-----------------------+------------------
 c00f575e-d706-4f6b... | databricks_postgres

Se current_user corresponder à ID do cliente da entidade de serviço, o OAuth estará funcionando corretamente.

Node.js

Instalar dependências:

npm install pg

Executar:

node app.js

Saída esperada:

Connected as: c00f575e-d706-4f6b-b62c-e7a14850571b

Nota: A primeira conexão após a inatividade pode demorar mais, pois o Lakebase começa a calcular do zero.

Resolução de problemas

Erro Corrigir
"invalid_client" ou "Falta de autenticação do cliente" Verifique se DATABRICKS_CLIENT_ID e DATABRICKS_CLIENT_SECRET são corretos. Use autenticação básica (codificada em base64).
"A API está desabilitada para usuários sem direito de acesso ao workspace" Habilite o "acesso ao espaço de trabalho" para a entidade de serviço (pré-requisitos).
"INVALID_PARAMETER_VALUE" / "O campo 'endpoint' é obrigatório" Verifique se o parâmetro endpoint está incluído no corpo do POST na etapa 2 com o formato projects/<id>/branches/<id>/endpoints/<id>.
"A função não existe" ou a autenticação falha Crie uma função OAuth por meio de SQL (pré-requisitos).
"Conexão recusada" ou "tempo de espera esgotado" A primeira conexão após o escalonamento para zero pode levar mais tempo. Implemente a lógica de repetição.
Token expirado/"falha na autenticação de senha" Os tokens de workspace e de banco de dados expiram após 60 minutos. Implemente o cache com verificações de expiração.