Créer un service Snowpark Container Services qui utilise les droits de l’appelant¶
Introduction¶
Dans ce tutoriel, vous découvrirez la conception d’un service, la présentation d’une UI Web, qui utilise la fonction des droits de l’appelant lors de l’exécution des requêtes SQL pour le compte des utilisateurs.
Vous créez un service (nommé query_service
) qui exécute une requête fournie dans la requête. Par défaut, les conteneurs d’applications se connectent à Snowflake en tant qu’utilisateur du service en utilisant le rôle de propriétaire du service. Mais cette application utilise la fonction des droits de l’appelant pour se connecter au point de terminaison de service en tant qu’utilisateur final et en utilisant les privilèges accordés à cet utilisateur.
Lors des tests, vous utilisez le service à partir d’un navigateur Web car la fonction des droits de l’appelant n’est prise en charge que lors de l’accès à un service utilisant l’entrée réseau. La fonction des droits de l’appelant n’est pas disponible lors de l’accès à un service à l’aide d’une fonction de service.
Le service effectue les opérations suivantes :
Expose un point de terminaison public.
Lorsqu’un utilisateur se connecte au point de terminaison, le service met à sa disposition une UI Web pour lui permettre d’effectuer une requête. Le service exécute la requête dans Snowflake et renvoie les résultats. Dans ce tutoriel, vous exécutez la commande suivante SQL :
SELECT CURRENT_USER(), CURRENT_ROLE();
La commande renvoie le nom de l’utilisateur actuellement connecté et le rôle actuellement actif, qui dépendent tous deux de l’utilisation ou non des droits de l’appelant.
Lorsque les droits de l’appelant sont utilisés, le service se connecte à Snowflake en tant qu’utilisateur appelant et dans le rôle par défaut de l’utilisateur. La commande renvoie votre nom d’utilisateur et votre rôle par défaut.
Lorsque les droits de l’appelant ne sont pas utilisés, le comportement par défaut s’applique : le service se connecte à Snowflake en tant qu’utilisateur du service et dans le rôle de propriétaire du service. Par conséquent, la commande renvoie le nom de l’utilisateur du service sous la forme :
SF$SERVICE$unique-id
,TEST_ROLE
.
Ce tutoriel comporte deux parties :
Partie 1 : créez et testez un service. Vous téléchargez le code fourni pour ce tutoriel et suivez les instructions étape par étape :
Téléchargez le code de service pour ce tutoriel.
Créez une image Docker pour Snowpark Container Services, et chargez l’image dans un référentiel de votre compte.
Créez un service.
Communiquez avec le service en utilisant l’entrée réseau pour vous connecter au point de terminaison public que le service expose. À l’aide d’un navigateur, vous vous connectez au point de terminaison public et exécutez la commande SELECT CURRENT_USER() ;. Vérifiez la sortie de la commande pour vous assurer que le conteneur a exécuté la commande en tant qu’utilisateur connecté.
Partie 2 : comprendre le service. Cette section donne un aperçu du code du service et met en évidence la manière dont le code de l’application utilise les droits de l’appelant.
Préparer¶
Suivez la Configuration commune pour configurer les conditions préalables et créer les ressources Snowflake qui sont requises pour tous les tutoriels Snowpark Container Services fournis dans cette documentation.
Télécharger le code de service¶
Le code (une application Python) est fourni pour créer le service de requête.
Téléchargez
SnowparkContainerServices-Tutorials.zip
.Décompressez le contenu, qui comprend un répertoire pour chaque tutoriel. Le répertoire
Tutorial-6-callers-rights
contient les fichiers suivants :Dockerfile
main.py
templates/basic_ui.html
Créer une image et la télécharger¶
Construisez une image pour la plateforme linux/amd64 prise en charge par Snowpark Container Services, puis chargez l’image dans le référentiel d’images de votre compte (voir Configuration commune).
Vous aurez besoin d’informations sur le référentiel (l’URL du référentiel et le nom d’hôte du registre) avant de pouvoir construire et charger l’image. Pour plus d’informations, voir Registre et référentiels.
Obtenir des informations sur le référentiel
Pour obtenir l’URL du référentiel, exécutez la commande SQL SHOW IMAGE REPOSITORIES.
SHOW IMAGE REPOSITORIES;
La colonne
repository_url
de la sortie fournit l’URL. En voici un exemple :<orgname>-<acctname>.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository
Le nom d’hôte dans l’URL du référentiel est le nom d’hôte du registre. En voici un exemple :
<orgname>-<acctname>.registry.snowflakecomputing.com
Construire l’image et la charger dans le référentiel
Ouvrez une fenêtre de terminal et accédez au répertoire contenant les fichiers que vous avez décompressés.
Pour créer une image Docker, exécutez la commande
docker build
suivante à l’aide de la CLI Docker. Notez que la commande spécifie le répertoire de travail actuel (.
) commePATH
pour les fichiers à utiliser pour la conception de l’image.docker build --rm --platform linux/amd64 -t <repository_url>/<image_name> .
Pour
image_name
, utilisezquery_service:latest
.
Exemple
docker build --rm --platform linux/amd64 -t myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest .
Chargez l’image dans le référentiel de votre compte Snowflake. Pour que Docker télécharge une image en votre nom dans votre référentiel, vous devez d’abord authentifier Docker avec Snowflake.
Pour authentifier Docker auprès du registre Snowflake, exécutez la commande suivante :
docker login <registry_hostname> -u <username>
Pour
username
, indiquez votre nom d’utilisateur Snowflake. Docker vous demandera votre mot de passe.
Pour charger l’image, exécutez la commande suivante :
docker push <repository_url>/<image_name>
Exemple
docker push myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest
Créez un service¶
Dans cette section, vous créez un service (query_service).
Vérifiez que le pool de calcul est prêt et que vous êtes dans le bon contexte pour créer le service.
Auparavant, vous avez défini le contexte dans l’étape Configuration commune. Pour vous assurer que vous êtes dans le bon contexte pour les instructions SQL de cette étape, exécutez ce qui suit :
USE ROLE test_role; USE DATABASE tutorial_db; USE SCHEMA data_schema; USE WAREHOUSE tutorial_warehouse;
Pour s’assurer que le pool de calcul que vous avez créé dans la configuration commune est prêt, exécutez
DESCRIBE COMPUTE POOL
, et vérifiez questate
estACTIVE
ouIDLE
. Si lestate
estSTARTING
, vous devez attendre que lestate
devienneACTIVE
ouIDLE
.
DESCRIBE COMPUTE POOL tutorial_compute_pool;
Pour créer le service, exécutez la commande suivante en utilisant
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 $$;
Note
Si un service portant ce nom existe déjà, utilisez la commande DROP SERVICE pour supprimer le service précédemment créé, puis créez ce service.
Exécutez les commandes SQL suivantes pour obtenir des informations détaillées sur le service que vous venez de créer. Pour plus d’informations, voir Snowpark Container Services : utilisation des tâches.
Pour dresser la liste des services de votre compte, exécutez la commande SHOW SERVICES :
SHOW SERVICES;
Pour obtenir le statut de votre service, exécutez la commande SHOW SERVICE CONTAINERS IN SERVICE :
SHOW SERVICE CONTAINERS IN SERVICE query_service;
Pour obtenir des informations sur votre service, exécutez la commande DESCRIBE SERVICE :
DESCRIBE SERVICE query_service;
Utilisez le service¶
Dans cette section, vous vérifiez que les droits de l’appelant configurés pour le service fonctionnent. Vous vous connectez au point de terminaison public à partir d’un navigateur, vous exécutez une requête et vous vérifiez que la session Snowflake créée par le service opère en tant qu’utilisateur appelant et non en tant qu’utilisateur du service.
Tout d’abord, pour configurer le contexte des instructions SQL de cette section, exécutez ce qui suit :
USE ROLE test_role;
USE DATABASE tutorial_db;
USE SCHEMA data_schema;
USE WAREHOUSE tutorial_warehouse;
Le service expose un point de terminaison public (voir la spécification en ligne fournie dans la commande CREATE SERVICE) ; par conséquent, connectez-vous d’abord au point de terminaison à l’aide d’un navigateur web, puis utilisez l’UI Web que le service expose à Internet pour envoyer des requêtes au point de terminaison de service.
Trouvez l’URL du point de terminaison public auquel le service est exposé :
SHOW ENDPOINTS IN SERVICE query_service;
La colonne
ingress_url
de la réponse fournit l’URL.Exemple
p6bye-myorg-myacct.snowflakecomputing.app
Ajoutez
/ui
à l’URL du point de terminaison et collez-la dans le navigateur Web. Le service exécute alors la fonctionui()
(voirmain.py
).Notez que la première fois que vous accédez à l’URL du point de terminaison, il vous sera demandé de vous connecter à Snowflake.
Utilisez le même utilisateur que celui que vous avez utilisé pour créer le service. Une fois la connexion réussie, le service affiche l’UI Web suivant.
Saisissez la commande suivante dans la zone de texte et appuyez sur la touche Entrée pour voir les résultats.
SELECT CURRENT_USER(), CURRENT_ROLE()DONE;
Parce que vous avez inclus la capacité
executeAsCaller
dans la spécification du service, lorsqu’une requête arrive, Snowflake insère l’en-têteSf-Context-Current-User-Token
dans la requête et transmet ensuite la requête à votre point de terminaison de service.À des fins d’illustration, le code de service de ce tutoriel exécute la requête à la fois en tant qu’appelant et en tant qu’utilisateur du service.
Exécute la requête pour le compte de l’appelant (utilisateur entrant) : dans ce cas, le code utilise le jeton utilisateur que Snowflake fournit pour construire un jeton de connexion pour se connecter à Snowflake. Le service utilise donc les droits de l’appelant. Snowflake exécute la requête pour le compte de l’appelant, en affichant le nom de l’appelant et le nom de son rôle actif dans le résultat de la requête. Par exemple :
['TESTUSER, PUBLIC']
Exécute la requête au nom de l’utilisateur du service : dans ce cas, le code n’utilise pas le jeton utilisateur que Snowflake fournit dans la requête lors de la construction du jeton de connexion pour se connecter à Snowflake. Ainsi, le service n’utilise pas les droits de l’appelant, ce qui amène Snowflake à exécuter la requête au nom de l’utilisateur du service. Le résultat de la requête indique le nom de l’utilisateur du service (qui est le même que le nom du service) et le rôle actif.
['QUERY_SERVICE, TEST_ROLE']
Lorsque le service exécute la requête (SELECT CURRENT_USER(), CURRENT_ROLE();
) pour le compte de l’appelant, Snowflake n’a pas besoin de l’entrepôt de l’utilisateur pour exécuter cette simple requête. Par conséquent, le service n’a pas eu besoin d’appelant pour obtenir des. Dans la section suivante, le service exécute une requête non triviale au nom de l’utilisateur appelant qui exige que vous accordiez les autorisations de l’appelant au service.
Note
Vous pouvez accéder au point de terminaison d’entrée de manière programmatique. Pour un exemple de code, voir Accès aux points de terminaison publics depuis l’extérieur de Snowflake et authentification. Notez que vous devez ajouter /ui
à l’URL du point de terminaison dans le code afin que Snowflake puisse acheminer la demande vers la fonction ui()
dans le code de service.
Utiliser le service avec des autorisations à l’appelant¶
Dans cette section, le service exécute la requête suivante pour le compte de l’appelant (l’utilisateur qui se connecte au point de terminaison d’entrée du service).
SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
Le service n’a pas l’autorisation d’accéder à la table et n’a pas l’autorisation d’exécuter la requête dans l’entrepôt par défaut. Pour permettre au service d’exécuter cette requête au nom de l’appelant, vous accordez les autorisations nécessaires de l’appelant au service.
Pour illustrer ce scénario, vous créez un nouveau rôle (ingress_user_role
) et une table (ingress_user_table
) accessible au nouveau rôle mais pas au rôle de propriétaire du service (test_role
). Par conséquent, lorsque le service tente d’exécuter la requête en utilisant les identifiants du service, Snowflake renvoie une erreur. Mais lorsque le service exécute la requête pour le compte de l’utilisateur, Snowflake exécute la requête et renvoie le résultat.
Créer des rôles et des ressources¶
Créez un rôle (
ingress_user_role
) et une base de données (ingress_user_db
) auxquels seul ce rôle peut accéder. Vous accordez ensuite ce rôle à votre utilisateur, afin qu’il puisse se connecter au point de terminaison public du service et effectuer des requêtes dans cette table.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;
Créez une table (
ingress_user_table
) à laquelle seul le rôleingress_user_role
peut accéder.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' );
Notez que lorsque le service tente d’interroger la table pour le compte de l’appelant, il n’opère qu’en tant que
test_role
, le rôle qui a été utilisé pour créer le service (le rôle de propriétaire du service). Ce rôle n’a pas l’autorisation d’accéder à la table des utilisateurs.Accordez des autorisations à l’appelant pour le rôle de propriétaire du service (
test_role
) pour interroger les tables de la base de donnéesingress_user_db
. Ce privilège permet au service d’interroger les tables de cette base de données uniquement si les conditions suivantes sont remplies :Le service utilise une session de droits de l’appelant.
Dans la session, l’appelant a également l’autorisation d’exécuter ces requêtes.
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;
Configurez l’entrepôt par défaut et les rôles par défaut secondaires.
Lorsqu’une session est créée pour un utilisateur, Snowflake active le rôle principal par défaut, les rôles secondaires par défaut et l’entrepôt par défaut de l’utilisateur connecté. Dans ce tutoriel,
vous définissez
DEFAULT_SECONDARY_ROLES
sur ALL afin que lorsqu’une session est créée pour l’utilisateur actuel, Snowflake définisse les rôles secondaires actuels comme étant tous les rôles accordés à l’utilisateur.Vous avez également défini l’entrepôt par défaut sur
tutorial_warehouse
où les requêtesingress_user_table
sont exécutées.
ALTER USER SET DEFAULT_SECONDARY_ROLES = ('ALL'); ALTER USER SET DEFAULT_WAREHOUSE = TUTORIAL_WAREHOUSE;
Remarques :
Dans ce tutoriel, vous vous connectez au point de terminaison public du service. L’utilisateur a le
test_role
pour rôle principal etingress_user_role
pour rôle secondaire. Cela permet à la session de faire tout ce que leingress_user_role
autorise.Le rôle par défaut et l’entrepôt par défaut n’affectent que le rôle et l’entrepôt activés lorsque le service établit une session au nom de votre utilisateur. Une fois la session de droits de l’appelant établie, vous ne pouvez pas modifier le rôle, mais vous pouvez modifier l’entrepôt.
Utiliser le service et tester les autorisations accordées à l’appelant¶
Trouvez l’URL du point de terminaison public auquel le service est exposé :
SHOW ENDPOINTS IN SERVICE tutorial_db.data_schema.query_service;
La colonne
ingress_url
de la réponse fournit l’URL.Exemple
p6bye-myorg-myacct.snowflakecomputing.app
Ajoutez
/ui
à l’URL du point de terminaison et collez-la dans le navigateur Web. Le service exécute alors la fonctionui()
(voirecho_service.py
) : notez que la première fois que vous accédez à l’URL du point de terminaison, il vous sera demandé de vous connecter à Snowflake. Pour ce test, utilisez le même utilisateur que celui que vous avez utilisé pour créer le service afin de vous assurer que l’utilisateur dispose des privilèges nécessaires :Utilisez le même utilisateur que celui que vous avez utilisé pour créer le service. Une fois la connexion réussie, le service affiche l’UI Web suivant.
Saisissez la commande suivante dans la zone de texte et appuyez sur la touche Entrée pour voir les résultats.
SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
À des fins d’illustration, le code de service de ce tutoriel exécute la requête à la fois en tant qu’appelant et en tant qu’utilisateur du service.
Exécute la requête pour le compte de l’appelant (utilisateur entrant) : dans ce cas, le code utilise le jeton utilisateur fourni par Snowflake pour construire un jeton de connexion pour se connecter à Snowflake. Le service utilise donc les droits de l’appelant. Snowflake exécute la requête pour le compte de l’appelant. Comme l’appelant utilise le
ingress_user_role role
qui a le privilège d’interroger la tableingress_user_table
, la requête renvoie une seule ligne dans le résultat :['this table is only accessible to ingress_user_role']
Exécute la requête au nom de l’utilisateur du service : dans ce cas, le code n’utilise pas le jeton utilisateur que Snowflake fournit dans la requête lors de la construction du jeton de connexion pour se connecter à Snowflake. Ainsi, Snowflake exécute la requête pour le compte de l’utilisateur du service. Comme le propriétaire du service utilise le
test_role
par défaut, qui n’a pas l’autorisation d’interroger la table, une erreur s’affiche :Encountered an error when executing query:... SQL compilation error: Database 'INGRESS_USER_DB' does not exist or not authorized.
Nettoyage¶
Vous devez supprimer les ressources facturables que vous avez créées. Pour plus d’informations, voir l’étape 5 dans le tutoriel 3.
Révision du code de service¶
Cette section couvre les sujets suivants :
Examen du code du tutoriel : examinez les fichiers de code qui mettent en œuvre le service de requête.
Examen du code du tutoriel¶
Le fichier zip que vous avez chargé à l’étape 1 comprend les fichiers suivants :
Dockerfile
main.py
templates/basic_ui.html
Vous utilisez également la spécification de service lors de la création du service. La section suivante explique comment ces composants de code fonctionnent ensemble pour créer le service.
fichier main.py¶
Ce fichier Python contient le code qui implémente un serveur HTTP minimal qui exécute une requête dans la requête et renvoie les résultats de la requête. Le code fournit une interface Web utilisateur (UI) pour la soumission des requêtes d’écho.
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)
Dans le code :
La fonction
ui
affiche le formulaire web suivant et traite les requêtes soumises à partir du formulaire web.Cette fonction utilise le décorateur
@app.route()
pour spécifier que les requêtes pour/ui
sont traitées par cette fonction :@app.route("/ui", methods=["GET", "POST"]) def ui():
Le service de requête expose publiquement le point de terminaison
execute
(voir la spécification de service en ligne que vous avez fournie lors de la création du service), ce qui permet de communiquer avec le service sur le Web. Lorsque vous chargez l’URL du point de terminaison public avec /ui ajouté dans votre navigateur, celui-ci envoie une requête HTTP GET pour ce chemin, et le serveur achemine la requête vers cette fonction. La fonction s’exécute et renvoie un simple formulaire HTML permettant à l’utilisateur de saisir une requête.Après que l’utilisateur a saisi une requête et soumis le formaulaire, le navigateur envoie une requête HTTP POST pour ce chemin. Comme la spécification du service inclut la capacité
executeAsCaller
, Snowflake ajoute l’en-têteSf-Context-Current-User-Token
à la requête entrante et transmet la requête à cette même fonction (voir Connexion à Snowflake en utilisant les droits de l’appelant).Le code exécute deux fois la fonction
run_query
:En tant qu’utilisateur d’entrée. Dans ce cas, le jeton de connexion est la concaténation du jeton OAuth et du jeton de l’utilisateur d’entrée.
token = get_login_token() + "." + ingress_user_token
En tant qu’utilisateur du service. Dans ce cas, le jeton de connexion n’est que le jeton OAuth.
token = get_login_token()
La fonction
readiness_probe
utilise le décorateur@app.get()
pour spécifier que les requêtes pour/healthcheck
sont traitées par cette fonction :@app.get("/healthcheck") def readiness_probe():
Cette fonction permet à Snowflake de vérifier l’état de préparation du service. Lorsque le conteneur démarre, Snowflake veut confirmer que l’application fonctionne et que le service est prêt à répondre aux requêtes. Snowflake envoie une requête HTTP GET avec ce chemin (en tant que sonde de santé, probe readiness) pour s’assurer que seuls les conteneurs sains servent le trafic. La fonction peut faire ce que vous voulez.
La fonction
get_logger
permet de mettre en place la journalisation.
Dockerfile¶
Ce fichier contient toutes les commandes pour construire une image en utilisant 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"]
Le Dockerfile contient des instructions pour installer la bibliothèque Flask dans le conteneur Docker. Le code de main.py
s’appuie sur la bibliothèque Flask pour traiter les requêtes HTTP.
/template/basic_ui.html¶
Le service de requête expose publiquement le point de terminaison echoendpoint
(voir la spécification du service), ce qui permet de communiquer avec le service sur le Web. Lorsque vous chargez l’URL du point de terminaison public avec /ui
en annexe dans votre navigateur, le service de requête affiche cette forme.

Vous pouvez saisir une requête dans le formulaire et soumettre le formulaire, et le service renvoie le résulata dans une réponse 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>
Spécification du service¶
Snowflake utilise les informations que vous fournissez dans cette spécification pour configurer et faire fonctionner votre service.
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
Dans la spécification de service, les champs spec
, capabilities
et serviceRoles
sont les champs de premier niveau.
spec
fournit des détails sur les spécifications (voir Référence Spécification de service). Notez que le service expose un point de terminaison public (execute
) qui permet l’accès au service à partir du Web public.capabilities
Spécifie la capacitéexecuteAsCaller
. Cela indique à Snowflake que l’application a l’intention d’utiliser les droits de l’appelant.serviceRoles
spécifie un rôle de service (ui_usage
) et un point de terminaison (execute
) pour lesquels le privilège USAGE doit être accordé.Le champ
readinessProbe
identifie lesport
etpath
que Snowflake peut utiliser pour envoyer une requête HTTP GET à la probe readiness afin de vérifier que le service est prêt à gérer le trafic.Le code de service (
echo_python.py
) met en œuvre la probe readiness comme suit :@app.get("/healthcheck") def readiness_probe():
Le fichier de spécification inclut donc le champ
container.readinessProbe
en conséquence.
Pour plus d’informations sur les spécifications de service, voir Référence Spécification de service.
Quelle est la prochaine étape ?¶
Maintenant que vous avez terminé ce tutoriel, vous pouvez retourner à Travailler avec les services pour explorer d’autres sujets.