-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
#644 Add Support For BigQuery Access Control #1931
#644 Add Support For BigQuery Access Control #1931
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, I'm super excited about this! I have a bunch of comments but overall structure looks good.
google/resource_bigquery_dataset.go
Outdated
"domain": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"access.group_by_email", "access.special_group", "access.user_by_email", "access.view"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly this won't work. ConflictsWith
needs to point to an exact address of a field, and since this is a list, there's no way to wildcard all indexes. If you want a check like this, you'll just have to do it at apply-time (inside the create fn)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API error message is pretty good in this case, so I just dropped this validation on our side.
google/resource_bigquery_dataset.go
Outdated
da := bigquery.DatasetAccess{} | ||
msi := m.(map[string]interface{}) | ||
da.Role = msi["role"].(string) | ||
if val, ok := msi["domain"]; ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would need to confirm 100% since I don't remember which cases this applies to and which it doesn't, but I think the map will actually have all values in it, even if they weren't set, which would mean you could do:
da := bigquery.DatasetAccess{
Role: msi["role"].(string),
Domain: msi["domain}.(string),
[etc]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing it that way unfortunately breaks existing tests (where access
isn't set at all)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? You're already inside a block that checks whether access is set once you've gotten to this point.
google/resource_bigquery_dataset.go
Outdated
access := []*bigquery.DatasetAccess{} | ||
for _, m := range v.([]interface{}) { | ||
da := bigquery.DatasetAccess{} | ||
msi := m.(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does msi just stand for map[string]interface? if so maybe a sliiiightly more descriptive var name could be used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
google/resource_bigquery_dataset.go
Outdated
"role": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateRole, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a validateFn that you can use for enums: validation.StringInSlice([]string{[values here]}, false),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Done.
google/resource_bigquery_dataset.go
Outdated
access := make([]map[string]interface{}, 0, len(a)) | ||
for _, da := range a { | ||
var ai map[string]interface{} | ||
ai = make(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you shouldn't need to do this in two lines, you can just do
ai := make(map[string]interface{})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
location = "EU" | ||
default_table_expiration_ms = 3600000 | ||
|
||
access = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can just be
access {
role = [...]
user_by_email = [...]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
resource "google_bigquery_dataset" "access_test" { | ||
dataset_id = "%s" | ||
friendly_name = "foo" | ||
description = "This is a foo description" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with keeping these here, but I would also be fine with the test only setting required fields + access-related stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Removed optional fields.
location = "EU" | ||
default_table_expiration_ms = 3600000 | ||
|
||
access = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likewise, this would just be
access {
...
}
access {
...
}
}, | ||
{ | ||
view = { | ||
project_id = "%s" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this be project_id = "${google_bigquery_dataset.other_dataset.project}"
? would that make sense semantically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that's more consistent.
@@ -73,6 +84,9 @@ The following arguments are supported: | |||
|
|||
* `labels` - (Optional) A mapping of labels to assign to the resource. | |||
|
|||
* `access` - (Optional) An array of objects that define dataset access for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a "structure is documented below" statement and then docs for each of the attributes? search for that string in our existing docs and you'll find a bunch of examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comments!
google/resource_bigquery_dataset.go
Outdated
"role": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateRole, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Done.
google/resource_bigquery_dataset.go
Outdated
"domain": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"access.group_by_email", "access.special_group", "access.user_by_email", "access.view"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API error message is pretty good in this case, so I just dropped this validation on our side.
google/resource_bigquery_dataset.go
Outdated
da := bigquery.DatasetAccess{} | ||
msi := m.(map[string]interface{}) | ||
da.Role = msi["role"].(string) | ||
if val, ok := msi["domain"]; ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing it that way unfortunately breaks existing tests (where access
isn't set at all)
google/resource_bigquery_dataset.go
Outdated
access := []*bigquery.DatasetAccess{} | ||
for _, m := range v.([]interface{}) { | ||
da := bigquery.DatasetAccess{} | ||
msi := m.(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
google/resource_bigquery_dataset.go
Outdated
access := make([]map[string]interface{}, 0, len(a)) | ||
for _, da := range a { | ||
var ai map[string]interface{} | ||
ai = make(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
google/resource_bigquery_dataset.go
Outdated
} | ||
if da.View != nil { | ||
view := make(map[string]string) | ||
view["project_id"] = da.View.ProjectId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat! Done.
resource "google_bigquery_dataset" "access_test" { | ||
dataset_id = "%s" | ||
friendly_name = "foo" | ||
description = "This is a foo description" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Removed optional fields.
location = "EU" | ||
default_table_expiration_ms = 3600000 | ||
|
||
access = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}, | ||
{ | ||
view = { | ||
project_id = "%s" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that's more consistent.
@@ -73,6 +84,9 @@ The following arguments are supported: | |||
|
|||
* `labels` - (Optional) A mapping of labels to assign to the resource. | |||
|
|||
* `access` - (Optional) An array of objects that define dataset access for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also fixed markdown formatting with the nested list of special groups
Friendly ping @danawillow (now that you're back from vacation and I'm sure your inbox is swamped). |
google/resource_bigquery_dataset.go
Outdated
da := bigquery.DatasetAccess{} | ||
msi := m.(map[string]interface{}) | ||
da.Role = msi["role"].(string) | ||
if val, ok := msi["domain"]; ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? You're already inside a block that checks whether access is set once you've gotten to this point.
google/resource_bigquery_dataset.go
Outdated
func flattenAccess(a []*bigquery.DatasetAccess) []map[string]interface{} { | ||
access := make([]map[string]interface{}, 0, len(a)) | ||
for _, da := range a { | ||
ai := make(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this section can also be done in a single block:
ai := map[string]interface{}{
"role": da.Role,
..etc...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
google/resource_bigquery_dataset.go
Outdated
@@ -228,6 +308,7 @@ func resourceBigQueryDatasetRead(d *schema.ResourceData, meta interface{}) error | |||
d.Set("project", id.Project) | |||
d.Set("etag", res.Etag) | |||
d.Set("labels", res.Labels) | |||
d.Set("access", flattenAccess(res.Access)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for setting values that aren't primitives, we're trying to get in the habit of checking the error returned from d.Set (we've had some confusing bugs in the past that would have shown up much sooner if we had error-checked). something like:
if err := d.Set("access", flattenAccess(res.Access)); err != nil {
return err
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
func testAccBigQueryDatasetWithOneAccess(datasetID string) string { | ||
return fmt.Sprintf(` | ||
resource "google_bigquery_dataset" "access_test" { | ||
dataset_id = "%s" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: since this isn't trying to align with anything else, you can move the equals sign way back over to the left
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
access { | ||
role = "READER" | ||
domain = "example.com" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: looks like a tab snuck in here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}`, datasetID) | ||
} | ||
|
||
func getBigQueryTableWithView(datasetID, tableID string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this func only used in the one place? If so, may as well just inline it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
{ | ||
role = "OWNER" | ||
user_by_email = "[email protected]" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you look at this section? The formatting seems to have gotten a bit wonky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -26,6 +26,17 @@ resource "google_bigquery_dataset" "default" { | |||
labels { | |||
env = "default" | |||
} | |||
|
|||
access = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be:
access {
role = "READER"
domain = "example.com"
}
access {
role = "WRITER"
group_by_email = "[email protected]"
}
There's nothing wrong with the other way, this is just more canonical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
the user specified by the other member of the access object. The following | ||
string values are supported: `READER`, `WRITER`, `OWNER`. | ||
|
||
* `domain` - (Pick one) A domain to grant access to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure what "Pick one" means in this context, do you mean Optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was mimic-ing the BQ API documentation, it basically means you have to pick exactly one of domain
, group_by_email
, special_group
, user_by_email
, or view
. If there's a way to be more clear, I'm happy to change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, got it. I'd just try to match the rest of the Terraform documentation, where we just say Required or Optional there. I think that Pick One itself is pretty redundant, right? There isn't a way to specify multiple of the same field, so you don't really have a choice but to pick one (or zero, but that's covered in Required vs Optional).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little more nuanced because these are keys in a map, not values that can be taken by a single field. I updated the docs to reflect that (while marking these optional). Let me know what you think.
google/resource_bigquery_dataset.go
Outdated
Optional: true, | ||
}, | ||
"view": &schema.Schema{ | ||
Type: schema.TypeMap, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has specific keys that have to be in the map, correct? If so, then this should be another nested object (list with MaxSize: 1 and a defined schema) so we don't allow users to set keys in the map that aren't valid options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Oh also! Fun GH trick: if you write fixes #644 in the body of your PR, it'll close the issue automatically: https://help.github.com/articles/closing-issues-using-keywords/ |
Yup that looks good to me, thanks! I'll go ahead and merge this. |
* Updates based on PR comments * Fix markdown nested list * Second round of feedback * Clarify pick one docs
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks! |
I went ahead and added separate tests, leaving the "basic" tests to prove that the existing way (no explicit access block) continues to work.
Fixes #644
This is my first non-toy Go code and my first contribution to this project, so feedback is most welcome.