Criação de um serviço do Snowpark Container Services que use os direitos do chamador

Introdução

Neste tutorial, você explorará a criação de um serviço, apresentando uma UI da Web, que usa o recurso de direitos do chamador ao executar consultas SQL em nome dos usuários.

Você cria um serviço (chamado query_service) que executa uma consulta fornecida na solicitação. Por padrão, os contêineres de aplicativos se conectam ao Snowflake como o usuário do serviço usando a função de proprietário do serviço. Mas esse aplicativo usa o recurso de direitos do chamador para se conectar ao ponto de extremidade do serviço como o usuário final e usar os privilégios concedidos a esse usuário.

Ao testar, você usa o serviço de um navegador da Web porque o recurso de direitos do chamador só é suportado ao acessar um serviço usando o ingresso na rede. O recurso de direitos do chamador não está disponível ao acessar um serviço usando uma função de serviço.

O serviço faz o seguinte:

  • Expõe um ponto de extremidade público.

  • Quando um usuário faz login no ponto de extremidade, o serviço fornece uma UI da Web para fazer uma consulta. O serviço executa a consulta no Snowflake e retorna os resultados. Neste tutorial, você executa o seguinte comando SQL:

    SELECT CURRENT_USER(), CURRENT_ROLE();
    
    Copy

    O comando retorna o nome do usuário conectado no momento e a função ativa no momento, sendo que ambos dependem do uso dos direitos do chamador.

    • Quando os direitos do chamador são usados, o serviço se conecta ao Snowflake como o usuário chamador e a função padrão do usuário. O comando retorna seu nome de usuário e função padrão.

    • Quando os direitos do chamador não são usados, o comportamento padrão entra em ação, em que o serviço se conecta ao Snowflake como o usuário do serviço e a função de proprietário do serviço. Portanto, o comando retorna o nome do usuário do serviço no formato: SF$SERVICE$unique-id, TEST_ROLE.

Existem duas partes neste tutorial:

Parte 1: Criar e testar um serviço. Faça download do código fornecido para este tutorial e siga as instruções passo a passo:

  1. Baixe o código de serviço deste tutorial.

  2. Crie uma imagem Docker para Snowpark Container Services e carregue a imagem em um repositório em sua conta.

  3. Crie um serviço.

  4. Comunique-se com o serviço usando o ingresso na rede para se conectar ao ponto de extremidade público que o serviço expõe. Usando um navegador da Web, você faz login no ponto de extremidade público e executa o comando SELECT CURRENT_USER();. Verifique a saída do comando para garantir que o contêiner executou o comando como o usuário conectado.

Parte 2: Entender o serviço. Esta seção fornece uma visão geral do código de serviço e destaca como o código do aplicativo usa os direitos do chamador.

Preparação

Siga Configuração comum para configurar pré-requisitos e criar recursos do Snowflake que são necessários para todos os tutoriais do Snowpark Container Services fornecidos nesta documentação.

Download do código de serviço

O código (um aplicativo Python) é fornecido para criar o serviço de consulta.

  1. Baixe SnowparkContainerServices-Tutorials.zip.

  2. Descompacte o conteúdo, que inclui um diretório para cada tutorial. O diretório Tutorial-6-callers-rights possui os seguintes arquivos:

    • Dockerfile

    • main.py

    • templates/basic_ui.html

Criação e carregamento de uma imagem

Crie uma imagem para a plataforma Linux/AMD64 compatível com o Snowpark Container Services e, em seguida, faça upload da imagem para o repositório de imagens da sua conta (consulte Configuração comum).

Você precisará de informações sobre o repositório (o URL do repositório e o nome do host do registro) antes de poder construir e fazer upload da imagem. Para obter mais informações, consulte Registro e repositórios.

Obter informações sobre o repositório

  1. Para obter o URL do repositório, execute o comando SHOW IMAGE REPOSITORIES SQL.

    SHOW IMAGE REPOSITORIES;
    
    Copy
    • A coluna repository_url na saída fornece o URL. Abaixo um exemplo:

      <orgname>-<acctname>.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository
      
    • O nome do host no URL do repositório é o nome do host de registro. Abaixo um exemplo:

      <orgname>-<acctname>.registry.snowflakecomputing.com
      

Criar a imagem e carregá-la no repositório

  1. Abra uma janela de terminal e mude para o diretório que contém os arquivos que você descompactou.

  2. Para criar uma imagem do Docker, execute o seguinte comando docker build usando o Docker CLI. Observe que o comando especifica o diretório de trabalho atual (.) como o PATH para os arquivos a serem usados para criar a imagem.

    docker build --rm --platform linux/amd64 -t <repository_url>/<image_name> .
    
    Copy
    • Para image_name, use query_service:latest:

    Exemplo

    docker build --rm --platform linux/amd64 -t myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest .
    
    Copy
  3. Faça upload da imagem para o repositório em sua conta Snowflake. Para que o Docker faça upload de uma imagem em seu nome para o repositório, você deve primeiro autenticar o Docker com o Snowflake.

    1. Para autenticar o Docker com o registro Snowflake, execute o seguinte comando:

      docker login <registry_hostname> -u <username>
      
      Copy
      • Para username, especifique seu nome de usuário do Snowflake. O Docker solicitará sua senha.

    2. Para fazer upload da imagem, execute o seguinte comando:

      docker push <repository_url>/<image_name>
      
      Copy

      Exemplo

      docker push myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest
      
      Copy

Criação de um serviço

Nesta seção, você cria um serviço (query_service).

  1. Verifique se o pool de computação está pronto e se você está no contexto certo para criar o serviço.

    1. Anteriormente, você definiu o contexto na etapa Configuração comum. Para garantir que você esteja no contexto correto para as declarações do SQL nesta etapa, execute o seguinte:

    USE ROLE test_role;
    USE DATABASE tutorial_db;
    USE SCHEMA data_schema;
    USE WAREHOUSE tutorial_warehouse;
    
    Copy
    1. Para garantir que o pool de computação que você criou na configuração comum esteja pronto, execute DESCRIBE COMPUTE POOL e verifique se o state é ACTIVE ou IDLE. Se state for STARTING, será necessário aguardar até que state mude para ACTIVE ou IDLE.

    DESCRIBE COMPUTE POOL tutorial_compute_pool;
    
    Copy
  2. Para criar o serviço, execute o seguinte comando usando test_role:

    CREATE SERVICE query_service
      IN COMPUTE POOL tutorial_compute_pool
      FROM SPECIFICATION $$
        spec:
          containers:
          - name: main
            image: /tutorial_db/data_schema/tutorial_repository/query_service:latest
            env:
              SERVER_PORT: 8000
            readinessProbe:
              port: 8000
              path: /healthcheck
          endpoints:
          - name: execute
            port: 8000
            public: true
        capabilities:
          securityContext:
            executeAsCaller: true
        serviceRoles:
        - name: ui_usage
          endpoints:
          - execute
    $$;
    
    Copy

    Nota

    Se já existir um serviço com esse nome, use o comando DROP SERVICE para excluir o serviço criado anteriormente e, em seguida, crie esse serviço.

  3. Execute os seguintes comandos SQL para obter informações detalhadas sobre o serviço que você acabou de criar. Para obter mais informações, consulte Snowpark Container Services: como trabalhar com serviços.

    • Para listar os serviços da sua conta, execute o comando SHOW SERVICES:

      SHOW SERVICES;
      
      Copy
    • Para obter o status do seu serviço, execute o comando SHOW SERVICE CONTAINERS IN SERVICE:

      SHOW SERVICE CONTAINERS IN SERVICE query_service;
      
      Copy
    • Para obter informações sobre seu serviço, execute o comando DESCRIBE SERVICE:

      DESCRIBE SERVICE query_service;
      
      Copy

Uso do serviço

Nesta seção, verifique se os direitos do chamador configurados para o serviço funcionam. Você faz login no ponto de extremidade público a partir de um navegador, executa uma consulta e verifica se a sessão do Snowflake que o serviço criou opera como o usuário que chama, em vez de como o usuário do serviço.

Primeiro, para configurar o contexto das declarações do SQL nesta seção, execute o seguinte:

USE ROLE test_role;
USE DATABASE tutorial_db;
USE SCHEMA data_schema;
USE WAREHOUSE tutorial_warehouse;
Copy

O serviço expõe um ponto de extremidade público (consulte a especificação inline fornecida no comando CREATE SERVICE); portanto, primeiro faça login no ponto de extremidade usando um navegador da Web e, em seguida, use a UI da Web que o serviço expõe à Internet para enviar solicitações de consulta ao ponto de extremidade do serviço.

  1. Encontre o URL do endereço do ponto de extremidade público que o serviço expõe:

    SHOW ENDPOINTS IN SERVICE query_service;
    
    Copy

    A coluna ingress_url na resposta fornece o URL.

    Exemplo

    p6bye-myorg-myacct.snowflakecomputing.app
    
  2. Anexe /ui ao URL do ponto de extremidade e cole-o no navegador da web. Isso faz com que o serviço execute a função ui() (consulte main.py).

    Observe que na primeira vez que você acessar o URL do ponto de extremidade, será solicitado a fazer login no Snowflake.

  3. Use o mesmo usuário que você usou para criar o serviço. Após o login bem-sucedido, o serviço mostra a seguinte UI da Web.

    Formulário da Web para comunicação com o serviço de consulta.

    Digite o seguinte comando na caixa de texto e pressione Enter para ver os resultados.

    SELECT CURRENT_USER(), CURRENT_ROLE()DONE;
    
    Copy

    Como você incluiu o recurso executeAsCaller na especificação do serviço, quando uma solicitação chega, o Snowflake insere o cabeçalho Sf-Context-Current-User-Token na solicitação e, em seguida, encaminha a solicitação para o ponto de extremidade do serviço.

    Para fins de ilustração, o código de serviço neste tutorial executa a consulta tanto como chamador quanto como usuário do serviço.

    Formulário da Web para se comunicar com o serviço de consulta.
    • Executa a consulta em nome do chamador (usuário de entrada): nesse caso, o código usa o token de usuário que o Snowflake fornece para construir um token de login para conexão com o Snowflake. Assim, o serviço usa os direitos do chamador. O Snowflake executa a consulta em nome do chamador, exibindo o nome do chamador e o nome da função ativa no resultado de consulta. Por exemplo:

      ['TESTUSER, PUBLIC']
      
    • Executa a consulta em nome do usuário do serviço: nesse caso, o código não usa o token de usuário que o Snowflake fornece na solicitação ao construir o token de login para se conectar ao Snowflake. Assim, o serviço não utiliza os direitos do chamador, fazendo com que o Snowflake execute a consulta em nome do usuário do serviço. O resultado de consulta mostra o nome do usuário do serviço (que é o mesmo que o nome do serviço) e a função ativa.

      ['QUERY_SERVICE, TEST_ROLE']
      

Quando o serviço executa a consulta (SELECT CURRENT_USER(), CURRENT_ROLE();) em nome do chamador, o Snowflake não precisa do warehouse do usuário para executar essa consulta simples. Portanto, o serviço não precisava de nenhuma concessão do chamador. Na próxima seção, o serviço executa uma consulta não trivial em nome do usuário chamador que exige que você conceda concessões do chamador ao serviço.

Nota

Você pode acessar o ponto de extremidade de entrada programaticamente. Para obter um código de amostra, consulte Acesso de ponto de extremidade público de fora do Snowflake e autenticação. Observe que é necessário anexar /ui ao URL do ponto de extremidade no código para que o Snowflake possa rotear a solicitação para a função ui() no código de serviço.

Uso do serviço com concessões do chamador

Nesta seção, o serviço executa a seguinte consulta em nome do chamador (o usuário que faz login no ponto de extremidade de entrada do serviço).

SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
Copy

O serviço não tem permissões para acessar a tabela e não tem permissão para executar a consulta no warehouse padrão. Para permitir que o serviço execute essa consulta em nome do chamador, você concede a concessão do chamador necessária ao serviço.

Para demonstrar o cenário, você cria uma nova função (ingress_user_role) e uma tabela (ingress_user_table) que é acessível à nova função, mas não à função de proprietário do serviço (test_role). Portanto, quando o serviço tenta executar a consulta usando as credenciais do serviço, o Snowflake retorna um erro. Mas quando o serviço executa a consulta em nome do usuário, o Snowflake executa a consulta e retorna o resultado.

Criação de funções e recursos

  1. Crie uma função (ingress_user_role) e um banco de dados (ingress_user_db) que somente essa função possa acessar. Em seguida, você concede essa função ao seu usuário, para que ele possa fazer login no ponto de extremidade público do serviço e consultar essa tabela.

    USE ROLE accountadmin;
    
    CREATE ROLE ingress_user_role;
    GRANT ROLE ingress_user_role TO USER <your_user_name>;
    
    GRANT USAGE ON WAREHOUSE tutorial_warehouse TO ROLE ingress_user_role;
    
    CREATE DATABASE IF NOT EXISTS ingress_user_db;
    GRANT OWNERSHIP ON DATABASE ingress_user_db TO ROLE ingress_user_role COPY CURRENT GRANTS;
    
    Copy
  2. Crie uma tabela (ingress_user_table) que somente a função ingress_user_role possa acessar.

    USE ROLE ingress_user_role;
    
    CREATE SCHEMA IF NOT EXISTS ingress_user_db.ingress_user_schema;
    USE WAREHOUSE tutorial_warehouse;
    CREATE TABLE ingress_user_db.ingress_user_schema.ingress_user_table (col string) AS (
        SELECT 'this table is only accessible to the ingress_user_role'
    );
    
    Copy

    Observe que quando o serviço tenta consultar a tabela em nome do chamador, o serviço opera somente como test_role, a função que foi usada para criar o serviço (a função de proprietário do serviço). Essa função não tem permissão para acessar a tabela de usuários.

  3. Concede concessões do chamador à função de proprietário do serviço (test_role) para consultar tabelas no banco de dados ingress_user_db. Esse privilégio permite que o serviço consulte tabelas nesse banco de dados somente se o seguinte for verdadeiro:

    USE ROLE accountadmin;
    
    GRANT CALLER USAGE ON DATABASE ingress_user_db TO ROLE test_role;
    GRANT INHERITED CALLER USAGE ON ALL SCHEMAS IN DATABASE ingress_user_db TO ROLE test_role;
    GRANT INHERITED CALLER SELECT ON ALL TABLES IN DATABASE ingress_user_db TO ROLE test_role;
    GRANT CALLER USAGE ON WAREHOUSE tutorial_warehouse TO ROLE test_role;
    SHOW CALLER GRANTS TO ROLE test_role;
    
    Copy
  4. Configure o warehouse padrão e as funções secundárias padrão.

    Quando uma sessão é criada para um usuário, o Snowflake ativa a função primária padrão, as funções secundárias padrão e o warehouse padrão do usuário conectado. Neste tutorial,

    • Você define DEFAULT_SECONDARY_ROLES como ALL para que, quando uma sessão for criada para o usuário atual, o Snowflake defina as funções secundárias atuais como todas as funções concedidas ao usuário.

    • Você também define o warehouse padrão como tutorial_warehouse, onde as consultas ingress_user_table são executadas.

    ALTER USER SET DEFAULT_SECONDARY_ROLES = ('ALL');
    ALTER USER SET DEFAULT_WAREHOUSE = TUTORIAL_WAREHOUSE;
    
    Copy

    Observe o seguinte:

    • Neste tutorial, você faz login no ponto de extremidade público do serviço. O usuário tem test_role como função principal e ingress_user_role como função secundária. Isso permite que a sessão faça tudo o que o ingress_user_role permite.

    • A função padrão e o warehouse padrão afetam apenas a função e o warehouse ativados quando o serviço estabelece uma sessão em nome do usuário. Após o estabelecimento de uma sessão de direitos do chamador, você não pode alterar a função, mas pode alterar o warehouse.

Uso do serviço e teste das concessões do chamador

  1. Encontre o URL do endereço do ponto de extremidade público que o serviço expõe:

    SHOW ENDPOINTS IN SERVICE tutorial_db.data_schema.query_service;
    
    Copy

    A coluna ingress_url na resposta fornece o URL.

    Exemplo

    p6bye-myorg-myacct.snowflakecomputing.app
    
  2. Anexe /ui ao URL do ponto de extremidade e cole-o no navegador da web. Isso faz com que o serviço execute a função ui() (consulte echo_service.py): observe que, na primeira vez que acessar o URL do ponto de extremidade, você será solicitado a fazer login no Snowflake. Para esse teste, use o mesmo usuário que você usou para criar o serviço para garantir que o usuário tenha os privilégios necessários:

  3. Use o mesmo usuário que você usou para criar o serviço. Após o login bem-sucedido, o serviço mostra a seguinte UI da Web.

    Formulário da Web para se comunicar com o serviço de consulta.

    Digite o seguinte comando na caixa de texto e pressione Enter para ver os resultados.

    SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
    
    Copy

    Para fins de ilustração, o código de serviço neste tutorial executa a consulta tanto como chamador quanto como usuário do serviço.

    • Executa a consulta em nome do chamador (usuário de entrada): nesse caso, o código usa o token de usuário fornecido pelo Snowflake para construir um token de login para conexão com o Snowflake. Assim, o serviço usa os direitos do chamador. O Snowflake executa a consulta em nome do chamador. Como o chamador está usando o ingress_user_role role que tem o privilégio de consultar a tabela ingress_user_table, a consulta retorna uma linha no resultado:

      ['this table is only accessible to ingress_user_role']
      
    • Executa a consulta em nome do usuário do serviço: nesse caso, o código não usa o token de usuário que o Snowflake fornece na solicitação ao construir o token de login para se conectar ao Snowflake. Assim, o Snowflake executa a consulta em nome do usuário do serviço. Como o proprietário do serviço usa o endereço padrão test_role, que não tem permissão para consultar a tabela, você vê um erro:

      Encountered an error when executing query:... SQL compilation error: Database 'INGRESS_USER_DB' does not exist or not authorized.
      
    Formulário da web para comunicação com o serviço Echo.

Limpeza

Você deve remover os recursos faturáveis que criou. Para obter mais informações, consulte a Etapa 5 no Tutorial 3.

Revisão do código de serviço

Esta seção cobre os seguintes tópicos:

Revisão do código do tutorial

O arquivo zip baixado na etapa 1 inclui os seguintes arquivos:

  • Dockerfile

  • main.py

  • templates/basic_ui.html

Você também usa a especificação de serviço ao criar o serviço. A seção a seguir explica como esses componentes de código funcionam juntos para criar o serviço.

arquivo main.py

Esse arquivo Python contém o código que implementa um servidor HTTP mínimo que executa uma consulta na solicitação e retorna os resultados de consulta. O código fornece uma interface de usuário da Web (UI) para o envio de solicitações de eco.

from flask import Flask
from flask import request
from flask import render_template
import logging
import os
import sys

from snowflake.snowpark import Session
from snowflake.snowpark.exceptions import *

# Environment variables below will be automatically populated by Snowflake.
SNOWFLAKE_ACCOUNT = os.getenv("SNOWFLAKE_ACCOUNT")
SNOWFLAKE_HOST = os.getenv("SNOWFLAKE_HOST")
SNOWFLAKE_DATABASE = os.getenv("SNOWFLAKE_DATABASE")
SNOWFLAKE_SCHEMA = os.getenv("SNOWFLAKE_SCHEMA")

# Custom environment variables
SNOWFLAKE_USER = os.getenv("SNOWFLAKE_USER")
SNOWFLAKE_PASSWORD = os.getenv("SNOWFLAKE_PASSWORD")
SNOWFLAKE_ROLE = os.getenv("SNOWFLAKE_ROLE")
SNOWFLAKE_WAREHOUSE = os.getenv("SNOWFLAKE_WAREHOUSE")

SERVICE_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
SERVER_PORT = os.getenv("SERVER_PORT", 8080)


def get_logger(logger_name):
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.DEBUG)
    handler.setFormatter(
        logging.Formatter("%(name)s [%(asctime)s] [%(levelname)s] %(message)s")
    )
    logger.addHandler(handler)
    return logger


def get_login_token():
    """
    Read the login token supplied automatically by Snowflake. These tokens
    are short lived and should always be read right before creating any new connection.
    """
    with open("/snowflake/session/token", "r") as f:
        return f.read()


def get_connection_params(ingress_user_token=None):
    """
    Construct Snowflake connection params from environment variables.
    """
    if os.path.exists("/snowflake/session/token"):
        if ingress_user_token:
            logger.info("Creating a session on behalf of user.")
            token = get_login_token() + "." + ingress_user_token
        else:
            logger.info("Creating a session as service user.")
            token = get_login_token()

        return {
            "account": SNOWFLAKE_ACCOUNT,
            "host": SNOWFLAKE_HOST,
            "authenticator": "oauth",
            "token": token,
            "warehouse": SNOWFLAKE_WAREHOUSE,
            "database": SNOWFLAKE_DATABASE,
            "schema": SNOWFLAKE_SCHEMA,
        }
    else:
        return {
            "account": SNOWFLAKE_ACCOUNT,
            "host": SNOWFLAKE_HOST,
            "user": SNOWFLAKE_USER,
            "password": SNOWFLAKE_PASSWORD,
            "role": SNOWFLAKE_ROLE,
            "warehouse": SNOWFLAKE_WAREHOUSE,
            "database": SNOWFLAKE_DATABASE,
            "schema": SNOWFLAKE_SCHEMA,
        }


logger = get_logger("query-service")
app = Flask(__name__)


@app.get("/healthcheck")
def readiness_probe():
    return "I'm ready!"


@app.route("/ui", methods=["GET", "POST"])
def ui():
    """
    Main handler for providing a web UI.
    """
    if request.method == "POST":
        # get ingress user token
        ingress_user = request.headers.get("Sf-Context-Current-User")
        ingress_user_token = request.headers.get("Sf-Context-Current-User-Token")

        if ingress_user:
            logger.info(f"Received a request from user {ingress_user}")

        # getting input in HTML form
        query = request.form.get("query")
        if query:
            logger.info(f"Received a request for query: {query}.")
            query_result_ingress_user = (
                run_query(query, ingress_user_token)
                if ingress_user_token
                else "Token is missing. Can't execute as ingress user."
            )
            query_result_service_user = run_query(query)
            return render_template(
                "basic_ui.html",
                query_input=query,
                query_result_ingress_user=query_result_ingress_user,
                query_result_service_user=query_result_service_user,
            )
    return render_template("basic_ui.html")


@app.route("/query", methods=["GET"])
def query():
    """
    Main handler for providing programmatic access.
    """
    # get ingress user token
    query = request.args.get("query")
    logger.info(f"Received query request: {query}.")
    if query:
        ingress_user = request.headers.get("Sf-Context-Current-User")
        ingress_user_token = request.headers.get("Sf-Context-Current-User-Token")

        if ingress_user:
            logger.info(f"Received a request from user {ingress_user}")

        res = run_query(query, ingress_user_token)
        return str(res)
    return "DONE"


def run_query(query, ingress_user_token=None):
    # start a Snowflake session as the ingress user
    try:
        with Session.builder.configs(
            get_connection_params(ingress_user_token)
        ).create() as session:
            logger.info(
                f"Snowflake connection established (id={session.session_id}). Now executing query: {query}."
            )
            try:
                res = session.sql(query).collect()
                logger.info(f"Query execution done: {query}.")
                return (
                    "[Empty Result]"
                    if len(res) == 0
                    else [", ".join(row) for row in res]
                )
            except Exception as e:
                return "Encountered an error when executing query: " + str(e)
    except Exception as e:
        return "Encountered an error when connecting to Snowflake: " + str(e)

if __name__ == '__main__':
  app.run(host=SERVICE_HOST, port=SERVER_PORT)
Copy

No código:

  • A função ui exibe o seguinte formulário da Web e trata as solicitações de consulta enviadas pelo formulário da Web.

    Formulário da Web para comunicação com o serviço de consulta.

    Esta função usa o decorador @app.route() para especificar que as solicitações de /ui serão tratadas por esta função:

    @app.route("/ui", methods=["GET", "POST"])
    def ui():
    
    Copy

    O serviço de consulta expõe publicamente o ponto de extremidade execute (consulte a especificação de serviço em linha que você forneceu ao criar o serviço), permitindo a comunicação com o serviço pela Web. Quando você carrega o URL do ponto de extremidade público com /ui anexado em seu navegador, o navegador envia uma solicitação HTTP GET para esse caminho e o servidor encaminha a solicitação para essa função. A função é executada e retorna um formulário HTML simples para o usuário inserir uma consulta.

    Depois que o usuário insere uma consulta e envia o formulário, o navegador envia uma solicitação HTTP POST para esse caminho. Como a especificação do serviço inclui o recurso executeAsCaller, o Snowflake adiciona o cabeçalho Sf-Context-Current-User-Token à solicitação de entrada e encaminha a solicitação para essa mesma função (consulte Conexão com o Snowflake usando os direitos do chamador).

    O código executa a função run_query duas vezes:

    • Como usuário de entrada. Nesse caso, o token de login é a concatenação do token do OAuth e do token de usuário de ingresso.

      token = get_login_token() + "." + ingress_user_token
      
      Copy
    • Como usuário do serviço. Nesse caso, o token de login é apenas o token do OAuth.

      token = get_login_token()
      
      Copy
  • A função readiness_probe usa o decorador @app.get() para especificar que as solicitações de /healthcheck serão tratadas por esta função:

    @app.get("/healthcheck")
    def readiness_probe():
    
    Copy

    Esta função permite que o Snowflake verifique a prontidão do serviço. Quando o contêiner é iniciado, o Snowflake deseja confirmar se o aplicativo está funcionando e se o serviço está pronto para atender às solicitações. O Snowflake envia uma solicitação HTTP GET com esse caminho (como uma análise de integridade, análise de prontidão) para garantir que apenas contêineres íntegros tenham tráfego. A função pode fazer o que você quiser.

  • A função get_logger ajuda a configurar a geração de registros.

Arquivo Docker

Este arquivo contém todos os comandos para criar uma imagem usando Docker.

ARG BASE_IMAGE=python:3.10-slim-buster
FROM $BASE_IMAGE
COPY main.py ./
COPY templates/ ./templates/
RUN pip install --upgrade pip && pip install flask snowflake-snowpark-python
CMD ["python", "main.py"]
Copy

O Dockerfile contém instruções para instalar a biblioteca do Flask no contêiner do Docker. O código em main.py depende da biblioteca Flask para lidar com solicitações HTTP.

/template/basic_ui.html

O serviço de consulta expõe publicamente o ponto de extremidade echoendpoint (consulte a especificação do serviço), permitindo a comunicação com o serviço pela Web. Quando você carrega o URL do ponto de extremidade público com /ui anexado em seu navegador, o serviço de consulta exibe esse formulário.

Formulário da Web para comunicação com o serviço de consulta.

Você pode inserir uma consulta no formulário e enviar o formulário, e o serviço retorna os resultados em uma resposta de HTTP.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Welcome to the query service!</title>
  </head>
  <body>
    <h1>Welcome to the query service!</h1>
    <form action="{{ url_for("ui") }}" method="post">
      <label for="query">query:<label><br>
      <input type="text" id="query" name="query" size="50"><br>
    </form>
    <h2>Query:</h2>
    {{ query_input }}
    <h2>Result (executed on behalf of ingress user):</h2>
    {{ query_result_ingress_user }}
    <h2>Result (executed as service user):</h2>
    {{ query_result_service_user }}
  </body>
</html>
Copy

Especificação de serviço

Snowflake usa as informações fornecidas nesta especificação para configurar e executar seu serviço.

spec:
  containers:
  - name: main
    image: /tutorial_db/data_schema/tutorial_repository/query_service:latest
    env:
      SERVER_PORT: 8000
    readinessProbe:
      port: 8000
      path: /healthcheck
  endpoints:
  - name: execute
    port: 8000
    public: true
capabilities:
  securityContext:
    executeAsCaller: true
serviceRoles:
- name: ui_usage
  endpoints:
  - execute
Copy

Na especificação do serviço, spec, capabilities e serviceRoles são os campos de nível superior.

  • spec fornece detalhes de especificação (consulte Referência de especificação de serviço). Observe que o serviço expõe um ponto de extremidade público (execute) que permite o acesso de entrada ao serviço a partir da Web pública.

  • capabilities Especifica o recurso executeAsCaller. Isso informa ao Snowflake que o aplicativo pretende usar os direitos do chamador.

  • serviceRoles especifica uma função de serviço (ui_usage) e um nome de ponto de extremidade (execute) para conceder o privilégio USAGE.

  • O campo readinessProbe identifica port e path que o Snowflake pode usar para enviar uma solicitação HTTP GET à análise de prontidão para verificar se o serviço está pronto para lidar com o tráfego.

    O código de serviço (echo_python.py) implementa a sonda de prontidão da seguinte forma:

    @app.get("/healthcheck")
    def readiness_probe():
    
    Copy

    Portanto, o arquivo de especificação inclui o campo container.readinessProbe adequadamente.

Para obter mais informações sobre especificações de serviço, consulte Referência de especificação de serviço.

Qual é o próximo passo?

Agora que você concluiu este tutorial, você pode retornar para Como trabalhar com serviços para explorar outros tópicos.