J'écris une fonction cloud à:
Remarque:
Je souhaite que ce code s'exécute de manière autonome chaque nuit pour copier une base de données de production dans un environnement de préparation. Je prévois donc de le déclencher à l'aide de Cloud Scheduler.
Si vous avez une solution meilleure / plus simple pour extraire cela dans GCP, je suis à l'écoute :)
Voici mon code (la fonction réelle est clone_db
au bas du fichier ):
Error: function crashed. Details: <HttpError 403 when requesting https://www.googleapis.com/sql/v1beta4/projects/<project_id>/instances/<instance_id>/import?alt=json returned "The service account does not have the required permissions for the bucket.">
Les choses fonctionnent parfaitement bien jusqu'à ce que j'essaye d ' importer les données exportées dans mon instance cible.
Lorsqu'elle appelle import_
, la fonction échoue avec l'erreur suivante:
from os import getenv from datetime import datetime from time import sleep from googleapiclient import discovery from googleapiclient.errors import HttpError from oauth2client.client import GoogleCredentials from google.cloud import storage GS_BUCKET = getenv("GS_BUCKET") GS_FOLDER = "sql-exports" GS_EXPORT_PATH = f"gs://{GS_BUCKET}/{GS_FOLDER}" def __sql_file_name(db: str, timestamp: datetime): return f"{db}-{timestamp.strftime('%Y-%m-%d')}.sql.gz" def __sql_file_uri(db: str, timestamp: datetime): return f"{GS_EXPORT_PATH}/{__sql_file_name(db, timestamp)}" def __export_source_db(service, project: str, timestamp: datetime, instance: str, db: str): context = { "exportContext": { "kind": "sql#exportContext", "fileType": "SQL", "uri": __sql_file_uri(db, timestamp), "databases": [db], } } return service.instances().export(project=project, instance=instance, body=context).execute() def __import_target_db(service, project: str, timestamp: datetime, instance: str, db: str): context = { "importContext": { "kind": "sql#importContext", "fileType": "SQL", "uri": __sql_file_uri(db, timestamp), "database": db, } } return service.instances().import_(project=project, instance=instance, body=context).execute() def __drop_db(service, project: str, instance: str, db: str): try: return service.databases().delete(project=project, instance=instance, database=db).execute() except HttpError as e: if e.resp.status == 404: return {"status": "DONE"} else: raise e def __create_db(service, project: str, instance: str, db: str): database = { "name": db, "project": project, "instance": instance, } return service.databases().insert(project=project, instance=instance, body=database).execute() def __update_export_permissions(file_name: str): client = storage.Client() file = client.get_bucket(GS_BUCKET).get_blob(f"{GS_FOLDER}/{file_name}") file.acl.user(getenv("TARGET_DB_SERVICE_ACCOUNT")).grant_read() file.acl.save() def __delete_sql_file(file_name: str): client = storage.Client() bucket = client.get_bucket(GS_BUCKET) bucket.delete_blob(f"{GS_FOLDER}/{file_name}") def __wait_for(operation_type, operation, service, project): if operation["status"] in ("PENDING", "RUNNING", "UNKNOWN"): print(f"{operation_type} operation in {operation['status']} status. Waiting for completion...") while operation['status'] != "DONE": sleep(1) operation = service.operations().get(project=project, operation=operation['name']).execute() print(f"{operation_type} operation completed!") def clone_db(_): credentials = GoogleCredentials.get_application_default() service = discovery.build('sqladmin', 'v1beta4', credentials=credentials) # Project ID of the project that contains the instance to be exported. project = getenv('PROJECT_ID') # Cloud SQL instance ID. This does not include the project ID. source = { "instance": getenv("SOURCE_INSTANCE_ID"), "db": getenv("SOURCE_DB_NAME") } timestamp = datetime.utcnow() print(f"Exporting database {source['instance']}:{source['db']} to Cloud Storage...") operation = __export_source_db(service, project, timestamp, **source) __wait_for("Export", operation, service, project) print("Updating exported file permissions...") __update_export_permissions(__sql_file_name(source["db"], timestamp)) print("Done.") target = { "instance": getenv("TARGET_INSTANCE_ID"), "db": getenv("TARGET_DB_NAME") } print(f"Dropping target database {target['instance']}:{target['db']}") operation = __drop_db(service, project, **target) __wait_for("Drop", operation, service, project) print(f"Creating database {target['instance']}:{target['db']}...") operation = __create_db(service, project, **target) __wait_for("Creation", operation, service, project) print(f"Importing data into {target['instance']}:{target['db']}...") operation = __import_target_db(service, project, timestamp, **target) __wait_for("Import", operation, service, project) print("Deleting exported SQL file") __delete_sql_file(__sql_file_name(source["db"], timestamp)) print("Done.")
J'ai lu cette erreur dans de nombreuses autres questions et réponses ici et sur le Web , mais je n'arrive pas à comprendre comment faire fonctionner les choses.
Voici ce que j'ai fait:
Éditeur de projet
défini dans IAM Storage Object Admin
. J'ai essayé diverses autres combinaisons de rôles (lecteur / propriétaire hérité, visionneuse d'objets de stockage, ...) en vain Fait intéressant, lorsque j'essaie d'importer manuellement le même fichier sur la même instance à partir de la console GCP Cloud SQL, les choses fonctionnent parfaitement bien.
Une fois que c'est fait, je peux voir que les autorisations de mon fichier exporté ont été mises à jour pour inclure le compte de service de l'instance en tant que Reader
, tout comme je l'ai fait dans mon code à la fin pour essayer de reproduire le comportement.
Que me manque-t-il donc ici?
Quelles autorisations dois-je définir, pour quel compte de service, pour que cela fonctionne?
3 Réponses :
L'instance CloudSQL s'exécute sous un compte de service Google qui ne fait pas partie de votre projet.
Vous devez trouver le compte de service de votre instance - Cloud SQL-> nom du cluster -> Compte de service
Ensuite, vous prenez le compte de service ci-dessus et lui donnez l'autorisation d'écriture / lecture pour le bucket concerné
C'est ce que je voulais dire lorsque j'ai dit que j'avais essayé ce qui suit: "Le compte de service de l'instance Cloud SQL cible est ajouté aux autorisations du bucket en tant qu'administrateur d'objets de stockage. J'ai essayé diverses autres combinaisons de rôles (lecteur / propriétaire hérité, visionneuse d'objets de stockage, ...) en vain "
J'ai eu le même problème et j'ai essayé beaucoup de choses différentes. Même après avoir donné les droits de propriétaire du compte de service DB sur le projet, le bucket et les fichiers SQL, cela ne fonctionnait pas lors de l'importation / exportation depuis / vers d'autres fichiers fonctionnait toujours.
J'ai donc fini par renommer mon fichier d'importation et, étonnamment, cela a fonctionné (l'ancien nom de fichier était assez long et contenait des traits de soulignement comme dans votre exemple). Mais je ne trouve rien dans la documentation sur ces limitations de dénomination et à ce stade, je ne peux même pas dire si ce problème est lié au nom de fichier ou à l'utilisation de traits de soulignement. Mais cela vaut peut-être la peine d'essayer.
Mec, c'est incroyable: D Je suis sur ce sujet depuis un mois avec le support de Google, je suis remonté aux ingénieurs de l'équipe Cloud Functions et nous n'avons pas pu trouver le problème. Le message d'erreur est clairement trompeur et je suppose qu'il y a un joli bogue à corriger; Je vais faire un suivi avec eux avec cette solution de contournement; Merci!!
Le problème vient de votre code et non de Cloud SQL.
Lorsque vous appelez la fonction _import_target_db
, vous recherchez un fichier qui n'existe pas dans votre bucket Cloud Storage.
Entrer dans les détails:
Vous avez exporté la base de données vers votre bucket avec le nom:
gs://yourBucket/sql-exports/exportedDatabaseName-yyyy-mm-dd.sql.gz
Cependant, lorsque vous essayez de l'importer, la fonction d'importation recherche un fichier nommé:
gs: //yourBucket/sql-exports/importDatabaseName-yyyy-mm-dd.sql.gz
Ce fichier n'existe pas dans votre bucket et pour des raisons de sécurité, une erreur 403 Forbidden
est renvoyée.
Oui, vous avez absolument raison! Nous l'avons repéré par la suite et j'ai oublié de mettre à jour la question ... l'autorisation d'accès m'a jeté et je n'ai pas remis en question la validité de mon code quand cela s'est produit, ce qui en fait un cas compliqué pour rien 😅 La réponse de Google à «pourrait-il y avoir un meilleur message d'erreur »est que pour des raisons de sécurité, il vaut mieux ne pas divulguer les informations de« fichier non trouvé »par rapport à« aucune permission d'y accéder », ce qui est logique. En tout cas merci de regarder dedans, bonne prise!