Skip to content

Commit

Permalink
Merge pull request #67 from flovouin/feat/permissions-graph-breaking-…
Browse files Browse the repository at this point in the history
…change

💥 `metabase_permissions_graph` breaking changes for Metabase .50
  • Loading branch information
flovouin authored Sep 8, 2024
2 parents 09fc2b0 + e7a1900 commit 91551aa
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 159 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
BREAKING CHANGES:

- Support Metabase v\*.50, and drop support for earlier versions.
- `metabase_permissions_graph`'s permissions support two new fields: `view_data` and `create_queries`. The `native` field is no longer supported.

## 0.7.0 (2024-06-27)

NEW FEATURES:
Expand Down
41 changes: 12 additions & 29 deletions docs/resources/permissions_graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,17 @@ resource "metabase_permissions_graph" "graph" {
permissions = [
{
group = metabase_permissions_group.data_analysts.id
database = metabase_database.bigquery.id
data = {
# Native: Yes
native = "write"
# Data access: Unrestricted
schemas = "all"
}
group = metabase_permissions_group.data_analysts.id
database = metabase_database.bigquery.id
view_data = "unrestricted"
create_queries = "query-builder-and-native"
},
{
group = metabase_permissions_group.business_stakeholders.id
database = metabase_database.bigquery.id
data = {
# Native: No (by omitting the `native` attribute or setting it to "none")
# Data access: Unrestricted
schemas = "all"
}
# This looks like no other value can be set, at least in the free version of Metabase.
view_data = "unrestricted"
create_queries = "query-builder"
},
# Permissions for the "All Users" group. Those cannot be removed entirely, but they can be limited.
# The example below gives the minimum set of permissions for the free version of Metabase:
Expand All @@ -71,12 +65,11 @@ resource "metabase_permissions_graph" "graph" {
database = metabase_database.bigquery.id
# Cannot be removed but has no impact when using the free version of Metabase.
download = {
native = "full"
schemas = "full"
}
# Omitting the `data` attribute entirely results in the lowest level of permissions:
# Data access: No self-service
# Native: No
view_data = "unrestricted"
# This gives the least access possible.
create_queries = "no"
},
]
}
Expand All @@ -103,31 +96,22 @@ resource "metabase_permissions_graph" "graph" {

Required:

- `create_queries` (String) The permission definition for creating queries.
- `database` (Number) The ID of the database to which the permission applies.
- `group` (Number) The ID of the group to which the permission applies.
- `view_data` (String) The permission definition for data access.

Optional:

- `data` (Attributes) The permission definition for data access. (see [below for nested schema](#nestedatt--permissions--data))
- `data_model` (Attributes) The permission definition for accessing the data model. (see [below for nested schema](#nestedatt--permissions--data_model))
- `details` (String) The permission definition for accessing details.
- `download` (Attributes) The permission definition for downloading data. (see [below for nested schema](#nestedatt--permissions--download))

<a id="nestedatt--permissions--data"></a>
### Nested Schema for `permissions.data`

Optional:

- `native` (String) The permission for native SQL querying
- `schemas` (String) The permission to access data through the Metabase interface


<a id="nestedatt--permissions--data_model"></a>
### Nested Schema for `permissions.data_model`

Optional:

- `native` (String) The permission for native SQL querying
- `schemas` (String) The permission to access data through the Metabase interface


Expand All @@ -136,7 +120,6 @@ Optional:

Optional:

- `native` (String) The permission for native SQL querying
- `schemas` (String) The permission to access data through the Metabase interface

## Import
Expand Down
27 changes: 10 additions & 17 deletions examples/resources/metabase_permissions_graph/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,17 @@ resource "metabase_permissions_graph" "graph" {

permissions = [
{
group = metabase_permissions_group.data_analysts.id
database = metabase_database.bigquery.id
data = {
# Native: Yes
native = "write"
# Data access: Unrestricted
schemas = "all"
}
group = metabase_permissions_group.data_analysts.id
database = metabase_database.bigquery.id
view_data = "unrestricted"
create_queries = "query-builder-and-native"
},
{
group = metabase_permissions_group.business_stakeholders.id
database = metabase_database.bigquery.id
data = {
# Native: No (by omitting the `native` attribute or setting it to "none")
# Data access: Unrestricted
schemas = "all"
}
# This looks like no other value can be set, at least in the free version of Metabase.
view_data = "unrestricted"
create_queries = "query-builder"
},
# Permissions for the "All Users" group. Those cannot be removed entirely, but they can be limited.
# The example below gives the minimum set of permissions for the free version of Metabase:
Expand All @@ -47,12 +41,11 @@ resource "metabase_permissions_graph" "graph" {
database = metabase_database.bigquery.id
# Cannot be removed but has no impact when using the free version of Metabase.
download = {
native = "full"
schemas = "full"
}
# Omitting the `data` attribute entirely results in the lowest level of permissions:
# Data access: No self-service
# Native: No
view_data = "unrestricted"
# This gives the least access possible.
create_queries = "no"
},
]
}
117 changes: 48 additions & 69 deletions internal/provider/permissions_graph_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,38 +41,35 @@ type PermissionsGraphResourceModel struct {

// The model for a single edge in the permissions graph.
type DatabasePermissions struct {
Group types.Int64 `tfsdk:"group"` // The ID of the permissions group to which the permission applies.
Database types.Int64 `tfsdk:"database"` // The ID of the database to which the permission applies.
Data types.Object `tfsdk:"data"` // Data-related permission.
Download types.Object `tfsdk:"download"` // Download-related permission (only available with advanced permissions).
DataModel types.Object `tfsdk:"data_model"` // Data-model-related permission (only available with advanced permissions).
Details types.String `tfsdk:"details"` // Details permission (only available with advanced permissions).
Group types.Int64 `tfsdk:"group"` // The ID of the permissions group to which the permission applies.
Database types.Int64 `tfsdk:"database"` // The ID of the database to which the permission applies.
ViewData types.String `tfsdk:"view_data"` // View data access permission.
CreateQueries types.String `tfsdk:"create_queries"` // Create queries access permission.
Download types.Object `tfsdk:"download"` // Download-related permission (only available with advanced permissions).
DataModel types.Object `tfsdk:"data_model"` // Data-model-related permission (only available with advanced permissions).
Details types.String `tfsdk:"details"` // Details permission (only available with advanced permissions).
}

// The object type definition for the `DatabasePermissions` model.
var databasePermissionsObjectType = types.ObjectType{
AttrTypes: map[string]attr.Type{
"group": types.Int64Type,
"database": types.Int64Type,
"data": accessPermissionsObjectType,
"download": accessPermissionsObjectType,
"data_model": accessPermissionsObjectType,
"details": types.StringType,
"group": types.Int64Type,
"database": types.Int64Type,
"view_data": types.StringType,
"create_queries": types.StringType,
"download": accessPermissionsObjectType,
"data_model": accessPermissionsObjectType,
"details": types.StringType,
},
}

// The model for a single permission setting in an edge of the graph.
type AccessPermissions struct {
Native types.String `tfsdk:"native"` // Native-access (SQL) permissions.
Schemas types.String `tfsdk:"schemas"` // Schemas permissions.
}

// The schema for the `AccessPermissions` model.
var accessPermissionAttributes = map[string]schema.Attribute{
"native": schema.StringAttribute{
MarkdownDescription: "The permission for native SQL querying",
Optional: true,
},
"schemas": schema.StringAttribute{
MarkdownDescription: "The permission to access data through the Metabase interface",
Optional: true,
Expand All @@ -82,7 +79,6 @@ var accessPermissionAttributes = map[string]schema.Attribute{
// The object type definition for the `AccessPermissions` model.
var accessPermissionsObjectType = types.ObjectType{
AttrTypes: map[string]attr.Type{
"native": types.StringType,
"schemas": types.StringType,
},
}
Expand Down Expand Up @@ -124,10 +120,13 @@ Permissions for the Administrators group cannot be changed. To avoid issues duri
MarkdownDescription: "The ID of the database to which the permission applies.",
Required: true,
},
"data": schema.SingleNestedAttribute{
"view_data": schema.StringAttribute{
MarkdownDescription: "The permission definition for data access.",
Optional: true,
Attributes: accessPermissionAttributes,
Required: true,
},
"create_queries": schema.StringAttribute{
MarkdownDescription: "The permission definition for creating queries.",
Required: true,
},
"download": schema.SingleNestedAttribute{
MarkdownDescription: "The permission definition for downloading data.",
Expand Down Expand Up @@ -166,7 +165,6 @@ func makeAccessPermissionsFromDatabaseAccess(ctx context.Context, da *metabase.P
}

obj, diags := types.ObjectValueFrom(ctx, accessPermissionsObjectType.AttrTypes, AccessPermissions{
Native: stringValueOrNull(da.Native),
Schemas: stringValueOrNull(&schemas),
})
if diags.HasError() {
Expand All @@ -192,10 +190,9 @@ func makePermissionsObjectFromDatabasePermissions(ctx context.Context, groupId s
return nil, diags
}

dataAccess, accessDiags := makeAccessPermissionsFromDatabaseAccess(ctx, p.Data)
diags.Append(accessDiags...)
if diags.HasError() {
return nil, diags
createQueries := metabase.PermissionsGraphDatabasePermissionsCreateQueriesNo
if p.CreateQueries != nil {
createQueries = *p.CreateQueries
}

downloadAccess, accessDiags := makeAccessPermissionsFromDatabaseAccess(ctx, p.Download)
Expand All @@ -211,12 +208,13 @@ func makePermissionsObjectFromDatabasePermissions(ctx context.Context, groupId s
}

permissionsObject, objectDiags := types.ObjectValueFrom(ctx, databasePermissionsObjectType.AttrTypes, DatabasePermissions{
Group: types.Int64Value(int64(groupIdInt)),
Database: types.Int64Value(int64(dbIdInt)),
Data: *dataAccess,
Download: *downloadAccess,
DataModel: *dataModelAccess,
Details: stringValueOrNull(p.Details),
Group: types.Int64Value(int64(groupIdInt)),
Database: types.Int64Value(int64(dbIdInt)),
ViewData: types.StringValue(string(p.ViewData)),
CreateQueries: types.StringValue(string(createQueries)),
Download: *downloadAccess,
DataModel: *dataModelAccess,
Details: stringValueOrNull(p.Details),
})
diags.Append(objectDiags...)
if diags.HasError() {
Expand Down Expand Up @@ -275,28 +273,19 @@ func updateModelFromPermissionsGraph(ctx context.Context, g metabase.Permissions
// Makes a Metabase API `PermissionsGraphDatabaseAccess` struct from a Terraform model object.
// `setIfNull` can be used to set the default values (forbidding any access) to permissions.
// This is useful when removing permissions for example.
// `setNative` determines whether the `native` attribute should be set in the access object.
// This is useful because the "data model" permission does not support it.
func makeDatasetAccessFromModel(ctx context.Context, apObj types.Object, setIfNull bool, setNative bool) (*metabase.PermissionsGraphDatabaseAccess, diag.Diagnostics) {
func makeDatasetAccessFromModel(ctx context.Context, apObj types.Object, setIfNull bool) (*metabase.PermissionsGraphDatabaseAccess, diag.Diagnostics) {
var diags diag.Diagnostics

if !setIfNull && apObj.IsNull() {
return nil, diags
}

// Default values ("none") forbid any access.
var native *metabase.PermissionsGraphDatabaseAccessNative

var schemas metabase.PermissionsGraphDatabaseAccess_Schemas
err := schemas.FromPermissionsGraphDatabaseAccessSchemas0(metabase.PermissionsGraphDatabaseAccessSchemas0None)
if err != nil {
diags.AddError("Unexpected error setting schemas to none value", err.Error())
return nil, diags
}
if setNative {
none := metabase.PermissionsGraphDatabaseAccessNativeNone
native = &none
}

if !apObj.IsNull() {
var ap AccessPermissions
Expand All @@ -306,11 +295,6 @@ func makeDatasetAccessFromModel(ctx context.Context, apObj types.Object, setIfNu
return nil, diags
}

if setNative && !ap.Native.IsNull() {
nativeValue := metabase.PermissionsGraphDatabaseAccessNative(ap.Native.ValueString())
native = &nativeValue
}

if !ap.Schemas.IsNull() {
err := schemas.FromPermissionsGraphDatabaseAccessSchemas0(metabase.PermissionsGraphDatabaseAccessSchemas0(ap.Schemas.ValueString()))
if err != nil {
Expand All @@ -321,7 +305,6 @@ func makeDatasetAccessFromModel(ctx context.Context, apObj types.Object, setIfNu
}

return &metabase.PermissionsGraphDatabaseAccess{
Native: native,
Schemas: &schemas,
}, diags
}
Expand Down Expand Up @@ -367,35 +350,38 @@ func makePermissionsGraphFromModel(ctx context.Context, data PermissionsGraphRes
return nil, diags
}

data, accessDiags := makeDatasetAccessFromModel(ctx, p.Data, true, true)
diags.Append(accessDiags...)
if diags.HasError() {
return nil, diags
viewData := metabase.PermissionsGraphDatabasePermissionsViewData(p.ViewData.ValueString())

createQueries := valueApproximateStringOrNull[metabase.PermissionsGraphDatabasePermissionsCreateQueries](p.CreateQueries)
if createQueries == nil {
no := metabase.PermissionsGraphDatabasePermissionsCreateQueriesNo
createQueries = &no
}

download, accessDiags := makeDatasetAccessFromModel(ctx, p.Download, advancedPermissions, true)
download, accessDiags := makeDatasetAccessFromModel(ctx, p.Download, advancedPermissions)
diags.Append(accessDiags...)
if diags.HasError() {
return nil, diags
}

dataModel, accessDiags := makeDatasetAccessFromModel(ctx, p.DataModel, advancedPermissions, false)
dataModel, accessDiags := makeDatasetAccessFromModel(ctx, p.DataModel, advancedPermissions)
diags.Append(accessDiags...)
if diags.HasError() {
return nil, diags
}

details := valueApproximateStringOrNull[metabase.PermissionsGraphDatabasePermissionsDetails](p.Details)
if details == nil && advancedPermissions {
no := metabase.No
no := metabase.PermissionsGraphDatabasePermissionsDetailsNo
details = &no
}

dbPermMap[databaseId] = metabase.PermissionsGraphDatabasePermissions{
Data: data,
Download: download,
DataModel: dataModel,
Details: details,
ViewData: viewData,
CreateQueries: createQueries,
Download: download,
DataModel: dataModel,
Details: details,
}
}

Expand Down Expand Up @@ -436,31 +422,24 @@ func makePermissionsGraphFromModel(ctx context.Context, data PermissionsGraphRes
continue
}

// If the permission does not exist in the plan but exists in the state, it should be explicitly deleted by
// creating the permission with "none" values.
nativeNone := metabase.PermissionsGraphDatabaseAccessNativeNone

var schemasNone metabase.PermissionsGraphDatabaseAccess_Schemas
err := schemasNone.FromPermissionsGraphDatabaseAccessSchemas0(metabase.PermissionsGraphDatabaseAccessSchemas0None)
if err != nil {
diags.AddError("Unexpected error setting schema none value", err.Error())
return nil, diags
}
no := metabase.PermissionsGraphDatabasePermissionsCreateQueriesNo
deletedPermissions := metabase.PermissionsGraphDatabasePermissions{
Data: &metabase.PermissionsGraphDatabaseAccess{
Native: &nativeNone,
Schemas: &schemasNone,
},
CreateQueries: &no,
}
if advancedPermissions {
deletedPermissions.Download = &metabase.PermissionsGraphDatabaseAccess{
Native: &nativeNone,
Schemas: &schemasNone,
}
deletedPermissions.DataModel = &metabase.PermissionsGraphDatabaseAccess{
Schemas: &schemasNone,
}
no := metabase.No
no := metabase.PermissionsGraphDatabasePermissionsDetailsNo
deletedPermissions.Details = &no
}
dbPermMap[databaseId] = deletedPermissions
Expand Down
Loading

0 comments on commit 91551aa

Please sign in to comment.