Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support BigQuery authorized routines #6680

Merged
merged 11 commits into from
Nov 8, 2022
65 changes: 65 additions & 0 deletions mmv1/products/bigquery/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,30 @@ objects:
but additional target types may be added in the future. Possible values: VIEWS
item_type: Api::Type::String
required: true
- !ruby/object:Api::Type::NestedObject
name: 'routine'
description: |
A routine from a different dataset to grant access to. Queries
executed against that routine will have read access to tables in
this dataset. The role field is not required when this field is
set. If that routine is updated by any user, access to the routine
needs to be granted again via an update operation.
properties:
- !ruby/object:Api::Type::String
name: 'datasetId'
description: The ID of the dataset containing this table.
required: true
- !ruby/object:Api::Type::String
name: 'projectId'
description: The ID of the project containing this table.
required: true
- !ruby/object:Api::Type::String
name: 'routineId'
description: |
The ID of the routine. The ID must contain only letters (a-z,
A-Z), numbers (0-9), or underscores (_). The maximum length
is 256 characters.
required: true
- !ruby/object:Api::Type::Integer
name: 'creationTime'
output: true
Expand Down Expand Up @@ -275,6 +299,7 @@ objects:
- iamMember
- view
- dataset
- routine
description: |
Gives dataset access for a single entity. This resource is intended to be used in cases where
it is not possible to compile a full list of access blocks to include in a
Expand Down Expand Up @@ -317,6 +342,7 @@ objects:
- iam_member
- view
- dataset
- routine
- !ruby/object:Api::Type::String
name: 'groupByEmail'
description: An email address of a Google Group to grant access to.
Expand All @@ -328,6 +354,7 @@ objects:
- iam_member
- view
- dataset
- routine
- !ruby/object:Api::Type::String
name: 'domain'
description: |
Expand All @@ -341,6 +368,7 @@ objects:
- iam_member
- view
- dataset
- routine
- !ruby/object:Api::Type::String
name: 'specialGroup'
description: |
Expand All @@ -365,6 +393,7 @@ objects:
- iam_member
- view
- dataset
- routine
- !ruby/object:Api::Type::String
name: 'iamMember'
description: |
Expand All @@ -378,6 +407,7 @@ objects:
- iam_member
- view
- dataset
- routine
- !ruby/object:Api::Type::NestedObject
name: 'view'
description: |
Expand All @@ -394,6 +424,7 @@ objects:
- iam_member
- view
- dataset
- routine
properties:
- !ruby/object:Api::Type::String
name: 'datasetId'
Expand Down Expand Up @@ -422,6 +453,7 @@ objects:
- iam_member
- view
- dataset
- routine
properties:
- !ruby/object:Api::Type::NestedObject
name: 'dataset'
Expand All @@ -444,6 +476,39 @@ objects:
but additional target types may be added in the future. Possible values: VIEWS
item_type: Api::Type::String
required: true
- !ruby/object:Api::Type::NestedObject
name: 'routine'
description: |
A routine from a different dataset to grant access to. Queries
executed against that routine will have read access to tables in
this dataset. The role field is not required when this field is
set. If that routine is updated by any user, access to the routine
needs to be granted again via an update operation.
exactly_one_of:
- user_by_email
- group_by_email
- domain
- special_group
- iam_member
- view
- dataset
- routine
properties:
- !ruby/object:Api::Type::String
name: 'datasetId'
description: The ID of the dataset containing this table.
required: true
- !ruby/object:Api::Type::String
name: 'projectId'
description: The ID of the project containing this table.
required: true
- !ruby/object:Api::Type::String
name: 'routineId'
description: |
The ID of the routine. The ID must contain only letters (a-z,
A-Z), numbers (0-9), or underscores (_). The maximum length
is 256 characters.
required: true
- !ruby/object:Api::Resource
name: 'Job'
kind: 'bigquery#job'
Expand Down
15 changes: 15 additions & 0 deletions mmv1/products/bigquery/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ overrides: !ruby/object:Overrides::ResourceOverrides
private: "private"
public: "public"
account_name: "bqowner"
- !ruby/object:Provider::Terraform::Examples
name: "bigquery_dataset_authorized_routine"
primary_resource_id: "dataset"
Copy link
Member

Choose a reason for hiding this comment

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

This needs to match the second part of a dataset resource's terraform id. For example, for:

resource "google_bigquery_dataset" "public" {
    // args
}

the resource address is google_bigquery_dataset.public, so primary_resource_id would need to be public.

The "primary resource" will be imported as part of the test. In this case, the important thing is to make sure that imports work correctly for the routine field (which is on the private dataset)

Suggested change
primary_resource_id: "dataset"
primary_resource_id: "private"

vars:
private: "private"
Copy link
Member

Choose a reason for hiding this comment

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

These need a hyphen so that they automatically get prefixed with tf-test (so that they can be easily cleaned up after the tests run, if something goes wrong.)

Suggested change
private: "private"
private: "private-dataset"
public: "public-dataset"

public: "public"
Tei1988 marked this conversation as resolved.
Show resolved Hide resolved
test_env_vars:
service_account: :SERVICE_ACCT
virtual_fields:
- !ruby/object:Api::Type::Boolean
name: 'delete_contents_on_destroy'
Expand Down Expand Up @@ -109,6 +117,13 @@ overrides: !ruby/object:Overrides::ResourceOverrides
vars:
private: "private"
public: "public"
- !ruby/object:Provider::Terraform::Examples
name: "bigquery_dataset_access_authorized_routine"
skip_test: true # not importable
primary_resource_id: "access"
Copy link
Member

Choose a reason for hiding this comment

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

In this example, the routine is actually set on google_bigquery_dataset_access.authorized_routine, which is not the expected resource type, so you'll need to set that explicitly as well.

Suggested change
primary_resource_id: "access"
primary_resource_type: "google_bigquery_dataset_access"
primary_resource_id: "authorized_routine"

vars:
private: "private"
Copy link
Member

Choose a reason for hiding this comment

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

same here

public: "public"
properties:
datasetId: !ruby/object:Overrides::Terraform::PropertyOverride
ignore_read: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
resource "google_bigquery_dataset" "public" {
dataset_id = "<%= ctx[:vars]['public'] %>"
description = "This dataset is public"
}

resource "google_bigquery_routine" "public" {
dataset_id = google_bigquery_dataset.public.dataset_id
routine_id = "sample_routine"
Copy link
Member

Choose a reason for hiding this comment

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

Is this automatically deleted if the dataset is deleted? And/or is there any issue with quotas for this resource, or name conflicts if parallel test runs happen at the same time? Otherwise it might be worth making this use a var as well.

routine_type = "TABLE_VALUED_FUNCTION"
language = "SQL"
definition_body = <<-EOS
SELECT 1 + value AS value
EOS
arguments {
name = "value"
argument_kind = "FIXED_TYPE"
data_type = jsonencode({ "typeKind" = "INT64" })
}
return_table_type = jsonencode({ "columns" = [
{ "name" = "value", "type" = { "typeKind" = "INT64" } },
] })
}

resource "google_bigquery_dataset" "private" {
dataset_id = "<%= ctx[:vars]['private'] %>"
description = "This dataset is private"
}

resource "google_bigquery_dataset_access" "authorized_routine" {
dataset_id = google_bigquery_dataset.private.dataset_id
routine {
project_id = google_bigquery_routine.public.project
dataset_id = google_bigquery_routine.public.dataset_id
routine_id = google_bigquery_routine.public.routine_id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
resource "google_bigquery_dataset" "public" {
dataset_id = "<%= ctx[:vars]['public'] %>"
description = "This dataset is public"
}

resource "google_bigquery_routine" "public" {
dataset_id = google_bigquery_dataset.public.dataset_id
routine_id = "sample_routine"
routine_type = "TABLE_VALUED_FUNCTION"
language = "SQL"
definition_body = <<-EOS
SELECT 1 + value AS value
EOS
arguments {
name = "value"
argument_kind = "FIXED_TYPE"
data_type = jsonencode({ "typeKind" = "INT64" })
}
return_table_type = jsonencode({ "columns" = [
{ "name" = "value", "type" = { "typeKind" = "INT64" } },
] })
}

resource "google_bigquery_dataset" "private" {
dataset_id = "<%= ctx[:vars]['private'] %>"
description = "This dataset is private"
access {
role = "WRITER"
specialGroup = "projectWriters"
Copy link
Member

Choose a reason for hiding this comment

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

this would need to be special_group to match the Terraform field name - but I'd prefer not including these special_group blocks. If we just match what the API is returning we won't be able to tell whether it's acting on what we send it or it's ignoring what we sent but it just happens to match what's in the config right now.

Copy link
Contributor Author

@Tei1988 Tei1988 Oct 24, 2022

Choose a reason for hiding this comment

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

@melinath
Thank you for your comment.
Sorry for my misunderstanding about your advice.
I agree with your opinion.
I removed them.

On the other hands, I'm worried about the failed test.

}
access {
role = "OWNER"
specialGroup = "projectOwners"
}
access {
role = "OWNER"
user_by_email = "<%= ctx[:test_env_vars]['service_account'] %>"
}
access {
role = "READER"
specialGroup = "projectReaders"
}
access {
routine {
project_id = google_bigquery_routine.public.project
dataset_id = google_bigquery_routine.public.dataset_id
routine_id = google_bigquery_routine.public.routine_id
}
}
}