Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions bigquery_etl/cli/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ def _update_table_schema(file_path: Path, options: dict):
tmp_dataset="tmp", # Default dataset for temporary tables during schema updates
tmp_tables={},
use_cloud_function=options["use_cloud_function"],
billing_project=(project_id if not options["use_cloud_function"] else None),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: this (<artifact project> if not use_cloud_function else None) expression is now duplicated at three deploy call sites — here, bigquery_etl/cli/deploy.py:1280, and bigquery_etl/deploy.py:107 — and each derives the project differently (extract_from_query_path here vs. file_path.parent.parent.parent.name in the other two).

Since correct --target behaviour now depends on every authenticated dry run in the deploy path carrying this argument, a future deploy-path dry run that omits it will silently bill to mozdata and 403 for sandbox service accounts. Consider a single helper (e.g. dryrun_billing_project(project, use_cloud_function) in bigquery_etl/dryrun.py) that the three sites call, so the policy lives in one place.

respect_dryrun_skip=options["respect_dryrun_skip"],
is_init=False,
credentials=options["credentials"],
Expand Down Expand Up @@ -1270,17 +1271,21 @@ def _deploy_table_artifact(file_path: Path, options: dict):
if not options["table_force"] and str(file_path).endswith(".sql"):
client = bigquery.Client(credentials=options["credentials"])
try:
table_name = file_path.parent.name
dataset_name = file_path.parent.parent.name
project_name = file_path.parent.parent.parent.name
query_schema = Schema.from_query_file(
file_path,
use_cloud_function=options["use_cloud_function"],
billing_project=(
project_name if not options["use_cloud_function"] else None
),
respect_skip=options["respect_dryrun_skip"],
sql_dir=options["sql_dir"],
client=client,
id_token=options["id_token"],
)
if not existing_schema.equal(query_schema):
dataset_name = file_path.parent.parent.name
table_name = file_path.parent.name
raise FailedDeployException(
f"Query {file_path} does not match schema in {schema_path}. "
f"Run `./bqetl query schema update {dataset_name}.{table_name}`"
Expand Down
3 changes: 3 additions & 0 deletions bigquery_etl/cli/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2559,6 +2559,7 @@ def _update_query_schema(
id_token=None,
use_dataset_schema=False,
use_global_schema=False,
billing_project=None,
):
"""
Update the schema of a specific query file.
Expand Down Expand Up @@ -2651,6 +2652,7 @@ def _update_query_schema(
query_file_path,
content=sql_content,
use_cloud_function=use_cloud_function,
billing_project=billing_project,
respect_skip=respect_dryrun_skip,
sql_dir=sql_dir,
credentials=credentials,
Expand Down Expand Up @@ -2731,6 +2733,7 @@ def _update_query_schema(
table_name,
partitioned_by=partitioned_by,
use_cloud_function=use_cloud_function,
billing_project=billing_project,
respect_skip=respect_dryrun_skip,
credentials=credentials,
id_token=id_token,
Expand Down
1 change: 1 addition & 0 deletions bigquery_etl/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def deploy_table(
query_schema = Schema.from_query_file(
artifact_file,
use_cloud_function=use_cloud_function,
billing_project=(project_name if not use_cloud_function else None),
respect_skip=respect_dryrun_skip,
sql_dir=sql_dir,
client=client,
Expand Down
24 changes: 13 additions & 11 deletions bigquery_etl/dryrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,18 @@ def __init__(
self.project = project
self.dataset = dataset
self.table = table
self.billing_project = billing_project
# if using cloud function and billing project isn't set, randomly select project to use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: the comment now only describes the use_cloud_function half of the branch it sits above. Suggest something like # if the billing project isn't set, use the configured default (a random cloud function project, or default_billing_project for authenticated dry runs).

self.billing_project = (
billing_project
if billing_project or not use_cloud_function
else random.choice(
ConfigLoader.get("dry_run", "default_projects", fallback=[None])
if not self.billing_project:
self.billing_project = (
random.choice(
ConfigLoader.get(
"dry_run", "cloud_function_billing_projects", fallback=[None]
)
)
if use_cloud_function
else ConfigLoader.get("dry_run", "default_billing_project")

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Rather than basing the workaround on whether a target is active, I opted to specify the billing project when doing deploys. What do you think now @claude?

)
)
try:
self.metadata = Metadata.of_query_file(self.sqlfile)
except FileNotFoundError:
Expand Down Expand Up @@ -474,17 +478,15 @@ def dry_run_result(self):
)
result = json.load(r)
else:
# Prefer billing project if provided, otherwise use the project from the SQL file
self.client.project = (
self.billing_project if self.billing_project else project
)
job_config = bigquery.QueryJobConfig(
dry_run=True,
use_query_cache=False,
default_dataset=f"{project}.{dataset}",
query_parameters=query_parameters,
)
job = self.client.query(sql, job_config=job_config)
job = self.client.query(
sql, job_config=job_config, project=self.billing_project
)
try:
dataset_labels = self.client.get_dataset(job.default_dataset).labels
except Exception as e:
Expand Down
3 changes: 2 additions & 1 deletion bqetl_project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ dry_run:
- sql/moz-fx-data-shared-prod/firefox_accounts_derived/event_types_history_v1/query.sql
# Tests
- sql/moz-fx-data-test-project/test/simple_view/view.sql
default_projects: # projects to use for query dry run jobs
default_billing_project: mozdata # billing project to use for authenticated query dry run jobs
cloud_function_billing_projects: # billing projects to use for query dry run jobs from the cloud function
- moz-fx-data-backfill-10
- moz-fx-data-backfill-11
- moz-fx-data-backfill-12
Expand Down
Loading