From bc4c75c892bfd7b4400617e0630ff017390e4a95 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:54:55 +0200 Subject: [PATCH 01/19] Check with the cloud config team if we want to remove this check or not. It was failing with global connections. --- .../project_connection_acceptance_test.go | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/pkg/sdkv2/resources/project_connection_acceptance_test.go b/pkg/sdkv2/resources/project_connection_acceptance_test.go index 5df62f31..68648b00 100644 --- a/pkg/sdkv2/resources/project_connection_acceptance_test.go +++ b/pkg/sdkv2/resources/project_connection_acceptance_test.go @@ -41,16 +41,6 @@ func TestAccDbtCloudProjectConnectionResource(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{}, }, - // EMPTY - { - Config: testAccDbtCloudProjectConnectionResourceEmptyConfig( - projectName, - connectionName, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudProjectConnectionEmpty("dbtcloud_project.test_project"), - ), - }, }, }) } @@ -82,28 +72,6 @@ resource "dbtcloud_project_connection" "test_project_connection" { `, projectName, connectionName) } -func testAccDbtCloudProjectConnectionResourceEmptyConfig( - projectName, connectionName string, -) string { - return fmt.Sprintf(` -resource "dbtcloud_project" "test_project" { - name = "%s" -} - -resource "dbtcloud_connection" "test_connection" { - name = "%s" - type = "snowflake" - project_id = dbtcloud_project.test_project.id - account = "test" - database = "db" - warehouse = "wh" - role = "user" - allow_sso = false - allow_keep_alive = false -} -`, projectName, connectionName) -} - func testAccCheckDbtCloudProjectConnectionExists(resource string) resource.TestCheckFunc { return func(state *terraform.State) error { rs, ok := state.RootModule().Resources[resource] @@ -129,30 +97,6 @@ func testAccCheckDbtCloudProjectConnectionExists(resource string) resource.TestC } } -func testAccCheckDbtCloudProjectConnectionEmpty(resource string) resource.TestCheckFunc { - return func(state *terraform.State) error { - rs, ok := state.RootModule().Resources[resource] - if !ok { - return fmt.Errorf("Not found: %s", resource) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No Record ID is set") - } - apiClient, err := acctest_helper.SharedClient() - if err != nil { - return fmt.Errorf("Issue getting the client") - } - project, err := apiClient.GetProject(rs.Primary.ID) - if err != nil { - return fmt.Errorf("Can't get project") - } - if project.ConnectionID != nil { - return fmt.Errorf("error fetching item with resource %s. %s", resource, err) - } - return nil - } -} - func testAccCheckDbtCloudProjectConnectionDestroy(s *terraform.State) error { apiClient, err := acctest_helper.SharedClient() if err != nil { From 27ef96144cc22472e8a70812f99440819cf12398 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:55:22 +0200 Subject: [PATCH 02/19] Interact with global connection API --- pkg/dbt_cloud/global_connection.go | 351 +++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 pkg/dbt_cloud/global_connection.go diff --git a/pkg/dbt_cloud/global_connection.go b/pkg/dbt_cloud/global_connection.go new file mode 100644 index 00000000..f1f5c5ee --- /dev/null +++ b/pkg/dbt_cloud/global_connection.go @@ -0,0 +1,351 @@ +package dbt_cloud + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/oapi-codegen/nullable" +) + +// TODO: Do we need this? Or do we just remove it? +type GlobalConnectionType int + +// Declare enum values using iota +const ( + Snowflake GlobalConnectionType = iota + BigQuery +) + +type GlobalConnectionInterface interface { + GeneratePostPayload() (*strings.Reader, error) + // UpdatePayload() (*strings.Reader, error) + ReadAnswer([]byte) (any, error) +} + +type GlobalConnectionPointerInterface interface { + GeneratePatchPayload() (*strings.Reader, error) +} + +type GlobalConnection struct { + ID *int64 `json:"id,omitempty"` + AccountID int64 `json:"account_id"` + AdapterVersion string `json:"adapter_version"` + Name string `json:"name"` + IsSshTunnelEnabled bool `json:"is_ssh_tunnel_enabled"` + PrivateLinkEndpointId *int64 `json:"private_link_endpoint_id"` + OauthConfigurationId *int64 `json:"oauth_configuration_id"` + // OauthRedirectUri *string `json:"oauth_redirect_uri"` //those are read-only fields, we could maybe get them as Computed but never send them + // IsConfiguredForNativeOauth bool `json:"is_configured_for_native_oauth"` +} + +type GlobalConnectionPointers struct { + ID *int64 `json:"id,omitempty"` + AccountID *int64 `json:"account_id,omitempty"` + AdapterVersion *string `json:"adapter_version,omitempty"` + Name *string `json:"name,omitempty"` + IsSshTunnelEnabled *bool `json:"is_ssh_tunnel_enabled,omitempty"` + PrivateLinkEndpointId *int64 `json:"private_link_endpoint_id"` // those seem to be sent all the time when we modify a connection so I didn't add an omitempty for now + OauthConfigurationId *int64 `json:"oauth_configuration_id"` +} + +type GlobalConnectionResponse[T GlobalConnectionInterface] struct { + Data T `json:"data"` + Status ResponseStatus `json:"status"` +} + +type SnowflakeGlobalConnection struct { + GlobalConnection + Config SnowflakeConfig `json:"config"` +} + +type SnowflakeConfig struct { + Account string `json:"account"` + Database string `json:"database"` + Warehouse string `json:"warehouse"` + ClientSessionKeepAlive bool `json:"client_session_keep_alive"` + Role string `json:"role"` + AllowSso bool `json:"allow_sso"` + OauthClientID string `json:"oauth_client_id"` + OauthClientSecret string `json:"oauth_client_secret"` +} + +// we create a pointer version so that we can PATCH parts of the connection +type SnowflakeGlobalConnectionPointers struct { + GlobalConnectionPointers + Config *SnowflakeConfigPointers `json:"config,omitempty"` +} + +// I originally put pointers everywhere and used omitempty but it prevented us from sending null values +// and differentiating between null and not sending the field at all +// TODO: rename to something else than Pointers now that those fields are not Pointers :-) +// TODO: check if we could reuse the same structure as the other one, with the nullable fields and the omitempty +type SnowflakeConfigPointers struct { + Account string `json:"account,omitempty"` + Database string `json:"database,omitempty"` + Warehouse string `json:"warehouse,omitempty"` + ClientSessionKeepAlive bool `json:"client_session_keep_alive,omitempty"` + Role nullable.Nullable[string] `json:"role,omitempty"` + AllowSso bool `json:"allow_sso,omitempty"` + OauthClientID string `json:"oauth_client_id,omitempty"` + OauthClientSecret string `json:"oauth_client_secret,omitempty"` +} + +// This is how it was before +// TODO: delete when we get the pattern working +// type SnowflakeConfigPointers struct { +// Account *string `json:"account,omitempty"` +// Database *string `json:"database,omitempty"` +// Warehouse *string `json:"warehouse,omitempty"` +// ClientSessionKeepAlive *bool `json:"client_session_keep_alive,omitempty"` +// Role *string `json:"role,omitempty"` +// AllowSso *bool `json:"allow_sso,omitempty"` +// OauthClientID *string `json:"oauth_client_id,omitempty"` +// OauthClientSecret *string `json:"oauth_client_secret,omitempty"` +// } + +type SnowflakeGlobalConnectionResponse struct { + Data SnowflakeGlobalConnection `json:"data"` + Status ResponseStatus `json:"status"` +} + +func (gc SnowflakeGlobalConnection) GeneratePostPayload() (*strings.Reader, error) { + payload, err := json.Marshal(gc) + + if err != nil { + return nil, err + } + + return strings.NewReader(string(payload)), nil +} + +func (gc SnowflakeGlobalConnection) ReadAnswer( + body []byte, +) (any, error) { + connectionResponse := SnowflakeGlobalConnectionResponse{} + err := json.Unmarshal(body, &connectionResponse) + if err != nil { + return nil, err + } + return connectionResponse.Data, nil +} + +func (gcp SnowflakeGlobalConnectionPointers) GeneratePatchPayload() (*strings.Reader, error) { + payload, err := json.Marshal(gcp) + + if err != nil { + return nil, err + } + + return strings.NewReader(string(payload)), nil +} + +type BigQueryGlobalConnection struct { + GlobalConnection + Config BigQueryConfig `json:"config"` +} + +type BigQueryConfig struct { + ProjectID string `json:"project_id"` + TimeoutSeconds int64 `json:"timeout_seconds"` + PrivateKeyID string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + ClientEmail string `json:"client_email"` + ClientID string `json:"client_id"` + AuthURI string `json:"auth_uri"` + TokenURI string `json:"token_uri"` + AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"` + ClientX509CertURL string `json:"client_x509_cert_url"` + Priority string `json:"priority"` + Retries int64 `json:"retries"` + Location string `json:"location"` + MaximumBytesBilled int64 `json:"maximum_bytes_billed"` + ExecutionProject string `json:"execution_project"` + ImpersonateServiceAccount string `json:"impersonate_service_account"` + JobRetryDeadlineSeconds int64 `json:"job_retry_deadline_seconds"` + JobCreationTimeoutSeconds int64 `json:"job_creation_timeout_seconds"` + ApplicationID string `json:"application_id"` + ApplicationSecret string `json:"application_secret"` + GcsBucket string `json:"gcs_bucket"` + DataprocRegion string `json:"dataproc_region"` + DataprocClusterName string `json:"dataproc_cluster_name"` + Scopes []string `json:"scopes"` +} + +type BigQueryGlobalConnectionResponse struct { + Data BigQueryGlobalConnection `json:"data"` + Status ResponseStatus `json:"status"` +} + +func (gc BigQueryGlobalConnection) GetCreatePayload() (*strings.Reader, error) { + payload, err := json.Marshal(gc) + + if err != nil { + return nil, err + } + + return strings.NewReader(string(payload)), nil +} + +func GetTypedGlobalConnection[T GlobalConnectionInterface]( + c *Client, + connectionID int64, +) (*T, error) { + req, err := http.NewRequest( + "GET", + fmt.Sprintf( + "%s/v3/accounts/%s/connections/%d/", + c.HostURL, + strconv.Itoa(c.AccountID), + connectionID, + ), + nil, + ) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var conn T + data, err := conn.ReadAnswer(body) + if err != nil { + return nil, err + } + + dataTyped := data.(T) + return &dataTyped, nil +} + +func (c *Client) GetSnowflakeGlobalConnection( + connectionID int64, +) (*SnowflakeGlobalConnection, error) { + + return GetTypedGlobalConnection[SnowflakeGlobalConnection](c, connectionID) +} + +func CreateTypedGlobalConnection[T GlobalConnectionInterface]( + c *Client, + connection T, +) (*T, error) { + + payload, err := connection.GeneratePostPayload() + if err != nil { + return nil, err + } + + req, err := http.NewRequest( + "POST", + fmt.Sprintf( + "%s/v3/accounts/%s/connections/", + c.HostURL, + strconv.Itoa(c.AccountID), + ), + payload, + ) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + data, err := connection.ReadAnswer(body) + if err != nil { + return nil, err + } + + // I could not find a way to do this without doing type inference on any + dataTyped := data.(T) + return &dataTyped, nil + +} + +func (c *Client) CreateSnowflakeGlobalConnection( + connection SnowflakeGlobalConnection, +) (*SnowflakeGlobalConnection, error) { + return CreateTypedGlobalConnection(c, connection) +} + +func UpdateTypedGlobalConnection[T GlobalConnectionInterface, PT GlobalConnectionPointerInterface]( + c *Client, + connectionID int64, + connection PT, +) (*T, error) { + + payload, err := connection.GeneratePatchPayload() + // connectionData, err := json.Marshal(connection) + if err != nil { + return nil, err + } + + req, err := http.NewRequest( + "PATCH", + fmt.Sprintf( + "%s/v3/accounts/%d/connections/%d/", + c.HostURL, + c.AccountID, + connectionID, + ), + payload, + ) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var conn T + data, err := conn.ReadAnswer(body) + if err != nil { + return nil, err + } + + // I could not find a way to do this without doing type inference on any + dataTyped := data.(T) + return &dataTyped, nil +} + +func (c *Client) UpdateSnowflakeGlobalConnection( + connectionID int64, + connectionPointers SnowflakeGlobalConnectionPointers, +) (*SnowflakeGlobalConnection, error) { + return UpdateTypedGlobalConnection[SnowflakeGlobalConnection]( + c, + connectionID, + connectionPointers, + ) +} + +func (c *Client) DeleteGlobalConnection(connectionID int64) (string, error) { + req, err := http.NewRequest( + "DELETE", + fmt.Sprintf( + "%s/v3/accounts/%s/connections/%d/", + c.HostURL, + strconv.Itoa(c.AccountID), + connectionID, + ), + nil, + ) + if err != nil { + return "", err + } + + _, err = c.doRequest(req) + if err != nil { + return "", err + } + + return "", nil +} From 635138e8e8bcb2c364bf1556cac754ed9981950b Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:56:00 +0200 Subject: [PATCH 03/19] Add code for the global_connection resource for Snowflake --- .../objects/global_connection/model.go | 64 ++++ .../objects/global_connection/resource.go | 294 ++++++++++++++++++ .../objects/global_connection/schema.go | 180 +++++++++++ pkg/helper/helper.go | 8 + 4 files changed, 546 insertions(+) create mode 100644 pkg/framework/objects/global_connection/model.go create mode 100644 pkg/framework/objects/global_connection/resource.go create mode 100644 pkg/framework/objects/global_connection/schema.go diff --git a/pkg/framework/objects/global_connection/model.go b/pkg/framework/objects/global_connection/model.go new file mode 100644 index 00000000..845303c8 --- /dev/null +++ b/pkg/framework/objects/global_connection/model.go @@ -0,0 +1,64 @@ +package global_connection + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type GlobalConnectionResourceModel struct { + ID types.Int64 `tfsdk:"id"` + AdapterVersion types.String `tfsdk:"adapter_version"` + Name types.String `tfsdk:"name"` + IsSshTunnelEnabled types.Bool `tfsdk:"is_ssh_tunnel_enabled"` + PrivateLinkEndpointId types.Int64 `tfsdk:"private_link_endpoint_id"` + OauthConfigurationId types.Int64 `tfsdk:"oauth_configuration_id"` + SnowflakeConfig *SnowflakeConfig `tfsdk:"snowflake"` + BigQueryConfig *BigQueryConfig `tfsdk:"bigquery"` +} + +type BigQueryConfig struct { + GCPProjectID types.String `tfsdk:"gcp_project_id"` + TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` + PrivateKeyID types.String `tfsdk:"private_key_id"` + PrivateKey types.String `tfsdk:"private_key"` + ClientEmail types.String `tfsdk:"client_email"` + ClientID types.String `tfsdk:"client_id"` + AuthURI types.String `tfsdk:"auth_uri"` + TokenURI types.String `tfsdk:"token_uri"` + AuthProviderX509CertURL types.String `tfsdk:"auth_provider_x509_cert_url"` + ClientX509CertURL types.String `tfsdk:"client_x509_cert_url"` + Priority types.String `tfsdk:"priority"` + Retries types.Int64 `tfsdk:"retries"` + Location types.String `tfsdk:"location"` + MaximumBytesBilled types.Int64 `tfsdk:"maximum_bytes_billed"` + ExecutionProject types.String `tfsdk:"execution_project"` + ImpersonateServiceAccount types.String `tfsdk:"impersonate_service_account"` + JobRetryDeadlineSeconds types.Int64 `tfsdk:"job_retry_deadline_seconds"` + JobCreationTimeoutSeconds types.Int64 `tfsdk:"job_creation_timeout_seconds"` + ApplicationID types.String `tfsdk:"application_id"` + ApplicationSecret types.String `tfsdk:"application_secret"` + GcsBucket types.String `tfsdk:"gcs_bucket"` + DataprocRegion types.String `tfsdk:"dataproc_region"` + DataprocClusterName types.String `tfsdk:"dataproc_cluster_name"` + Scopes []types.String `tfsdk:"scopes"` +} + +type SnowflakeConfig struct { + Account types.String `tfsdk:"account"` + Database types.String `tfsdk:"database"` + Warehouse types.String `tfsdk:"warehouse"` + ClientSessionKeepAlive types.Bool `tfsdk:"client_session_keep_alive"` + Role types.String `tfsdk:"role"` + AllowSso types.Bool `tfsdk:"allow_sso"` + OauthClientID types.String `tfsdk:"oauth_client_id"` + OauthClientSecret types.String `tfsdk:"oauth_client_secret"` +} + +type GlobalConnectionDataSourceModel struct { + // TBD, and do we use the same as the for the Resource model? +} + +// func ConvertGlobalConnectionModelToData( +// model GlobalConnectionResourceModel, +// ) dbt_cloud.Notification { +// TBD +// } diff --git a/pkg/framework/objects/global_connection/resource.go b/pkg/framework/objects/global_connection/resource.go new file mode 100644 index 00000000..eac8e70a --- /dev/null +++ b/pkg/framework/objects/global_connection/resource.go @@ -0,0 +1,294 @@ +package global_connection + +import ( + "context" + "strings" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &globalConnectionResource{} + _ resource.ResourceWithConfigure = &globalConnectionResource{} + _ resource.ResourceWithImportState = &globalConnectionResource{} + _ resource.ResourceWithConfigValidators = &globalConnectionResource{} +) + +func GlobalConnectionResource() resource.Resource { + return &globalConnectionResource{} +} + +type globalConnectionResource struct { + client *dbt_cloud.Client +} + +func (r *globalConnectionResource) Metadata( + _ context.Context, + req resource.MetadataRequest, + resp *resource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_global_connection" +} + +func (r globalConnectionResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.ExactlyOneOf( + path.MatchRoot("snowflake"), + path.MatchRoot("bigquery"), + ), + } +} + +func (r *globalConnectionResource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state GlobalConnectionResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + connectionID := state.ID.ValueInt64() + + switch { + case !(state.SnowflakeConfig == nil): + + connection, err := r.client.GetSnowflakeGlobalConnection(connectionID) + if err != nil { + if strings.HasPrefix(err.Error(), "resource-not-found") { + resp.Diagnostics.AddWarning( + "Resource not found", + "The connection resource was not found and has been removed from the state.", + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Error getting the connection", err.Error()) + return + } + + // global settings + state.ID = types.Int64PointerValue(connection.ID) + state.AdapterVersion = types.StringValue(connection.AdapterVersion) + state.Name = types.StringValue(connection.Name) + state.IsSshTunnelEnabled = types.BoolValue(connection.IsSshTunnelEnabled) + state.PrivateLinkEndpointId = types.Int64PointerValue(connection.PrivateLinkEndpointId) + state.OauthConfigurationId = types.Int64PointerValue(connection.OauthConfigurationId) + + // snowflake settings + state.SnowflakeConfig.Account = types.StringValue(connection.Config.Account) + state.SnowflakeConfig.Database = types.StringValue(connection.Config.Database) + state.SnowflakeConfig.Warehouse = types.StringValue(connection.Config.Warehouse) + state.SnowflakeConfig.ClientSessionKeepAlive = types.BoolValue( + connection.Config.ClientSessionKeepAlive, + ) + state.SnowflakeConfig.AllowSso = types.BoolValue(connection.Config.AllowSso) + + // nullable optional fields + // TODO: decide if it is better to read it as string, *string or nullable.Nullable[string] on the dbt_cloud side + // in this case role can never be empty so this works but we might have cases where null and empty are different + if connection.Config.Role != "" { + state.SnowflakeConfig.Role = types.StringValue(connection.Config.Role) + } else { + state.SnowflakeConfig.Role = types.StringNull() + } + + // We don't set the sensitive fields when we read because those are secret and never returned by the API + // sensitive fields: OauthClientID, OauthClientSecret + + default: + panic("Unknown connection type") + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + +} + +func (r *globalConnectionResource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan GlobalConnectionResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + switch { + case !(plan.SnowflakeConfig == nil): + + connectionInput := dbt_cloud.SnowflakeGlobalConnection{ + GlobalConnection: dbt_cloud.GlobalConnection{ + AccountID: int64(r.client.AccountID), + AdapterVersion: "snowflake_v0", + Name: plan.Name.ValueString(), + IsSshTunnelEnabled: plan.IsSshTunnelEnabled.ValueBool(), + PrivateLinkEndpointId: helper.TypesInt64ToInt64Pointer(plan.PrivateLinkEndpointId), + OauthConfigurationId: helper.TypesInt64ToInt64Pointer(plan.OauthConfigurationId), + }, + Config: dbt_cloud.SnowflakeConfig{ + Account: plan.SnowflakeConfig.Account.ValueString(), + Database: plan.SnowflakeConfig.Database.ValueString(), + Warehouse: plan.SnowflakeConfig.Warehouse.ValueString(), + ClientSessionKeepAlive: plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBool(), + Role: plan.SnowflakeConfig.Role.ValueString(), + AllowSso: plan.SnowflakeConfig.AllowSso.ValueBool(), + OauthClientID: plan.SnowflakeConfig.OauthClientID.ValueString(), + OauthClientSecret: plan.SnowflakeConfig.OauthClientSecret.ValueString(), + }, + } + + connection, err := r.client.CreateSnowflakeGlobalConnection(connectionInput) + if err != nil { + resp.Diagnostics.AddError("Error creating the connection", err.Error()) + return + } + + // we set the computed values that don't have any default + plan.ID = types.Int64Value(*connection.ID) + plan.AdapterVersion = types.StringValue(connection.AdapterVersion) + plan.IsSshTunnelEnabled = types.BoolValue(connection.IsSshTunnelEnabled) + + default: + panic("Unknown connection type") + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *globalConnectionResource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + var state GlobalConnectionResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + connectionID := state.ID.ValueInt64() + + _, err := r.client.DeleteGlobalConnection(connectionID) + if err != nil { + resp.Diagnostics.AddError("Error deleting the connection", err.Error()) + return + } + +} + +func (r *globalConnectionResource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + var plan, state GlobalConnectionResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + globalConfigChanges := dbt_cloud.GlobalConnectionPointers{} + + if plan.Name != state.Name { + globalConfigChanges.Name = plan.Name.ValueStringPointer() + } + if plan.PrivateLinkEndpointId != state.PrivateLinkEndpointId { + globalConfigChanges.PrivateLinkEndpointId = plan.PrivateLinkEndpointId.ValueInt64Pointer() + } + + switch { + case !(plan.SnowflakeConfig == nil): + + warehouseConfigChanges := dbt_cloud.SnowflakeConfigPointers{} + + // Snowflake specific ones + if plan.SnowflakeConfig.Account != state.SnowflakeConfig.Account { + warehouseConfigChanges.Account = plan.SnowflakeConfig.Account.ValueString() + } + if plan.SnowflakeConfig.Database != state.SnowflakeConfig.Database { + warehouseConfigChanges.Database = plan.SnowflakeConfig.Database.ValueString() + } + if plan.SnowflakeConfig.Warehouse != state.SnowflakeConfig.Warehouse { + warehouseConfigChanges.Warehouse = plan.SnowflakeConfig.Warehouse.ValueString() + } + if plan.SnowflakeConfig.ClientSessionKeepAlive != state.SnowflakeConfig.ClientSessionKeepAlive { + warehouseConfigChanges.ClientSessionKeepAlive = plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBool() + } + // here we need to take care of the null case + // when Role is Null, we still want to send it as null to the PATCH payload, to remove it, otherwise the omitempty doesn't add it to the payload + if plan.SnowflakeConfig.Role != state.SnowflakeConfig.Role { + if plan.SnowflakeConfig.Role.IsNull() { + warehouseConfigChanges.Role.SetNull() + } else { + warehouseConfigChanges.Role.Set(plan.SnowflakeConfig.Role.ValueString()) + } + } + if plan.SnowflakeConfig.AllowSso != state.SnowflakeConfig.AllowSso { + warehouseConfigChanges.AllowSso = plan.SnowflakeConfig.AllowSso.ValueBool() + } + if plan.SnowflakeConfig.OauthClientID != state.SnowflakeConfig.OauthClientID { + warehouseConfigChanges.OauthClientID = plan.SnowflakeConfig.OauthClientID.ValueString() + } + if plan.SnowflakeConfig.OauthClientSecret != state.SnowflakeConfig.OauthClientSecret { + warehouseConfigChanges.OauthClientSecret = plan.SnowflakeConfig.OauthClientSecret.ValueString() + } + + differentData := dbt_cloud.SnowflakeGlobalConnectionPointers{ + GlobalConnectionPointers: globalConfigChanges, + Config: &warehouseConfigChanges, + } + + // Update the global connection + updateConnection, err := r.client.UpdateSnowflakeGlobalConnection( + state.ID.ValueInt64(), + differentData, + ) + if err != nil { + resp.Diagnostics.AddError("Error updating global connection", err.Error()) + return + } + + // we set the computed values, no need to do it for ID as we use a PlanModifier with UseStateForUnknown() + plan.AdapterVersion = types.StringValue(updateConnection.AdapterVersion) + plan.IsSshTunnelEnabled = types.BoolValue(updateConnection.IsSshTunnelEnabled) + + // Set the updated state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + + } + +} + +func (r *globalConnectionResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + // TODO:for the import we need to pass more than just the ID... + // Or we just pass the ID but we need to get the type of connection first + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *globalConnectionResource) Configure( + _ context.Context, + req resource.ConfigureRequest, + _ *resource.ConfigureResponse, +) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*dbt_cloud.Client) +} diff --git a/pkg/framework/objects/global_connection/schema.go b/pkg/framework/objects/global_connection/schema.go new file mode 100644 index 00000000..2b1aed0c --- /dev/null +++ b/pkg/framework/objects/global_connection/schema.go @@ -0,0 +1,180 @@ +package global_connection + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *globalConnectionResource) Schema( + _ context.Context, + _ resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "adapter_version": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "is_ssh_tunnel_enabled": schema.BoolAttribute{ + Computed: true, + }, + "private_link_endpoint_id": schema.Int64Attribute{ + Optional: true, + }, + "oauth_configuration_id": schema.Int64Attribute{ + Optional: true, + }, + "bigquery": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "gcp_project_id": schema.StringAttribute{ + Required: true, + }, + "timeout_seconds": schema.Int64Attribute{ + Required: true, + }, + "private_key_id": schema.StringAttribute{ + Required: true, + }, + "private_key": schema.StringAttribute{ + Required: true, + }, + "client_email": schema.StringAttribute{ + Required: true, + }, + "client_id": schema.StringAttribute{ + Required: true, + }, + "auth_uri": schema.StringAttribute{ + Required: true, + }, + "token_uri": schema.StringAttribute{ + Required: true, + }, + "auth_provider_x509_cert_url": schema.StringAttribute{ + Required: true, + }, + "client_x509_cert_url": schema.StringAttribute{ + Required: true, + }, + "priority": schema.StringAttribute{ + Optional: true, + }, + "retries": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), + }, + "location": schema.StringAttribute{ + Optional: true, + }, + "maximum_bytes_billed": schema.Int64Attribute{ + Optional: true, + }, + "execution_project": schema.StringAttribute{ + Optional: true, + }, + "impersonate_service_account": schema.StringAttribute{ + Optional: true, + }, + "job_retry_deadline_seconds": schema.Int64Attribute{ + Optional: true, + }, + "job_creation_timeout_seconds": schema.Int64Attribute{ + Optional: true, + }, + "application_id": schema.StringAttribute{ + Required: true, + Description: "OAuth Client ID", + }, + "application_secret": schema.StringAttribute{ + Required: true, + Description: "OAuth Client Secret", + }, + "gcs_bucket": schema.StringAttribute{ + Optional: true, + }, + "dataproc_region": schema.StringAttribute{ + Optional: true, + }, + "dataproc_cluster_name": schema.StringAttribute{ + Optional: true, + }, + "scopes": schema.SetAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + Default: setdefault.StaticValue( + types.SetValueMust( + types.StringType, + []attr.Value{ + types.StringValue("https://www.googleapis.com/auth/bigquery"), + types.StringValue( + "https://www.googleapis.com/auth/cloud-platform", + ), + types.StringValue("https://www.googleapis.com/auth/drive"), + }, + ), + ), + }, + }, + }, + // this feels bad, but there is no error/warning when people add extra fields https://github.com/hashicorp/terraform/issues/33570 + "snowflake": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "account": schema.StringAttribute{ + Required: true, + }, + "database": schema.StringAttribute{ + Required: true, + }, + "warehouse": schema.StringAttribute{ + Required: true, + }, + "allow_sso": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + // TODO: required if allow_sso is true + "oauth_client_id": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "oauth_client_secret": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "role": schema.StringAttribute{ + Optional: true, + }, + "client_session_keep_alive": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + }, + }, + }, + } +} diff --git a/pkg/helper/helper.go b/pkg/helper/helper.go index 792738c9..93fbf7d4 100644 --- a/pkg/helper/helper.go +++ b/pkg/helper/helper.go @@ -70,6 +70,14 @@ func StringSetToStringSlice(set types.Set) []string { return result } +func TypesInt64ToInt64Pointer(value types.Int64) *int64 { + if value.IsNull() { + return nil + } + fieldVal := value.ValueInt64() + return &fieldVal +} + // useful for docs func DocString(inp string) string { newString := strings.ReplaceAll(inp, "~~~", "`") From 5a917da0e19b4a5d714e6e3eae52aa52be911661 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:56:19 +0200 Subject: [PATCH 04/19] Skeleton for resource acceptance test --- .../global_connection/resource_acceptance_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pkg/framework/objects/global_connection/resource_acceptance_test.go diff --git a/pkg/framework/objects/global_connection/resource_acceptance_test.go b/pkg/framework/objects/global_connection/resource_acceptance_test.go new file mode 100644 index 00000000..91233ec5 --- /dev/null +++ b/pkg/framework/objects/global_connection/resource_acceptance_test.go @@ -0,0 +1,15 @@ +package global_connection_test + +import ( + "testing" +) + +func TestAccDbtCloudGlobalConnectionResource(t *testing.T) { + + // TODO: + // - test that create/update works for all different connection types + // - have tests where we add/remove optional fields + // - have tests on the computed values + // - test the import as well + +} From 113d7b2388880482a78e6259eeeabe3aec50710e Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:56:36 +0200 Subject: [PATCH 05/19] Skeleton for datasource --- .../global_connection/data_source.go.todo | 63 ++++++++++++ .../data_source_acceptance_test.go.todo | 95 +++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 pkg/framework/objects/global_connection/data_source.go.todo create mode 100644 pkg/framework/objects/global_connection/data_source_acceptance_test.go.todo diff --git a/pkg/framework/objects/global_connection/data_source.go.todo b/pkg/framework/objects/global_connection/data_source.go.todo new file mode 100644 index 00000000..41b29131 --- /dev/null +++ b/pkg/framework/objects/global_connection/data_source.go.todo @@ -0,0 +1,63 @@ +package global_connection + +import ( + "context" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +var ( + _ datasource.DataSource = &globalConnectionDataSource{} + _ datasource.DataSourceWithConfigure = &globalConnectionDataSource{} +) + +func GlobalConnectionDataSource() datasource.DataSource { + return &globalConnectionDataSource{} +} + +type globalConnectionDataSource struct { + client *dbt_cloud.Client +} + +func (d *globalConnectionDataSource) Metadata( + _ context.Context, + req datasource.MetadataRequest, + resp *datasource.MetadataResponse, +) { + resp.TypeName = req.ProviderTypeName + "_global_connection" +} + +func (d *globalConnectionDataSource) Schema( + _ context.Context, + _ datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Retrieve notification details", + Attributes: map[string]schema.Attribute{ + // TODO + }, + } +} + +func (d *globalConnectionDataSource) Read( + ctx context.Context, + req datasource.ReadRequest, + resp *datasource.ReadResponse, +) { + // TODO, similar to read resource +} + +func (d *globalConnectionDataSource) Configure( + _ context.Context, + req datasource.ConfigureRequest, + _ *datasource.ConfigureResponse, +) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*dbt_cloud.Client) +} diff --git a/pkg/framework/objects/global_connection/data_source_acceptance_test.go.todo b/pkg/framework/objects/global_connection/data_source_acceptance_test.go.todo new file mode 100644 index 00000000..a85d7f10 --- /dev/null +++ b/pkg/framework/objects/global_connection/data_source_acceptance_test.go.todo @@ -0,0 +1,95 @@ +package global_connection_test + +import ( + "fmt" + "testing" + "time" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/acctest_helper" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDbtCloudGlobalConnectionDataSource(t *testing.T) { + + var userID string + if acctest_helper.IsDbtCloudPR() { + userID = "1" + } else { + userID = "100" + } + + currentTime := time.Now().Unix() + notificationEmail := fmt.Sprintf("%d-datasource@nomail.com", currentTime) + + randomProjectName := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + config := notification(randomProjectName, userID, notificationEmail) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.dbtcloud_notification.test_notification_external", + "notification_type", + "4", + ), + resource.TestCheckResourceAttrSet( + "data.dbtcloud_notification.test_notification_external", + "on_failure.0", + ), + resource.TestCheckResourceAttr( + "data.dbtcloud_notification.test_notification_external", + "external_email", + notificationEmail, + ), + ) + + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) +} + +func notification(projectName, userID, notificationEmail string) string { + return fmt.Sprintf(` + resource "dbtcloud_project" "test_notification_project" { + name = "%s" + } + + resource "dbtcloud_environment" "test_notification_environment" { + project_id = dbtcloud_project.test_notification_project.id + name = "Test Env Notification" + dbt_version = "%s" + type = "development" + } + + resource "dbtcloud_job" "test_notification_job_1" { + name = "Job 1 TF" + project_id = dbtcloud_project.test_notification_project.id + environment_id = dbtcloud_environment.test_notification_environment.environment_id + execute_steps = [ + "dbt compile" + ] + triggers = { + "github_webhook" : false, + "git_provider_webhook" : false, + "schedule" : false, + } + } + + resource "dbtcloud_notification" "test_notification_external" { + user_id = %s + on_failure = [dbtcloud_job.test_notification_job_1.id] + notification_type = 4 + external_email = "%s" + } + + data "dbtcloud_notification" "test_notification_external" { + notification_id = dbtcloud_notification.test_notification_external.id + } + `, projectName, acctest_helper.DBT_CLOUD_VERSION, userID, notificationEmail) +} From 3821a8bf60ca9367602c564693a768c02234c8ce Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:56:46 +0200 Subject: [PATCH 06/19] Add resource to provider --- pkg/provider/framework_provider.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/provider/framework_provider.go b/pkg/provider/framework_provider.go index 567d6873..9a82ee9c 100644 --- a/pkg/provider/framework_provider.go +++ b/pkg/provider/framework_provider.go @@ -7,6 +7,7 @@ import ( "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/environment" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/global_connection" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/group" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/group_partial_permissions" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/job" @@ -194,5 +195,6 @@ func (p *dbtCloudProvider) Resources(_ context.Context) []func() resource.Resour partial_license_map.PartialLicenseMapResource, group.GroupResource, service_token.ServiceTokenResource, + global_connection.GlobalConnectionResource, } } From 55cd79974ea37d5629ca0ac7608bc4ed88cdfe29 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:57:00 +0200 Subject: [PATCH 07/19] First doc generation for global connections --- docs/resources/global_connection.md | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 docs/resources/global_connection.md diff --git a/docs/resources/global_connection.md b/docs/resources/global_connection.md new file mode 100644 index 00000000..b51bdbb2 --- /dev/null +++ b/docs/resources/global_connection.md @@ -0,0 +1,84 @@ +--- +page_title: "dbtcloud_global_connection Resource - dbtcloud" +subcategory: "" +description: |- + +--- + +# dbtcloud_global_connection (Resource) + + + + + + + +## Schema + +### Required + +- `name` (String) + +### Optional + +- `bigquery` (Attributes) (see [below for nested schema](#nestedatt--bigquery)) +- `oauth_configuration_id` (Number) +- `private_link_endpoint_id` (Number) +- `snowflake` (Attributes) (see [below for nested schema](#nestedatt--snowflake)) + +### Read-Only + +- `adapter_version` (String) +- `id` (Number) The ID of this resource. +- `is_ssh_tunnel_enabled` (Boolean) + + +### Nested Schema for `bigquery` + +Required: + +- `application_id` (String) OAuth Client ID +- `application_secret` (String) OAuth Client Secret +- `auth_provider_x509_cert_url` (String) +- `auth_uri` (String) +- `client_email` (String) +- `client_id` (String) +- `client_x509_cert_url` (String) +- `gcp_project_id` (String) +- `private_key` (String) +- `private_key_id` (String) +- `timeout_seconds` (Number) +- `token_uri` (String) + +Optional: + +- `dataproc_cluster_name` (String) +- `dataproc_region` (String) +- `execution_project` (String) +- `gcs_bucket` (String) +- `impersonate_service_account` (String) +- `job_creation_timeout_seconds` (Number) +- `job_retry_deadline_seconds` (Number) +- `location` (String) +- `maximum_bytes_billed` (Number) +- `priority` (String) +- `retries` (Number) +- `scopes` (Set of String) + + + +### Nested Schema for `snowflake` + +Required: + +- `account` (String) +- `database` (String) +- `warehouse` (String) + +Optional: + +- `allow_sso` (Boolean) +- `client_session_keep_alive` (Boolean) +- `oauth_client_id` (String, Sensitive) +- `oauth_client_secret` (String, Sensitive) +- `role` (String) From 0441c564d063fd99cea8441f36becd737a4e3d88 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:57:21 +0200 Subject: [PATCH 08/19] Add nullable lib to handle PATCH with nulls --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index ea88fe7c..a23d4ed7 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.15.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 + github.com/oapi-codegen/nullable v1.1.0 github.com/samber/lo v1.39.0 github.com/sirupsen/logrus v1.9.3 ) diff --git a/go.sum b/go.sum index b5ab79dc..237ac726 100644 --- a/go.sum +++ b/go.sum @@ -162,6 +162,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= From 0381f9720cb3ff051ce0d61feb12a28c35f0e853 Mon Sep 17 00:00:00 2001 From: Chase Walden Date: Mon, 12 Aug 2024 14:11:41 -0600 Subject: [PATCH 09/19] Update dbtcloud_environment to take a connection_id --- .goreleaser.yml | 64 ++++++++++--------- pkg/dbt_cloud/environment.go | 8 ++- .../acctest_helper/acctest_helper.go | 40 ++++++++++++ pkg/framework/objects/environment/schema.go | 8 +++ .../{data_source.go.todo => data_source.go} | 12 ++-- .../objects/global_connection/model.go | 20 +++--- pkg/sdkv2/resources/environment.go | 21 +++++- 7 files changed, 127 insertions(+), 46 deletions(-) rename pkg/framework/objects/global_connection/{data_source.go.todo => data_source.go} (83%) diff --git a/.goreleaser.yml b/.goreleaser.yml index c7638dc1..e6cc7ece 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -4,41 +4,43 @@ before: hooks: # this is just an example and not a requirement for provider building/publishing - go mod tidy + - go generate ./... + builds: -- env: - # goreleaser does not work with CGO, it could also complicate - # usage by users in CI/CD systems like Terraform Cloud where - # they are unable to install libraries. - - CGO_ENABLED=0 - mod_timestamp: '{{ .CommitTimestamp }}' - flags: - - -trimpath - ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud.versionString={{.Env.VERSION}}"' - goos: - - freebsd - - windows - - linux - - darwin - goarch: - - amd64 - - '386' - - arm - - arm64 - ignore: - - goos: darwin - goarch: '386' - binary: '{{ .ProjectName }}_v{{ .Version }}' + - env: + # goreleaser does not work with CGO, it could also complicate + # usage by users in CI/CD systems like Terraform Cloud where + # they are unable to install libraries. + - CGO_ENABLED=0 + mod_timestamp: "{{ .CommitTimestamp }}" + flags: + - -trimpath + ldflags: + - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud.versionString={{.Env.VERSION}}"' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - "386" + - arm + - arm64 + ignore: + - goos: darwin + goarch: "386" + binary: "{{ .ProjectName }}_v{{ .Version }}" archives: -- format: zip - name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' + - format: zip + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" checksum: - name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' + name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" algorithm: sha256 signs: - artifacts: checksum args: - # if you are using this in a GitHub action or some other automated pipeline, you + # if you are using this in a GitHub action or some other automated pipeline, you # need to pass the batch flag to indicate its not interactive. - "--batch" - "--local-user" @@ -48,7 +50,7 @@ signs: - "--detach-sign" - "${artifact}" # release: - # If you want to manually examine the release before its live, uncomment this line: - # draft: true +# If you want to manually examine the release before its live, uncomment this line: +# draft: true changelog: - skip: true + disable: true diff --git a/pkg/dbt_cloud/environment.go b/pkg/dbt_cloud/environment.go index 4815fa9b..6b93bb80 100644 --- a/pkg/dbt_cloud/environment.go +++ b/pkg/dbt_cloud/environment.go @@ -33,6 +33,7 @@ type Environment struct { Custom_Environment_Variables *string `json:"custom_environment_variables"` DeploymentType *string `json:"deployment_type,omitempty"` ExtendedAttributesID *int `json:"extended_attributes_id,omitempty"` + ConnectionID *int `json:"connection_id,omitempty"` } func (c *Client) GetEnvironment(projectId int, environmentId int) (*Environment, error) { @@ -66,7 +67,9 @@ func (c *Client) CreateEnvironment( customBranch string, credentialId int, deploymentType string, - extendedAttributesID int) (*Environment, error) { + extendedAttributesID int, + connectionID int, +) (*Environment, error) { state := STATE_ACTIVE if !isActive { state = STATE_DELETED @@ -93,6 +96,9 @@ func (c *Client) CreateEnvironment( if extendedAttributesID != 0 { newEnvironment.ExtendedAttributesID = &extendedAttributesID } + if connectionID != 0 { + newEnvironment.ConnectionID = &connectionID + } newEnvironmentData, err := json.Marshal(newEnvironment) if err != nil { return nil, err diff --git a/pkg/framework/acctest_helper/acctest_helper.go b/pkg/framework/acctest_helper/acctest_helper.go index 307b8ae1..c56a2f9a 100644 --- a/pkg/framework/acctest_helper/acctest_helper.go +++ b/pkg/framework/acctest_helper/acctest_helper.go @@ -9,7 +9,9 @@ import ( "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/provider" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-mux/tf5to6server" "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" @@ -75,3 +77,41 @@ func TestAccPreCheck(t *testing.T) { func IsDbtCloudPR() bool { return os.Getenv("DBT_CLOUD_ACCOUNT_ID") == "1" } + +func HelperTestResourceSchema[R resource.Resource](t *testing.T, r R) { + ctx := context.Background() + + req := resource.SchemaRequest{} + res := resource.SchemaResponse{} + + r.Schema(ctx, req, &res) + + if res.Diagnostics.HasError() { + t.Fatalf("Error in schema: %v", res.Diagnostics) + } + + diags := res.Schema.ValidateImplementation(ctx) + + if diags.HasError() { + t.Fatalf("Error in schema validation: %v", diags) + } +} + +func HelperTestDataSourceSchema[DS datasource.DataSource](t *testing.T, ds DS) { + ctx := context.Background() + + req := datasource.SchemaRequest{} + res := datasource.SchemaResponse{} + + ds.Schema(ctx, req, &res) + + if res.Diagnostics.HasError() { + t.Fatalf("Error in schema: %v", res.Diagnostics) + } + + diags := res.Schema.ValidateImplementation(ctx) + + if diags.HasError() { + t.Fatalf("Error in schema validation: %v", diags) + } +} diff --git a/pkg/framework/objects/environment/schema.go b/pkg/framework/objects/environment/schema.go index fa7bd19a..ca65fdaa 100644 --- a/pkg/framework/objects/environment/schema.go +++ b/pkg/framework/objects/environment/schema.go @@ -55,6 +55,10 @@ func (r *environmentDataSource) Schema( Computed: true, Description: "The ID of the extended attributes applied", }, + "connection_id": schema.Int64Attribute{ + Computed: true, + Description: "A connection ID (used with Global Connections)", + }, }, } } @@ -116,6 +120,10 @@ func (r *environmentsDataSources) Schema( Computed: true, Description: "The ID of the extended attributes applied", }, + "connection_id": schema.Int64Attribute{ + Computed: true, + Description: "A connection ID (used with Global Connections)", + }, }, }, }, diff --git a/pkg/framework/objects/global_connection/data_source.go.todo b/pkg/framework/objects/global_connection/data_source.go similarity index 83% rename from pkg/framework/objects/global_connection/data_source.go.todo rename to pkg/framework/objects/global_connection/data_source.go index 41b29131..31ceac0e 100644 --- a/pkg/framework/objects/global_connection/data_source.go.todo +++ b/pkg/framework/objects/global_connection/data_source.go @@ -53,11 +53,13 @@ func (d *globalConnectionDataSource) Read( func (d *globalConnectionDataSource) Configure( _ context.Context, req datasource.ConfigureRequest, - _ *datasource.ConfigureResponse, + resp *datasource.ConfigureResponse, ) { - if req.ProviderData == nil { - return + switch c := req.ProviderData.(type) { + case nil: // do nothing + case *dbt_cloud.Client: + d.client = c + default: + resp.Diagnostics.AddError("Missing client", "A client is required to configure the global connection resource") } - - d.client = req.ProviderData.(*dbt_cloud.Client) } diff --git a/pkg/framework/objects/global_connection/model.go b/pkg/framework/objects/global_connection/model.go index 845303c8..4a0aa930 100644 --- a/pkg/framework/objects/global_connection/model.go +++ b/pkg/framework/objects/global_connection/model.go @@ -5,14 +5,18 @@ import ( ) type GlobalConnectionResourceModel struct { - ID types.Int64 `tfsdk:"id"` - AdapterVersion types.String `tfsdk:"adapter_version"` - Name types.String `tfsdk:"name"` - IsSshTunnelEnabled types.Bool `tfsdk:"is_ssh_tunnel_enabled"` - PrivateLinkEndpointId types.Int64 `tfsdk:"private_link_endpoint_id"` - OauthConfigurationId types.Int64 `tfsdk:"oauth_configuration_id"` - SnowflakeConfig *SnowflakeConfig `tfsdk:"snowflake"` - BigQueryConfig *BigQueryConfig `tfsdk:"bigquery"` + CommonConfig + SnowflakeConfig *SnowflakeConfig `tfsdk:"snowflake"` + BigQueryConfig *BigQueryConfig `tfsdk:"bigquery"` +} + +type CommonConfig struct { + ID types.Int64 `tfsdk:"id"` + AdapterVersion types.String `tfsdk:"adapter_version"` + Name types.String `tfsdk:"name"` + IsSshTunnelEnabled types.Bool `tfsdk:"is_ssh_tunnel_enabled"` + PrivateLinkEndpointId types.Int64 `tfsdk:"private_link_endpoint_id"` + OauthConfigurationId types.Int64 `tfsdk:"oauth_configuration_id"` } type BigQueryConfig struct { diff --git a/pkg/sdkv2/resources/environment.go b/pkg/sdkv2/resources/environment.go index 682a440d..176f708d 100644 --- a/pkg/sdkv2/resources/environment.go +++ b/pkg/sdkv2/resources/environment.go @@ -98,6 +98,11 @@ func ResourceEnvironment() *schema.Resource { Optional: true, Description: "ID of the extended attributes for the environment", }, + "connection_id": { + Type: schema.TypeInt, + Optional: true, + Description: "A connection ID (used with Global Connections)", + }, }, Importer: &schema.ResourceImporter{ @@ -125,6 +130,7 @@ func resourceEnvironmentCreate( customBranch := d.Get("custom_branch").(string) deploymentType := d.Get("deployment_type").(string) extendedAttributesID := d.Get("extended_attributes_id").(int) + connectionID := d.Get("connection_id").(int) environment, err := c.CreateEnvironment( isActive, @@ -137,6 +143,7 @@ func resourceEnvironmentCreate( credentialId, deploymentType, extendedAttributesID, + connectionID, ) if err != nil { return diag.FromErr(err) @@ -210,6 +217,9 @@ func resourceEnvironmentRead( if err := d.Set("extended_attributes_id", environment.ExtendedAttributesID); err != nil { return diag.FromErr(err) } + if err := d.Set("connection_id", environment.ConnectionID); err != nil { + return diag.FromErr(err) + } return diags } @@ -239,7 +249,8 @@ func resourceEnvironmentUpdate( d.HasChange("custom_branch") || d.HasChange("use_custom_branch") || d.HasChange("deployment_type") || - d.HasChange("extended_attributes_id") { + d.HasChange("extended_attributes_id") || + d.HasChange("connection_id") { environment, err := c.GetEnvironment(projectId, environmentId) if err != nil { @@ -290,6 +301,14 @@ func resourceEnvironmentUpdate( environment.ExtendedAttributesID = nil } } + if d.HasChange("connection_id") { + connectionID := d.Get("connection_id").(int) + if connectionID != 0 { + environment.ConnectionID = &connectionID + } else { + environment.ConnectionID = nil + } + } _, err = c.UpdateEnvironment(projectId, environmentId, *environment) if err != nil { return diag.FromErr(err) From 58bf771baa9ade5d42539f0f6b10228a9d13b6a7 Mon Sep 17 00:00:00 2001 From: Chase Walden Date: Mon, 12 Aug 2024 17:29:52 -0600 Subject: [PATCH 10/19] Update client for global connections --- pkg/dbt_cloud/global_connection.go | 366 +++++++----------- .../{data_source.go => data_source.go.todo} | 0 .../objects/global_connection/model.go | 10 +- .../objects/global_connection/resource.go | 128 +++--- 4 files changed, 194 insertions(+), 310 deletions(-) rename pkg/framework/objects/global_connection/{data_source.go => data_source.go.todo} (100%) diff --git a/pkg/dbt_cloud/global_connection.go b/pkg/dbt_cloud/global_connection.go index f1f5c5ee..9d6caa7a 100644 --- a/pkg/dbt_cloud/global_connection.go +++ b/pkg/dbt_cloud/global_connection.go @@ -1,289 +1,146 @@ package dbt_cloud import ( + "bytes" "encoding/json" "fmt" "net/http" - "strconv" - "strings" - - "github.com/oapi-codegen/nullable" ) -// TODO: Do we need this? Or do we just remove it? -type GlobalConnectionType int - -// Declare enum values using iota -const ( - Snowflake GlobalConnectionType = iota - BigQuery -) - -type GlobalConnectionInterface interface { - GeneratePostPayload() (*strings.Reader, error) - // UpdatePayload() (*strings.Reader, error) - ReadAnswer([]byte) (any, error) -} +// // TODO: Do we need this? Or do we just remove it? +// type GlobalConnectionType int -type GlobalConnectionPointerInterface interface { - GeneratePatchPayload() (*strings.Reader, error) -} +// // Declare enum values using iota +// const ( +// Snowflake GlobalConnectionType = iota +// BigQuery +// ) -type GlobalConnection struct { - ID *int64 `json:"id,omitempty"` - AccountID int64 `json:"account_id"` - AdapterVersion string `json:"adapter_version"` - Name string `json:"name"` - IsSshTunnelEnabled bool `json:"is_ssh_tunnel_enabled"` - PrivateLinkEndpointId *int64 `json:"private_link_endpoint_id"` - OauthConfigurationId *int64 `json:"oauth_configuration_id"` - // OauthRedirectUri *string `json:"oauth_redirect_uri"` //those are read-only fields, we could maybe get them as Computed but never send them - // IsConfiguredForNativeOauth bool `json:"is_configured_for_native_oauth"` +type GlobalConnectionConfig interface { + AdapterVersion() string } -type GlobalConnectionPointers struct { +type GlobalConnectionCommon struct { ID *int64 `json:"id,omitempty"` - AccountID *int64 `json:"account_id,omitempty"` - AdapterVersion *string `json:"adapter_version,omitempty"` - Name *string `json:"name,omitempty"` - IsSshTunnelEnabled *bool `json:"is_ssh_tunnel_enabled,omitempty"` - PrivateLinkEndpointId *int64 `json:"private_link_endpoint_id"` // those seem to be sent all the time when we modify a connection so I didn't add an omitempty for now + Name *string `json:"name"` + IsSshTunnelEnabled *bool `json:"is_ssh_tunnel_enabled"` + PrivateLinkEndpointId *int64 `json:"private_link_endpoint_id"` OauthConfigurationId *int64 `json:"oauth_configuration_id"` + // OauthRedirectUri *string `json:"oauth_redirect_uri"` //those are read-only fields, we could maybe get them as Computed but never send them + // IsConfiguredForNativeOauth bool `json:"is_configured_for_native_oauth"` } -type GlobalConnectionResponse[T GlobalConnectionInterface] struct { - Data T `json:"data"` - Status ResponseStatus `json:"status"` -} - -type SnowflakeGlobalConnection struct { - GlobalConnection - Config SnowflakeConfig `json:"config"` -} - -type SnowflakeConfig struct { - Account string `json:"account"` - Database string `json:"database"` - Warehouse string `json:"warehouse"` - ClientSessionKeepAlive bool `json:"client_session_keep_alive"` - Role string `json:"role"` - AllowSso bool `json:"allow_sso"` - OauthClientID string `json:"oauth_client_id"` - OauthClientSecret string `json:"oauth_client_secret"` -} - -// we create a pointer version so that we can PATCH parts of the connection -type SnowflakeGlobalConnectionPointers struct { - GlobalConnectionPointers - Config *SnowflakeConfigPointers `json:"config,omitempty"` -} - -// I originally put pointers everywhere and used omitempty but it prevented us from sending null values -// and differentiating between null and not sending the field at all -// TODO: rename to something else than Pointers now that those fields are not Pointers :-) -// TODO: check if we could reuse the same structure as the other one, with the nullable fields and the omitempty -type SnowflakeConfigPointers struct { - Account string `json:"account,omitempty"` - Database string `json:"database,omitempty"` - Warehouse string `json:"warehouse,omitempty"` - ClientSessionKeepAlive bool `json:"client_session_keep_alive,omitempty"` - Role nullable.Nullable[string] `json:"role,omitempty"` - AllowSso bool `json:"allow_sso,omitempty"` - OauthClientID string `json:"oauth_client_id,omitempty"` - OauthClientSecret string `json:"oauth_client_secret,omitempty"` -} - -// This is how it was before -// TODO: delete when we get the pattern working -// type SnowflakeConfigPointers struct { -// Account *string `json:"account,omitempty"` -// Database *string `json:"database,omitempty"` -// Warehouse *string `json:"warehouse,omitempty"` -// ClientSessionKeepAlive *bool `json:"client_session_keep_alive,omitempty"` -// Role *string `json:"role,omitempty"` -// AllowSso *bool `json:"allow_sso,omitempty"` -// OauthClientID *string `json:"oauth_client_id,omitempty"` -// OauthClientSecret *string `json:"oauth_client_secret,omitempty"` -// } - -type SnowflakeGlobalConnectionResponse struct { - Data SnowflakeGlobalConnection `json:"data"` - Status ResponseStatus `json:"status"` -} - -func (gc SnowflakeGlobalConnection) GeneratePostPayload() (*strings.Reader, error) { - payload, err := json.Marshal(gc) - - if err != nil { - return nil, err - } - - return strings.NewReader(string(payload)), nil -} - -func (gc SnowflakeGlobalConnection) ReadAnswer( - body []byte, -) (any, error) { - connectionResponse := SnowflakeGlobalConnectionResponse{} - err := json.Unmarshal(body, &connectionResponse) - if err != nil { - return nil, err - } - return connectionResponse.Data, nil -} - -func (gcp SnowflakeGlobalConnectionPointers) GeneratePatchPayload() (*strings.Reader, error) { - payload, err := json.Marshal(gcp) - - if err != nil { - return nil, err - } - - return strings.NewReader(string(payload)), nil -} - -type BigQueryGlobalConnection struct { - GlobalConnection - Config BigQueryConfig `json:"config"` -} - -type BigQueryConfig struct { - ProjectID string `json:"project_id"` - TimeoutSeconds int64 `json:"timeout_seconds"` - PrivateKeyID string `json:"private_key_id"` - PrivateKey string `json:"private_key"` - ClientEmail string `json:"client_email"` - ClientID string `json:"client_id"` - AuthURI string `json:"auth_uri"` - TokenURI string `json:"token_uri"` - AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"` - ClientX509CertURL string `json:"client_x509_cert_url"` - Priority string `json:"priority"` - Retries int64 `json:"retries"` - Location string `json:"location"` - MaximumBytesBilled int64 `json:"maximum_bytes_billed"` - ExecutionProject string `json:"execution_project"` - ImpersonateServiceAccount string `json:"impersonate_service_account"` - JobRetryDeadlineSeconds int64 `json:"job_retry_deadline_seconds"` - JobCreationTimeoutSeconds int64 `json:"job_creation_timeout_seconds"` - ApplicationID string `json:"application_id"` - ApplicationSecret string `json:"application_secret"` - GcsBucket string `json:"gcs_bucket"` - DataprocRegion string `json:"dataproc_region"` - DataprocClusterName string `json:"dataproc_cluster_name"` - Scopes []string `json:"scopes"` +type globalConnectionPayload[T GlobalConnectionConfig] struct { + GlobalConnectionCommon + AccountID int64 `json:"account_id"` + AdapterVersion *string `json:"adapter_version,omitempty"` + Config T `json:"config"` } -type BigQueryGlobalConnectionResponse struct { - Data BigQueryGlobalConnection `json:"data"` - Status ResponseStatus `json:"status"` +type globalConnectionResponse[T GlobalConnectionConfig] struct { + Status ResponseStatus `json:"status"` + Data globalConnectionPayload[T] `json:"data"` } -func (gc BigQueryGlobalConnection) GetCreatePayload() (*strings.Reader, error) { - payload, err := json.Marshal(gc) +type GlobalConnectionClient[T GlobalConnectionConfig] struct{ *Client } - if err != nil { - return nil, err +func NewGlobalConnectionClient[T GlobalConnectionConfig](c *Client) GlobalConnectionClient[T] { + return GlobalConnectionClient[T]{ + c, } - - return strings.NewReader(string(payload)), nil } -func GetTypedGlobalConnection[T GlobalConnectionInterface]( - c *Client, - connectionID int64, -) (*T, error) { +func (c *GlobalConnectionClient[T]) Get(connectionID int64) (*GlobalConnectionCommon, *T, error) { req, err := http.NewRequest( "GET", fmt.Sprintf( - "%s/v3/accounts/%s/connections/%d/", + "%s/v3/accounts/%d/connections/%d/", c.HostURL, - strconv.Itoa(c.AccountID), + c.AccountID, connectionID, ), nil, ) + if err != nil { - return nil, err + return nil, nil, err } body, err := c.doRequest(req) if err != nil { - return nil, err + return nil, nil, err } - var conn T - data, err := conn.ReadAnswer(body) + resp := new(globalConnectionResponse[T]) + + err = json.Unmarshal(body, resp) if err != nil { - return nil, err + return nil, nil, err } - dataTyped := data.(T) - return &dataTyped, nil + return &resp.Data.GlobalConnectionCommon, &resp.Data.Config, nil } -func (c *Client) GetSnowflakeGlobalConnection( - connectionID int64, -) (*SnowflakeGlobalConnection, error) { +func (c *GlobalConnectionClient[T]) Create(common GlobalConnectionCommon, config T) (*GlobalConnectionCommon, *T, error) { - return GetTypedGlobalConnection[SnowflakeGlobalConnection](c, connectionID) -} + buffer := new(bytes.Buffer) + enc := json.NewEncoder(buffer) -func CreateTypedGlobalConnection[T GlobalConnectionInterface]( - c *Client, - connection T, -) (*T, error) { + av := config.AdapterVersion() - payload, err := connection.GeneratePostPayload() + payload := globalConnectionPayload[T]{ + GlobalConnectionCommon: common, + AccountID: int64(c.AccountID), + AdapterVersion: &av, + Config: config, + } + + err := enc.Encode(payload) if err != nil { - return nil, err + return nil, nil, err } req, err := http.NewRequest( "POST", fmt.Sprintf( - "%s/v3/accounts/%s/connections/", + "%s/v3/accounts/%d/connections/", c.HostURL, - strconv.Itoa(c.AccountID), + c.AccountID, ), - payload, + buffer, ) if err != nil { - return nil, err + return nil, nil, err } body, err := c.doRequest(req) if err != nil { - return nil, err + return nil, nil, err } - data, err := connection.ReadAnswer(body) + resp := new(globalConnectionResponse[T]) + err = json.Unmarshal(body, resp) if err != nil { - return nil, err + return nil, nil, err } - // I could not find a way to do this without doing type inference on any - dataTyped := data.(T) - return &dataTyped, nil + return &resp.Data.GlobalConnectionCommon, &resp.Data.Config, nil } +func (c *GlobalConnectionClient[T]) Update(connectionID int64, common GlobalConnectionCommon, config T) (*GlobalConnectionCommon, *T, error) { -func (c *Client) CreateSnowflakeGlobalConnection( - connection SnowflakeGlobalConnection, -) (*SnowflakeGlobalConnection, error) { - return CreateTypedGlobalConnection(c, connection) -} + buffer := new(bytes.Buffer) + enc := json.NewEncoder(buffer) -func UpdateTypedGlobalConnection[T GlobalConnectionInterface, PT GlobalConnectionPointerInterface]( - c *Client, - connectionID int64, - connection PT, -) (*T, error) { + payload := globalConnectionPayload[T]{ + GlobalConnectionCommon: common, + AccountID: int64(c.AccountID), + Config: config, + } - payload, err := connection.GeneratePatchPayload() - // connectionData, err := json.Marshal(connection) + err := enc.Encode(payload) if err != nil { - return nil, err + return nil, nil, err } req, err := http.NewRequest( @@ -294,46 +151,33 @@ func UpdateTypedGlobalConnection[T GlobalConnectionInterface, PT GlobalConnectio c.AccountID, connectionID, ), - payload, + buffer, ) if err != nil { - return nil, err + return nil, nil, err } body, err := c.doRequest(req) if err != nil { - return nil, err + return nil, nil, err } - var conn T - data, err := conn.ReadAnswer(body) + resp := new(globalConnectionResponse[T]) + err = json.Unmarshal(body, resp) if err != nil { - return nil, err + return nil, nil, err } - // I could not find a way to do this without doing type inference on any - dataTyped := data.(T) - return &dataTyped, nil -} - -func (c *Client) UpdateSnowflakeGlobalConnection( - connectionID int64, - connectionPointers SnowflakeGlobalConnectionPointers, -) (*SnowflakeGlobalConnection, error) { - return UpdateTypedGlobalConnection[SnowflakeGlobalConnection]( - c, - connectionID, - connectionPointers, - ) + return &resp.Data.GlobalConnectionCommon, &resp.Data.Config, nil } func (c *Client) DeleteGlobalConnection(connectionID int64) (string, error) { req, err := http.NewRequest( "DELETE", fmt.Sprintf( - "%s/v3/accounts/%s/connections/%d/", + "%s/v3/accounts/%d/connections/%d/", c.HostURL, - strconv.Itoa(c.AccountID), + c.AccountID, connectionID, ), nil, @@ -349,3 +193,53 @@ func (c *Client) DeleteGlobalConnection(connectionID int64) (string, error) { return "", nil } + +// I originally put pointers everywhere and used omitempty but it prevented us from sending null values +// and differentiating between null and not sending the field at all +// TODO: rename to something else than Pointers now that those fields are not Pointers :-) +// TODO: check if we could reuse the same structure as the other one, with the nullable fields and the omitempty +type SnowflakeConfig struct { + Account *string `json:"account,omitempty"` + Database *string `json:"database,omitempty"` + Warehouse *string `json:"warehouse,omitempty"` + ClientSessionKeepAlive *bool `json:"client_session_keep_alive,omitempty"` + Role *string `json:"role,omitempty"` + AllowSso *bool `json:"allow_sso,omitempty"` + OauthClientID *string `json:"oauth_client_id,omitempty"` + OauthClientSecret *string `json:"oauth_client_secret,omitempty"` +} + +func (SnowflakeConfig) AdapterVersion() string { + return "snowflake_v0" +} + +type BigQueryConfig struct { + ProjectID *string `json:"project_id,omitempty"` + TimeoutSeconds *int64 `json:"timeout_seconds,omitempty"` + PrivateKeyID *string `json:"private_key_id,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` + ClientEmail *string `json:"client_email,omitempty"` + ClientID *string `json:"client_id,omitempty"` + AuthURI *string `json:"auth_uri,omitempty"` + TokenURI *string `json:"token_uri,omitempty"` + AuthProviderX509CertURL *string `json:"auth_provider_x509_cert_url,omitempty"` + ClientX509CertURL *string `json:"client_x509_cert_url,omitempty"` + Priority *string `json:"priority,omitempty"` + Retries *int64 `json:"retries,omitempty"` + Location *string `json:"location,omitempty"` + MaximumBytesBilled *int64 `json:"maximum_bytes_billed,omitempty"` + ExecutionProject *string `json:"execution_project,omitempty"` + ImpersonateServiceAccount *string `json:"impersonate_service_account,omitempty"` + JobRetryDeadlineSeconds *int64 `json:"job_retry_deadline_seconds,omitempty"` + JobCreationTimeoutSeconds *int64 `json:"job_creation_timeout_seconds,omitempty"` + ApplicationID *string `json:"application_id,omitempty"` + ApplicationSecret *string `json:"application_secret,omitempty"` + GcsBucket *string `json:"gcs_bucket,omitempty"` + DataprocRegion *string `json:"dataproc_region,omitempty"` + DataprocClusterName *string `json:"dataproc_cluster_name,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +func (BigQueryConfig) AdapterVersion() string { + return "bigquery_v0" +} diff --git a/pkg/framework/objects/global_connection/data_source.go b/pkg/framework/objects/global_connection/data_source.go.todo similarity index 100% rename from pkg/framework/objects/global_connection/data_source.go rename to pkg/framework/objects/global_connection/data_source.go.todo diff --git a/pkg/framework/objects/global_connection/model.go b/pkg/framework/objects/global_connection/model.go index 4a0aa930..c6bfea32 100644 --- a/pkg/framework/objects/global_connection/model.go +++ b/pkg/framework/objects/global_connection/model.go @@ -11,8 +11,8 @@ type GlobalConnectionResourceModel struct { } type CommonConfig struct { - ID types.Int64 `tfsdk:"id"` - AdapterVersion types.String `tfsdk:"adapter_version"` + ID types.Int64 `tfsdk:"id"` + // AdapterVersion types.String `tfsdk:"adapter_version"` // TODO(cwalden): needed? Name types.String `tfsdk:"name"` IsSshTunnelEnabled types.Bool `tfsdk:"is_ssh_tunnel_enabled"` PrivateLinkEndpointId types.Int64 `tfsdk:"private_link_endpoint_id"` @@ -57,6 +57,12 @@ type SnowflakeConfig struct { OauthClientSecret types.String `tfsdk:"oauth_client_secret"` } +type DatabricksConfig struct{} + +type FabricConfig struct{} + +type PostgresConfig struct{} + type GlobalConnectionDataSourceModel struct { // TBD, and do we use the same as the for the Resource model? } diff --git a/pkg/framework/objects/global_connection/resource.go b/pkg/framework/objects/global_connection/resource.go index eac8e70a..f9037834 100644 --- a/pkg/framework/objects/global_connection/resource.go +++ b/pkg/framework/objects/global_connection/resource.go @@ -56,9 +56,11 @@ func (r *globalConnectionResource) Read( connectionID := state.ID.ValueInt64() switch { - case !(state.SnowflakeConfig == nil): + case state.SnowflakeConfig != nil: - connection, err := r.client.GetSnowflakeGlobalConnection(connectionID) + c := dbt_cloud.NewGlobalConnectionClient[dbt_cloud.SnowflakeConfig](r.client) + + common, snowflakeCfg, err := c.Get(connectionID) if err != nil { if strings.HasPrefix(err.Error(), "resource-not-found") { resp.Diagnostics.AddWarning( @@ -73,30 +75,23 @@ func (r *globalConnectionResource) Read( } // global settings - state.ID = types.Int64PointerValue(connection.ID) - state.AdapterVersion = types.StringValue(connection.AdapterVersion) - state.Name = types.StringValue(connection.Name) - state.IsSshTunnelEnabled = types.BoolValue(connection.IsSshTunnelEnabled) - state.PrivateLinkEndpointId = types.Int64PointerValue(connection.PrivateLinkEndpointId) - state.OauthConfigurationId = types.Int64PointerValue(connection.OauthConfigurationId) + state.ID = types.Int64PointerValue(common.ID) + state.Name = types.StringPointerValue(common.Name) + state.IsSshTunnelEnabled = types.BoolPointerValue(common.IsSshTunnelEnabled) + state.PrivateLinkEndpointId = types.Int64PointerValue(common.PrivateLinkEndpointId) + state.OauthConfigurationId = types.Int64PointerValue(common.OauthConfigurationId) // snowflake settings - state.SnowflakeConfig.Account = types.StringValue(connection.Config.Account) - state.SnowflakeConfig.Database = types.StringValue(connection.Config.Database) - state.SnowflakeConfig.Warehouse = types.StringValue(connection.Config.Warehouse) - state.SnowflakeConfig.ClientSessionKeepAlive = types.BoolValue( - connection.Config.ClientSessionKeepAlive, - ) - state.SnowflakeConfig.AllowSso = types.BoolValue(connection.Config.AllowSso) + state.SnowflakeConfig.Account = types.StringPointerValue(snowflakeCfg.Account) + state.SnowflakeConfig.Database = types.StringPointerValue(snowflakeCfg.Database) + state.SnowflakeConfig.Warehouse = types.StringPointerValue(snowflakeCfg.Warehouse) + state.SnowflakeConfig.ClientSessionKeepAlive = types.BoolPointerValue(snowflakeCfg.ClientSessionKeepAlive) + state.SnowflakeConfig.AllowSso = types.BoolPointerValue(snowflakeCfg.AllowSso) // nullable optional fields // TODO: decide if it is better to read it as string, *string or nullable.Nullable[string] on the dbt_cloud side // in this case role can never be empty so this works but we might have cases where null and empty are different - if connection.Config.Role != "" { - state.SnowflakeConfig.Role = types.StringValue(connection.Config.Role) - } else { - state.SnowflakeConfig.Role = types.StringNull() - } + state.SnowflakeConfig.Role = types.StringPointerValue(snowflakeCfg.Role) // We don't set the sensitive fields when we read because those are secret and never returned by the API // sensitive fields: OauthClientID, OauthClientSecret @@ -121,40 +116,41 @@ func (r *globalConnectionResource) Create( return } + commonCfg := dbt_cloud.GlobalConnectionCommon{ + Name: plan.Name.ValueStringPointer(), + IsSshTunnelEnabled: plan.IsSshTunnelEnabled.ValueBoolPointer(), + PrivateLinkEndpointId: helper.TypesInt64ToInt64Pointer(plan.PrivateLinkEndpointId), + OauthConfigurationId: helper.TypesInt64ToInt64Pointer(plan.OauthConfigurationId), + } + switch { - case !(plan.SnowflakeConfig == nil): - - connectionInput := dbt_cloud.SnowflakeGlobalConnection{ - GlobalConnection: dbt_cloud.GlobalConnection{ - AccountID: int64(r.client.AccountID), - AdapterVersion: "snowflake_v0", - Name: plan.Name.ValueString(), - IsSshTunnelEnabled: plan.IsSshTunnelEnabled.ValueBool(), - PrivateLinkEndpointId: helper.TypesInt64ToInt64Pointer(plan.PrivateLinkEndpointId), - OauthConfigurationId: helper.TypesInt64ToInt64Pointer(plan.OauthConfigurationId), - }, - Config: dbt_cloud.SnowflakeConfig{ - Account: plan.SnowflakeConfig.Account.ValueString(), - Database: plan.SnowflakeConfig.Database.ValueString(), - Warehouse: plan.SnowflakeConfig.Warehouse.ValueString(), - ClientSessionKeepAlive: plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBool(), - Role: plan.SnowflakeConfig.Role.ValueString(), - AllowSso: plan.SnowflakeConfig.AllowSso.ValueBool(), - OauthClientID: plan.SnowflakeConfig.OauthClientID.ValueString(), - OauthClientSecret: plan.SnowflakeConfig.OauthClientSecret.ValueString(), - }, + case plan.SnowflakeConfig != nil: + + c := dbt_cloud.NewGlobalConnectionClient[dbt_cloud.SnowflakeConfig](r.client) + + snowflakeCfg := dbt_cloud.SnowflakeConfig{ + Account: plan.SnowflakeConfig.Account.ValueStringPointer(), + Database: plan.SnowflakeConfig.Database.ValueStringPointer(), + Warehouse: plan.SnowflakeConfig.Warehouse.ValueStringPointer(), + ClientSessionKeepAlive: plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBoolPointer(), + Role: plan.SnowflakeConfig.Role.ValueStringPointer(), + AllowSso: plan.SnowflakeConfig.AllowSso.ValueBoolPointer(), + OauthClientID: plan.SnowflakeConfig.OauthClientID.ValueStringPointer(), + OauthClientSecret: plan.SnowflakeConfig.OauthClientSecret.ValueStringPointer(), } - connection, err := r.client.CreateSnowflakeGlobalConnection(connectionInput) + commonResp, _, err := c.Create(commonCfg, snowflakeCfg) + if err != nil { resp.Diagnostics.AddError("Error creating the connection", err.Error()) return } // we set the computed values that don't have any default - plan.ID = types.Int64Value(*connection.ID) - plan.AdapterVersion = types.StringValue(connection.AdapterVersion) - plan.IsSshTunnelEnabled = types.BoolValue(connection.IsSshTunnelEnabled) + plan.ID = types.Int64PointerValue(commonResp.ID) + plan.IsSshTunnelEnabled = types.BoolPointerValue(commonResp.IsSshTunnelEnabled) + + // TODO(rest) default: panic("Unknown connection type") @@ -199,7 +195,7 @@ func (r *globalConnectionResource) Update( return } - globalConfigChanges := dbt_cloud.GlobalConnectionPointers{} + globalConfigChanges := dbt_cloud.GlobalConnectionCommon{} if plan.Name != state.Name { globalConfigChanges.Name = plan.Name.ValueStringPointer() @@ -209,60 +205,48 @@ func (r *globalConnectionResource) Update( } switch { - case !(plan.SnowflakeConfig == nil): + case plan.SnowflakeConfig != nil: + + c := dbt_cloud.NewGlobalConnectionClient[dbt_cloud.SnowflakeConfig](r.client) - warehouseConfigChanges := dbt_cloud.SnowflakeConfigPointers{} + warehouseConfigChanges := dbt_cloud.SnowflakeConfig{} // Snowflake specific ones if plan.SnowflakeConfig.Account != state.SnowflakeConfig.Account { - warehouseConfigChanges.Account = plan.SnowflakeConfig.Account.ValueString() + warehouseConfigChanges.Account = plan.SnowflakeConfig.Account.ValueStringPointer() } if plan.SnowflakeConfig.Database != state.SnowflakeConfig.Database { - warehouseConfigChanges.Database = plan.SnowflakeConfig.Database.ValueString() + warehouseConfigChanges.Database = plan.SnowflakeConfig.Database.ValueStringPointer() } if plan.SnowflakeConfig.Warehouse != state.SnowflakeConfig.Warehouse { - warehouseConfigChanges.Warehouse = plan.SnowflakeConfig.Warehouse.ValueString() + warehouseConfigChanges.Warehouse = plan.SnowflakeConfig.Warehouse.ValueStringPointer() } if plan.SnowflakeConfig.ClientSessionKeepAlive != state.SnowflakeConfig.ClientSessionKeepAlive { - warehouseConfigChanges.ClientSessionKeepAlive = plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBool() + warehouseConfigChanges.ClientSessionKeepAlive = plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBoolPointer() } // here we need to take care of the null case // when Role is Null, we still want to send it as null to the PATCH payload, to remove it, otherwise the omitempty doesn't add it to the payload if plan.SnowflakeConfig.Role != state.SnowflakeConfig.Role { - if plan.SnowflakeConfig.Role.IsNull() { - warehouseConfigChanges.Role.SetNull() - } else { - warehouseConfigChanges.Role.Set(plan.SnowflakeConfig.Role.ValueString()) - } + warehouseConfigChanges.Role = plan.SnowflakeConfig.Role.ValueStringPointer() } if plan.SnowflakeConfig.AllowSso != state.SnowflakeConfig.AllowSso { - warehouseConfigChanges.AllowSso = plan.SnowflakeConfig.AllowSso.ValueBool() + warehouseConfigChanges.AllowSso = plan.SnowflakeConfig.AllowSso.ValueBoolPointer() } if plan.SnowflakeConfig.OauthClientID != state.SnowflakeConfig.OauthClientID { - warehouseConfigChanges.OauthClientID = plan.SnowflakeConfig.OauthClientID.ValueString() + warehouseConfigChanges.OauthClientID = plan.SnowflakeConfig.OauthClientID.ValueStringPointer() } if plan.SnowflakeConfig.OauthClientSecret != state.SnowflakeConfig.OauthClientSecret { - warehouseConfigChanges.OauthClientSecret = plan.SnowflakeConfig.OauthClientSecret.ValueString() - } - - differentData := dbt_cloud.SnowflakeGlobalConnectionPointers{ - GlobalConnectionPointers: globalConfigChanges, - Config: &warehouseConfigChanges, + warehouseConfigChanges.OauthClientSecret = plan.SnowflakeConfig.OauthClientSecret.ValueStringPointer() } - // Update the global connection - updateConnection, err := r.client.UpdateSnowflakeGlobalConnection( - state.ID.ValueInt64(), - differentData, - ) + updateCommon, _, err := c.Update(state.ID.ValueInt64(), globalConfigChanges, warehouseConfigChanges) if err != nil { resp.Diagnostics.AddError("Error updating global connection", err.Error()) return } // we set the computed values, no need to do it for ID as we use a PlanModifier with UseStateForUnknown() - plan.AdapterVersion = types.StringValue(updateConnection.AdapterVersion) - plan.IsSshTunnelEnabled = types.BoolValue(updateConnection.IsSshTunnelEnabled) + plan.IsSshTunnelEnabled = types.BoolPointerValue(updateCommon.IsSshTunnelEnabled) // Set the updated state resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) From 01c78d7d38784dd9cd6b05be4698dee9f9e2c828 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:37:47 +0200 Subject: [PATCH 11/19] Fix issue with datasource for environments --- pkg/framework/objects/environment/data_source_all.go | 2 +- .../objects/environment/data_source_all_acceptance_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/framework/objects/environment/data_source_all.go b/pkg/framework/objects/environment/data_source_all.go index f11c1517..76f44c53 100644 --- a/pkg/framework/objects/environment/data_source_all.go +++ b/pkg/framework/objects/environment/data_source_all.go @@ -63,7 +63,7 @@ func (d *environmentsDataSources) Read( currentEnv := EnvironmentDataSourceModel{} currentEnv.EnvironmentID = types.Int64PointerValue( - helper.IntPointerToInt64Pointer(environment.Environment_Id), + helper.IntPointerToInt64Pointer(environment.ID), ) currentEnv.ProjectID = types.Int64Value(int64(environment.Project_Id)) diff --git a/pkg/framework/objects/environment/data_source_all_acceptance_test.go b/pkg/framework/objects/environment/data_source_all_acceptance_test.go index 0b6266fd..40405541 100644 --- a/pkg/framework/objects/environment/data_source_all_acceptance_test.go +++ b/pkg/framework/objects/environment/data_source_all_acceptance_test.go @@ -38,6 +38,10 @@ func TestAccDbtCloudEnvironmentsDataSource(t *testing.T) { "environments.0.name", randomEnvironmentName2, ), + resource.TestCheckResourceAttrSet( + "data.dbtcloud_environments.test_one_project", + "environments.0.environment_id", + ), ) resource.ParallelTest(t, resource.TestCase{ From 7e4caaa2579c6727e38c4b4617fb8c017f859a97 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:51:48 +0200 Subject: [PATCH 12/19] Add support for global connection to environments --- pkg/sdkv2/resources/environment.go | 33 ++- .../resources/environment_acceptance_test.go | 248 +++++++++++++++++- 2 files changed, 268 insertions(+), 13 deletions(-) diff --git a/pkg/sdkv2/resources/environment.go b/pkg/sdkv2/resources/environment.go index 176f708d..aa4d8064 100644 --- a/pkg/sdkv2/resources/environment.go +++ b/pkg/sdkv2/resources/environment.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -17,6 +18,15 @@ func ResourceEnvironment() *schema.Resource { ReadContext: resourceEnvironmentRead, UpdateContext: resourceEnvironmentUpdate, DeleteContext: resourceEnvironmentDelete, + Description: helper.DocString( + `Resource to manage dbt Cloud environments for the different dbt Cloud projects. + + In a given dbt Cloud project, one development environment can be defined and as many deployment environments as needed can be created. + + ~> In August 2024, dbt Cloud released the "global connection" feature, allowing connections to be defined at the account level and reused across environments and projects. + This version of the provider has the ~~~connection_id~~~ as an optional field but it is recommended to start setting it up in your projects. In future versions, this field will become mandatory. + `, + ), Schema: map[string]*schema.Schema{ "is_active": { @@ -99,12 +109,17 @@ func ResourceEnvironment() *schema.Resource { Description: "ID of the extended attributes for the environment", }, "connection_id": { - Type: schema.TypeInt, - Optional: true, - Description: "A connection ID (used with Global Connections)", + Type: schema.TypeInt, + Optional: true, + Description: helper.DocString( + `The ID of the connection to use (can be the ~~~id~~~ of a ~~~dbtcloud_global_connection~~~ or the ~~~connection_id~~~ of a legacy connection). + - At the moment, it is optional and the environment will use the connection set in ~~~dbtcloud_project_connection~~~ if ~~~connection_id~~~ is not set in this resource + - In future versions this field will become required, so it is recommended to set it from now on + - When configuring this field, it needs to be configured for all the environments of the project + - To avoid Terraform state issues, when using this field, the ~~~dbtcloud_project_connection~~~ resource should be removed from the project or you need to make sure that the ~~~connection_id~~~ is the same in ~~~dbtcloud_project_connection~~~ and in the ~~~connection_id~~~ of the Development environment of the project`, + ), }, }, - Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -217,8 +232,14 @@ func resourceEnvironmentRead( if err := d.Set("extended_attributes_id", environment.ExtendedAttributesID); err != nil { return diag.FromErr(err) } - if err := d.Set("connection_id", environment.ConnectionID); err != nil { - return diag.FromErr(err) + if v, ok := d.GetOk("connection_id"); ok && v != nil { + if err := d.Set("connection_id", environment.ConnectionID); err != nil { + return diag.FromErr(err) + } + } else { + if err := d.Set("connection_id", 0); err != nil { + return diag.FromErr(err) + } } return diags diff --git a/pkg/sdkv2/resources/environment_acceptance_test.go b/pkg/sdkv2/resources/environment_acceptance_test.go index a730a160..6bd0b85a 100644 --- a/pkg/sdkv2/resources/environment_acceptance_test.go +++ b/pkg/sdkv2/resources/environment_acceptance_test.go @@ -14,7 +14,8 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func TestAccDbtCloudEnvironmentResource(t *testing.T) { +// testing for the historical use case where connection_id is not configured at the env level +func TestAccDbtCloudEnvironmentResourceNoConnection(t *testing.T) { environmentName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) environmentName2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -26,7 +27,10 @@ func TestAccDbtCloudEnvironmentResource(t *testing.T) { CheckDestroy: testAccCheckDbtCloudEnvironmentDestroy, Steps: []resource.TestStep{ { - Config: testAccDbtCloudEnvironmentResourceBasicConfig(projectName, environmentName), + Config: testAccDbtCloudEnvironmentResourceNoConnectionBasicConfig( + projectName, + environmentName, + ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudEnvironmentExists("dbtcloud_environment.test_env"), resource.TestCheckResourceAttr( @@ -43,7 +47,7 @@ func TestAccDbtCloudEnvironmentResource(t *testing.T) { }, // RENAME { - Config: testAccDbtCloudEnvironmentResourceBasicConfig( + Config: testAccDbtCloudEnvironmentResourceNoConnectionBasicConfig( projectName, environmentName2, ), @@ -58,7 +62,7 @@ func TestAccDbtCloudEnvironmentResource(t *testing.T) { }, // MODIFY ADDING CRED { - Config: testAccDbtCloudEnvironmentResourceModifiedConfig( + Config: testAccDbtCloudEnvironmentResourceNoConnectionModifiedConfig( projectName, environmentName2, "", @@ -95,11 +99,16 @@ func TestAccDbtCloudEnvironmentResource(t *testing.T) { "deployment_type", "production", ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "connection_id", + "0", + ), ), }, // MODIFY CUSTOM BRANCH { - Config: testAccDbtCloudEnvironmentResourceModifiedConfig( + Config: testAccDbtCloudEnvironmentResourceNoConnectionModifiedConfig( projectName, environmentName2, "main", @@ -131,6 +140,11 @@ func TestAccDbtCloudEnvironmentResource(t *testing.T) { "dbtcloud_environment.test_env", "credential_id", ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "connection_id", + "0", + ), ), }, // IMPORT @@ -144,7 +158,9 @@ func TestAccDbtCloudEnvironmentResource(t *testing.T) { }) } -func testAccDbtCloudEnvironmentResourceBasicConfig(projectName, environmentName string) string { +func testAccDbtCloudEnvironmentResourceNoConnectionBasicConfig( + projectName, environmentName string, +) string { return fmt.Sprintf(` resource "dbtcloud_project" "test_project" { name = "%s" @@ -160,7 +176,7 @@ resource "dbtcloud_environment" "test_env" { `, projectName, environmentName, DBT_CLOUD_VERSION) } -func testAccDbtCloudEnvironmentResourceModifiedConfig( +func testAccDbtCloudEnvironmentResourceNoConnectionModifiedConfig( projectName, environmentName, customBranch, useCustomBranch string, ) string { return fmt.Sprintf(` @@ -188,6 +204,224 @@ resource "dbtcloud_bigquery_credential" "test_credential" { `, projectName, environmentName, DBT_CLOUD_VERSION, customBranch, useCustomBranch) } +// testing for the global connection use case where connection_id is added at the env level +func TestAccDbtCloudEnvironmentResourceConnection(t *testing.T) { + + environmentName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + environmentName2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDbtCloudEnvironmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDbtCloudEnvironmentResourceConnectionBasicConfig( + projectName, + environmentName, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckDbtCloudEnvironmentExists("dbtcloud_environment.test_env"), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "name", + environmentName, + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "deployment_type", + "production", + ), + ), + }, + // RENAME + { + Config: testAccDbtCloudEnvironmentResourceConnectionBasicConfig( + projectName, + environmentName2, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckDbtCloudEnvironmentExists("dbtcloud_environment.test_env"), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "name", + environmentName2, + ), + ), + }, + // MODIFY ADDING CRED + { + Config: testAccDbtCloudEnvironmentResourceConnectionModifiedConfig( + projectName, + environmentName2, + "", + "false", + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckDbtCloudEnvironmentExists("dbtcloud_environment.test_env"), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "name", + environmentName2, + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "dbt_version", + DBT_CLOUD_VERSION, + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "custom_branch", + "", + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "use_custom_branch", + "false", + ), + resource.TestCheckResourceAttrSet( + "dbtcloud_environment.test_env", + "credential_id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "deployment_type", + "production", + ), + ), + }, + // MODIFY CUSTOM BRANCH + { + Config: testAccDbtCloudEnvironmentResourceConnectionModifiedConfig( + projectName, + environmentName2, + "main", + "true", + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckDbtCloudEnvironmentExists("dbtcloud_environment.test_env"), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "name", + environmentName2, + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "dbt_version", + DBT_CLOUD_VERSION, + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "custom_branch", + "main", + ), + resource.TestCheckResourceAttr( + "dbtcloud_environment.test_env", + "use_custom_branch", + "true", + ), + resource.TestCheckResourceAttrSet( + "dbtcloud_environment.test_env", + "credential_id", + ), + ), + }, + // IMPORT + { + ResourceName: "dbtcloud_environment.test_env", + ImportState: true, + ImportStateVerify: true, + // TODO: Once the connection_id is mandatory, we can remove this exception and the custom logic for reading connection_id in the resource + ImportStateVerifyIgnore: []string{"connection_id"}, + }, + }, + }) +} + +func testAccDbtCloudEnvironmentResourceConnectionBasicConfig( + projectName, environmentName string, +) string { + return fmt.Sprintf(` +resource "dbtcloud_project" "test_project" { + name = "%s" +} + +resource dbtcloud_global_connection test { + name = "test connection" + + snowflake = { + account = "test" + role = "role" + warehouse = "warehouse" + database = "database" + allow_sso = false + } +} + +resource "dbtcloud_environment" "test_env" { + name = "%s" + type = "deployment" + dbt_version = "%s" + project_id = dbtcloud_project.test_project.id + deployment_type = "production" + connection_id = dbtcloud_global_connection.test.id + } + + `, projectName, environmentName, DBT_CLOUD_VERSION) +} + +func testAccDbtCloudEnvironmentResourceConnectionModifiedConfig( + projectName, environmentName, customBranch, useCustomBranch string, +) string { + return fmt.Sprintf(` +resource "dbtcloud_project" "test_project" { + name = "%s" + } + +resource dbtcloud_global_connection test { + name = "test connection" + snowflake = { + account = "test" + role = "role" + warehouse = "warehouse" + database = "database" + allow_sso = false + } +} + +resource dbtcloud_global_connection test2 { + name = "test connection" + snowflake = { + account = "test" + role = "role" + warehouse = "warehouse" + database = "database" + allow_sso = false + } +} + +resource "dbtcloud_environment" "test_env" { + name = "%s" + type = "deployment" + dbt_version = "%s" + custom_branch = "%s" + use_custom_branch = %s + project_id = dbtcloud_project.test_project.id + credential_id = dbtcloud_bigquery_credential.test_credential.credential_id + deployment_type = "production" + connection_id = dbtcloud_global_connection.test2.id +} + +resource "dbtcloud_bigquery_credential" "test_credential" { + project_id = dbtcloud_project.test_project.id + dataset = "my_bq_dataset" + num_threads = 16 + } + +`, projectName, environmentName, DBT_CLOUD_VERSION, customBranch, useCustomBranch) +} + func testAccCheckDbtCloudEnvironmentExists(resource string) resource.TestCheckFunc { return func(state *terraform.State) error { rs, ok := state.RootModule().Resources[resource] From 827f532d1a31a62d016d7d20c92c60ec66f7f013 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:52:17 +0200 Subject: [PATCH 13/19] Add deprecation notice to dbtcloud_project_connection --- pkg/sdkv2/resources/project_connection.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/sdkv2/resources/project_connection.go b/pkg/sdkv2/resources/project_connection.go index 9c11df81..edd3a420 100644 --- a/pkg/sdkv2/resources/project_connection.go +++ b/pkg/sdkv2/resources/project_connection.go @@ -32,14 +32,19 @@ func ResourceProjectConnection() *schema.Resource { ReadContext: resourceProjectConnectionRead, DeleteContext: resourceProjectConnectionDelete, - Schema: projectConnectionSchema, + Schema: projectConnectionSchema, + DeprecationMessage: "This resource is deprecated with the release of global connections and it will be removed in a future version of the provider. Going forward, please set the `connection_id` in the `dbtcloud_environment` resource instead.", Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, } } -func resourceProjectConnectionCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func resourceProjectConnectionCreate( + ctx context.Context, + d *schema.ResourceData, + m interface{}, +) diag.Diagnostics { c := m.(*dbt_cloud.Client) var diags diag.Diagnostics @@ -67,7 +72,11 @@ func resourceProjectConnectionCreate(ctx context.Context, d *schema.ResourceData return diags } -func resourceProjectConnectionRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func resourceProjectConnectionRead( + ctx context.Context, + d *schema.ResourceData, + m interface{}, +) diag.Diagnostics { c := m.(*dbt_cloud.Client) var diags diag.Diagnostics @@ -97,7 +106,11 @@ func resourceProjectConnectionRead(ctx context.Context, d *schema.ResourceData, return diags } -func resourceProjectConnectionDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func resourceProjectConnectionDelete( + ctx context.Context, + d *schema.ResourceData, + m interface{}, +) diag.Diagnostics { c := m.(*dbt_cloud.Client) var diags diag.Diagnostics From c0a23e235c7af9fcf49da4179922fb5240ad8399 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:52:49 +0200 Subject: [PATCH 14/19] Update description to mention using dbtcloud_global_connection --- docs/resources/bigquery_connection.md | 3 +++ pkg/sdkv2/resources/bigquery_connection.go | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/resources/bigquery_connection.md b/docs/resources/bigquery_connection.md index 69eb62bc..441e784b 100644 --- a/docs/resources/bigquery_connection.md +++ b/docs/resources/bigquery_connection.md @@ -3,6 +3,7 @@ page_title: "dbtcloud_bigquery_connection Resource - dbtcloud" subcategory: "" description: |- Resource to create BigQuery connections in dbt Cloud. Can be set to use OAuth for developers. + ~> This resource is going to be deprecated in the future, please use the dbtcloud_global_connection resource instead to crate BigQuery connections. --- # dbtcloud_bigquery_connection (Resource) @@ -10,6 +11,8 @@ description: |- Resource to create BigQuery connections in dbt Cloud. Can be set to use OAuth for developers. +~> This resource is going to be deprecated in the future, please use the `dbtcloud_global_connection` resource instead to crate BigQuery connections. + ## Example Usage ```terraform diff --git a/pkg/sdkv2/resources/bigquery_connection.go b/pkg/sdkv2/resources/bigquery_connection.go index 97ece7cc..69791d94 100644 --- a/pkg/sdkv2/resources/bigquery_connection.go +++ b/pkg/sdkv2/resources/bigquery_connection.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -23,7 +24,11 @@ func ResourceBigQueryConnection() *schema.Resource { UpdateContext: resourceBigQueryConnectionUpdate, DeleteContext: resourceBigQueryConnectionDelete, - Description: "Resource to create BigQuery connections in dbt Cloud. Can be set to use OAuth for developers.", + Description: helper.DocString( + `Resource to create BigQuery connections in dbt Cloud. Can be set to use OAuth for developers. + + ~> This resource is going to be deprecated in the future, please use the ~~~dbtcloud_global_connection~~~ resource instead to crate BigQuery connections.`, + ), Schema: map[string]*schema.Schema{ "connection_id": { Type: schema.TypeInt, From 1b36b202f6102c6fcee64e9d1761b6715609139c Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:53:56 +0200 Subject: [PATCH 15/19] Update docs for dbtcloud_environment --- docs/data-sources/environment.md | 1 + docs/data-sources/environments.md | 1 + docs/resources/environment.md | 18 +++++++++++++++++- .../resources/dbtcloud_environment/resource.tf | 3 +++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/data-sources/environment.md b/docs/data-sources/environment.md index 0223047d..587b918f 100644 --- a/docs/data-sources/environment.md +++ b/docs/data-sources/environment.md @@ -22,6 +22,7 @@ Retrieve data for a single environment ### Read-Only +- `connection_id` (Number) A connection ID (used with Global Connections) - `credentials_id` (Number) The project ID to which the environment belong - `custom_branch` (String) The custom branch name to use - `dbt_version` (String) Version number of dbt to use in this environment. diff --git a/docs/data-sources/environments.md b/docs/data-sources/environments.md index d12a6c25..b06d22f2 100644 --- a/docs/data-sources/environments.md +++ b/docs/data-sources/environments.md @@ -28,6 +28,7 @@ Retrieve data for multiple environments Read-Only: +- `connection_id` (Number) A connection ID (used with Global Connections) - `credentials_id` (Number) Credential ID to create the environment with. A credential is not required for development environments but is required for deployment environments - `custom_branch` (String) The custom branch name to use - `dbt_version` (String) Version number of dbt to use in this environment. diff --git a/docs/resources/environment.md b/docs/resources/environment.md index 9a3b3c2e..5a3f3c0a 100644 --- a/docs/resources/environment.md +++ b/docs/resources/environment.md @@ -2,13 +2,21 @@ page_title: "dbtcloud_environment Resource - dbtcloud" subcategory: "" description: |- - + Resource to manage dbt Cloud environments for the different dbt Cloud projects. + In a given dbt Cloud project, one development environment can be defined and as many deployment environments as needed can be created. + ~> In August 2024, dbt Cloud released the "global connection" feature, allowing connections to be defined at the account level and reused across environments and projects. + This version of the provider has the connection_id as an optional field but it is recommended to start setting it up in your projects. In future versions, this field will become mandatory. --- # dbtcloud_environment (Resource) +Resource to manage dbt Cloud environments for the different dbt Cloud projects. +In a given dbt Cloud project, one development environment can be defined and as many deployment environments as needed can be created. + +~> In August 2024, dbt Cloud released the "global connection" feature, allowing connections to be defined at the account level and reused across environments and projects. +This version of the provider has the `connection_id` as an optional field but it is recommended to start setting it up in your projects. In future versions, this field will become mandatory. ## Example Usage @@ -20,6 +28,7 @@ resource "dbtcloud_environment" "ci_environment" { project_id = dbtcloud_project.dbt_project.id type = "deployment" credential_id = dbtcloud_snowflake_credential.ci_credential.credential_id + connection_id = dbtcloud_global_connection.my_global_connection.id } // we can also set a deployment environment as being the production one @@ -30,6 +39,7 @@ resource "dbtcloud_environment" "prod_environment" { type = "deployment" credential_id = dbtcloud_snowflake_credential.prod_credential.credential_id deployment_type = "production" + connection_id = dbtcloud_connection.my_legacy_connection.connection_id } // Creating a development environment @@ -38,6 +48,7 @@ resource "dbtcloud_environment" "dev_environment" { name = "Dev" project_id = dbtcloud_project.dbt_project.id type = "development" + connection_id = dbtcloud_global_connection.my_other_global_connection } ``` @@ -53,6 +64,11 @@ resource "dbtcloud_environment" "dev_environment" { ### Optional +- `connection_id` (Number) The ID of the connection to use (can be the `id` of a `dbtcloud_global_connection` or the `connection_id` of a legacy connection). + - At the moment, it is optional and the environment will use the connection set in `dbtcloud_project_connection` if `connection_id` is not set in this resource + - In future versions this field will become required, so it is recommended to set it from now on + - When configuring this field, it needs to be configured for all the environments of the project + - To avoid Terraform state issues, when using this field, the `dbtcloud_project_connection` resource should be removed from the project or you need to make sure that the `connection_id` is the same in `dbtcloud_project_connection` and in the `connection_id` of the Development environment of the project - `credential_id` (Number) Credential ID to create the environment with. A credential is not required for development environments but is required for deployment environments - `custom_branch` (String) Which custom branch to use in this environment - `deployment_type` (String) The type of environment. Only valid for environments of type 'deployment' and for now can only be 'production', 'staging' or left empty for generic environments diff --git a/examples/resources/dbtcloud_environment/resource.tf b/examples/resources/dbtcloud_environment/resource.tf index 5efc12cd..ac6d7f1f 100644 --- a/examples/resources/dbtcloud_environment/resource.tf +++ b/examples/resources/dbtcloud_environment/resource.tf @@ -5,6 +5,7 @@ resource "dbtcloud_environment" "ci_environment" { project_id = dbtcloud_project.dbt_project.id type = "deployment" credential_id = dbtcloud_snowflake_credential.ci_credential.credential_id + connection_id = dbtcloud_global_connection.my_global_connection.id } // we can also set a deployment environment as being the production one @@ -15,6 +16,7 @@ resource "dbtcloud_environment" "prod_environment" { type = "deployment" credential_id = dbtcloud_snowflake_credential.prod_credential.credential_id deployment_type = "production" + connection_id = dbtcloud_connection.my_legacy_connection.connection_id } // Creating a development environment @@ -23,4 +25,5 @@ resource "dbtcloud_environment" "dev_environment" { name = "Dev" project_id = dbtcloud_project.dbt_project.id type = "development" + connection_id = dbtcloud_global_connection.my_other_global_connection } From 46084fd1bdfd21cce66d9ca799f69ce59fdda68f Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:55:17 +0200 Subject: [PATCH 16/19] Add TODO for future cleaning --- pkg/dbt_cloud/environment.go | 55 ++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/pkg/dbt_cloud/environment.go b/pkg/dbt_cloud/environment.go index 6b93bb80..4d039392 100644 --- a/pkg/dbt_cloud/environment.go +++ b/pkg/dbt_cloud/environment.go @@ -23,7 +23,7 @@ type Environment struct { Type string `json:"type"` Use_Custom_Branch bool `json:"use_custom_branch"` Custom_Branch *string `json:"custom_branch"` - Environment_Id *int `json:"-"` + Environment_Id *int `json:"-"` //TODO: check why this is here Support_Docs bool `json:"supports_docs"` Created_At *string `json:"created_at"` Updated_At *string `json:"updated_at"` @@ -37,7 +37,17 @@ type Environment struct { } func (c *Client) GetEnvironment(projectId int, environmentId int) (*Environment, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("%s/v3/accounts/%d/projects/%d/environments/%d/", c.HostURL, c.AccountID, projectId, environmentId), nil) + req, err := http.NewRequest( + "GET", + fmt.Sprintf( + "%s/v3/accounts/%d/projects/%d/environments/%d/", + c.HostURL, + c.AccountID, + projectId, + environmentId, + ), + nil, + ) if err != nil { return nil, err } @@ -104,7 +114,16 @@ func (c *Client) CreateEnvironment( return nil, err } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/v3/accounts/%d/projects/%d/environments/", c.HostURL, c.AccountID, projectId), strings.NewReader(string(newEnvironmentData))) + req, err := http.NewRequest( + "POST", + fmt.Sprintf( + "%s/v3/accounts/%d/projects/%d/environments/", + c.HostURL, + c.AccountID, + projectId, + ), + strings.NewReader(string(newEnvironmentData)), + ) if err != nil { return nil, err } @@ -124,7 +143,11 @@ func (c *Client) CreateEnvironment( return &environmentResponse.Data, nil } -func (c *Client) UpdateEnvironment(projectId int, environmentId int, environment Environment) (*Environment, error) { +func (c *Client) UpdateEnvironment( + projectId int, + environmentId int, + environment Environment, +) (*Environment, error) { // we don't send the environment details in the update request, just the credential_id environment.Credentials = nil @@ -134,7 +157,17 @@ func (c *Client) UpdateEnvironment(projectId int, environmentId int, environment return nil, err } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/v3/accounts/%d/projects/%d/environments/%d/", c.HostURL, c.AccountID, projectId, environmentId), strings.NewReader(string(environmentData))) + req, err := http.NewRequest( + "POST", + fmt.Sprintf( + "%s/v3/accounts/%d/projects/%d/environments/%d/", + c.HostURL, + c.AccountID, + projectId, + environmentId, + ), + strings.NewReader(string(environmentData)), + ) if err != nil { return nil, err } @@ -155,7 +188,17 @@ func (c *Client) UpdateEnvironment(projectId int, environmentId int, environment } func (c *Client) DeleteEnvironment(projectId, environmentId int) (string, error) { - req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/v3/accounts/%d/projects/%d/environments/%d/", c.HostURL, c.AccountID, projectId, environmentId), nil) + req, err := http.NewRequest( + "DELETE", + fmt.Sprintf( + "%s/v3/accounts/%d/projects/%d/environments/%d/", + c.HostURL, + c.AccountID, + projectId, + environmentId, + ), + nil, + ) if err != nil { return "", err } From d60dfdb72b5b25fb49a97751376e390c75ce5364 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:55:57 +0200 Subject: [PATCH 17/19] Add connection id to the environment datasources --- pkg/framework/objects/environment/model.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/framework/objects/environment/model.go b/pkg/framework/objects/environment/model.go index 599965f8..3af1e2bd 100644 --- a/pkg/framework/objects/environment/model.go +++ b/pkg/framework/objects/environment/model.go @@ -13,6 +13,7 @@ type EnvironmentDataSourceModel struct { CustomBranch types.String `tfsdk:"custom_branch"` DeploymentType types.String `tfsdk:"deployment_type"` ExtendedAttributesID types.Int64 `tfsdk:"extended_attributes_id"` + ConnectionID types.Int64 `tfsdk:"connection_id"` } type EnvironmentsDataSourceModel struct { From c6f6984a07c44fce7a06a3c3a24bac20d17c3482 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:56:23 +0200 Subject: [PATCH 18/19] Add support for Snowflake and BQ in global_connection --- .../dbtcloud_global_connection/resource.tf | 32 ++ pkg/dbt_cloud/global_connection.go | 101 ++-- .../objects/global_connection/model.go | 84 +-- .../objects/global_connection/resource.go | 521 +++++++++++++++++- .../resource_acceptance_test.go | 295 +++++++++- .../objects/global_connection/schema.go | 147 +++-- pkg/helper/helper.go | 8 + 7 files changed, 1016 insertions(+), 172 deletions(-) create mode 100644 examples/resources/dbtcloud_global_connection/resource.tf diff --git a/examples/resources/dbtcloud_global_connection/resource.tf b/examples/resources/dbtcloud_global_connection/resource.tf new file mode 100644 index 00000000..b4f74494 --- /dev/null +++ b/examples/resources/dbtcloud_global_connection/resource.tf @@ -0,0 +1,32 @@ +resource "dbtcloud_global_connection" "snowflake" { + name = "My Snowflake connection" + // we can set Privatelink if needed + private_link_endpoint_id = data.dbtcloud_privatelink_endpoint.my_private_link.id + snowflake = { + account = "my-snowflake-account" + database = "MY_DATABASE" + warehouse = "MY_WAREHOUSE" + client_session_keep_alive = false + allow_sso = true + oauth_client_id = "yourclientid" + oauth_client_secret = "yourclientsecret" + } +} + +resource "dbtcloud_global_connection" "bigquery" { + name = "My BigQuery connection" + bigquery = { + gcp_project_id = "my-gcp-project-id" + timeout_seconds = 1000 + private_key_id = "my-private-key-id" + private_key = "ABCDEFGHIJKL" + client_email = "my_client_email" + client_id = "my_client_id" + auth_uri = "my_auth_uri" + token_uri = "my_token_uri" + auth_provider_x509_cert_url = "my_auth_provider_x509_cert_url" + client_x509_cert_url = "my_client_x509_cert_url" + application_id = "oauth_application_id" + application_secret = "oauth_secret_id" + } +} \ No newline at end of file diff --git a/pkg/dbt_cloud/global_connection.go b/pkg/dbt_cloud/global_connection.go index 9d6caa7a..6466d472 100644 --- a/pkg/dbt_cloud/global_connection.go +++ b/pkg/dbt_cloud/global_connection.go @@ -5,27 +5,20 @@ import ( "encoding/json" "fmt" "net/http" -) - -// // TODO: Do we need this? Or do we just remove it? -// type GlobalConnectionType int -// // Declare enum values using iota -// const ( -// Snowflake GlobalConnectionType = iota -// BigQuery -// ) + "github.com/oapi-codegen/nullable" +) type GlobalConnectionConfig interface { AdapterVersion() string } type GlobalConnectionCommon struct { - ID *int64 `json:"id,omitempty"` - Name *string `json:"name"` - IsSshTunnelEnabled *bool `json:"is_ssh_tunnel_enabled"` - PrivateLinkEndpointId *int64 `json:"private_link_endpoint_id"` - OauthConfigurationId *int64 `json:"oauth_configuration_id"` + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + IsSshTunnelEnabled *bool `json:"is_ssh_tunnel_enabled,omitempty"` + PrivateLinkEndpointId nullable.Nullable[string] `json:"private_link_endpoint_id,omitempty"` + OauthConfigurationId *int64 `json:"oauth_configuration_id,omitempty"` // OauthRedirectUri *string `json:"oauth_redirect_uri"` //those are read-only fields, we could maybe get them as Computed but never send them // IsConfiguredForNativeOauth bool `json:"is_configured_for_native_oauth"` } @@ -81,7 +74,10 @@ func (c *GlobalConnectionClient[T]) Get(connectionID int64) (*GlobalConnectionCo return &resp.Data.GlobalConnectionCommon, &resp.Data.Config, nil } -func (c *GlobalConnectionClient[T]) Create(common GlobalConnectionCommon, config T) (*GlobalConnectionCommon, *T, error) { +func (c *GlobalConnectionClient[T]) Create( + common GlobalConnectionCommon, + config T, +) (*GlobalConnectionCommon, *T, error) { buffer := new(bytes.Buffer) enc := json.NewEncoder(buffer) @@ -127,7 +123,12 @@ func (c *GlobalConnectionClient[T]) Create(common GlobalConnectionCommon, config return &resp.Data.GlobalConnectionCommon, &resp.Data.Config, nil } -func (c *GlobalConnectionClient[T]) Update(connectionID int64, common GlobalConnectionCommon, config T) (*GlobalConnectionCommon, *T, error) { + +func (c *GlobalConnectionClient[T]) Update( + connectionID int64, + common GlobalConnectionCommon, + config T, +) (*GlobalConnectionCommon, *T, error) { buffer := new(bytes.Buffer) enc := json.NewEncoder(buffer) @@ -194,19 +195,15 @@ func (c *Client) DeleteGlobalConnection(connectionID int64) (string, error) { return "", nil } -// I originally put pointers everywhere and used omitempty but it prevented us from sending null values -// and differentiating between null and not sending the field at all -// TODO: rename to something else than Pointers now that those fields are not Pointers :-) -// TODO: check if we could reuse the same structure as the other one, with the nullable fields and the omitempty type SnowflakeConfig struct { - Account *string `json:"account,omitempty"` - Database *string `json:"database,omitempty"` - Warehouse *string `json:"warehouse,omitempty"` - ClientSessionKeepAlive *bool `json:"client_session_keep_alive,omitempty"` - Role *string `json:"role,omitempty"` - AllowSso *bool `json:"allow_sso,omitempty"` - OauthClientID *string `json:"oauth_client_id,omitempty"` - OauthClientSecret *string `json:"oauth_client_secret,omitempty"` + Account *string `json:"account,omitempty"` + Database *string `json:"database,omitempty"` + Warehouse *string `json:"warehouse,omitempty"` + ClientSessionKeepAlive *bool `json:"client_session_keep_alive,omitempty"` + Role nullable.Nullable[string] `json:"role,omitempty"` + AllowSso *bool `json:"allow_sso,omitempty"` + OauthClientID *string `json:"oauth_client_id,omitempty"` + OauthClientSecret *string `json:"oauth_client_secret,omitempty"` } func (SnowflakeConfig) AdapterVersion() string { @@ -214,30 +211,30 @@ func (SnowflakeConfig) AdapterVersion() string { } type BigQueryConfig struct { - ProjectID *string `json:"project_id,omitempty"` - TimeoutSeconds *int64 `json:"timeout_seconds,omitempty"` - PrivateKeyID *string `json:"private_key_id,omitempty"` - PrivateKey *string `json:"private_key,omitempty"` - ClientEmail *string `json:"client_email,omitempty"` - ClientID *string `json:"client_id,omitempty"` - AuthURI *string `json:"auth_uri,omitempty"` - TokenURI *string `json:"token_uri,omitempty"` - AuthProviderX509CertURL *string `json:"auth_provider_x509_cert_url,omitempty"` - ClientX509CertURL *string `json:"client_x509_cert_url,omitempty"` - Priority *string `json:"priority,omitempty"` - Retries *int64 `json:"retries,omitempty"` - Location *string `json:"location,omitempty"` - MaximumBytesBilled *int64 `json:"maximum_bytes_billed,omitempty"` - ExecutionProject *string `json:"execution_project,omitempty"` - ImpersonateServiceAccount *string `json:"impersonate_service_account,omitempty"` - JobRetryDeadlineSeconds *int64 `json:"job_retry_deadline_seconds,omitempty"` - JobCreationTimeoutSeconds *int64 `json:"job_creation_timeout_seconds,omitempty"` - ApplicationID *string `json:"application_id,omitempty"` - ApplicationSecret *string `json:"application_secret,omitempty"` - GcsBucket *string `json:"gcs_bucket,omitempty"` - DataprocRegion *string `json:"dataproc_region,omitempty"` - DataprocClusterName *string `json:"dataproc_cluster_name,omitempty"` - Scopes []string `json:"scopes,omitempty"` + ProjectID *string `json:"project_id,omitempty"` + TimeoutSeconds *int64 `json:"timeout_seconds,omitempty"` + PrivateKeyID *string `json:"private_key_id,omitempty"` + PrivateKey *string `json:"private_key,omitempty"` + ClientEmail *string `json:"client_email,omitempty"` + ClientID *string `json:"client_id,omitempty"` + AuthURI *string `json:"auth_uri,omitempty"` + TokenURI *string `json:"token_uri,omitempty"` + AuthProviderX509CertURL *string `json:"auth_provider_x509_cert_url,omitempty"` + ClientX509CertURL *string `json:"client_x509_cert_url,omitempty"` + Priority nullable.Nullable[string] `json:"priority,omitempty"` + Retries *int64 `json:"retries,omitempty"` //not nullable because there is a default in the UI + Location nullable.Nullable[string] `json:"location,omitempty"` + MaximumBytesBilled nullable.Nullable[int64] `json:"maximum_bytes_billed,omitempty"` + ExecutionProject nullable.Nullable[string] `json:"execution_project,omitempty"` + ImpersonateServiceAccount nullable.Nullable[string] `json:"impersonate_service_account,omitempty"` + JobRetryDeadlineSeconds nullable.Nullable[int64] `json:"job_retry_deadline_seconds,omitempty"` + JobCreationTimeoutSeconds nullable.Nullable[int64] `json:"job_creation_timeout_seconds,omitempty"` + ApplicationID nullable.Nullable[string] `json:"application_id,omitempty"` + ApplicationSecret nullable.Nullable[string] `json:"application_secret,omitempty"` + GcsBucket nullable.Nullable[string] `json:"gcs_bucket,omitempty"` + DataprocRegion nullable.Nullable[string] `json:"dataproc_region,omitempty"` + DataprocClusterName nullable.Nullable[string] `json:"dataproc_cluster_name,omitempty"` + Scopes []string `json:"scopes,omitempty"` //not nullable because there is a default in the UI } func (BigQueryConfig) AdapterVersion() string { diff --git a/pkg/framework/objects/global_connection/model.go b/pkg/framework/objects/global_connection/model.go index c6bfea32..0d1e40b8 100644 --- a/pkg/framework/objects/global_connection/model.go +++ b/pkg/framework/objects/global_connection/model.go @@ -4,46 +4,45 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type GlobalConnectionResourceModel struct { - CommonConfig - SnowflakeConfig *SnowflakeConfig `tfsdk:"snowflake"` - BigQueryConfig *BigQueryConfig `tfsdk:"bigquery"` -} +var supportedGlobalConfigTypes = []string{"bigquery", "snowflake"} -type CommonConfig struct { - ID types.Int64 `tfsdk:"id"` - // AdapterVersion types.String `tfsdk:"adapter_version"` // TODO(cwalden): needed? - Name types.String `tfsdk:"name"` - IsSshTunnelEnabled types.Bool `tfsdk:"is_ssh_tunnel_enabled"` - PrivateLinkEndpointId types.Int64 `tfsdk:"private_link_endpoint_id"` - OauthConfigurationId types.Int64 `tfsdk:"oauth_configuration_id"` +type GlobalConnectionResourceModel struct { + ID types.Int64 `tfsdk:"id"` + AdapterVersion types.String `tfsdk:"adapter_version"` + Name types.String `tfsdk:"name"` + IsSshTunnelEnabled types.Bool `tfsdk:"is_ssh_tunnel_enabled"` + PrivateLinkEndpointId types.String `tfsdk:"private_link_endpoint_id"` + OauthConfigurationId types.Int64 `tfsdk:"oauth_configuration_id"` + SnowflakeConfig *SnowflakeConfig `tfsdk:"snowflake"` + BigQueryConfig *BigQueryConfig `tfsdk:"bigquery"` } type BigQueryConfig struct { - GCPProjectID types.String `tfsdk:"gcp_project_id"` - TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` - PrivateKeyID types.String `tfsdk:"private_key_id"` - PrivateKey types.String `tfsdk:"private_key"` - ClientEmail types.String `tfsdk:"client_email"` - ClientID types.String `tfsdk:"client_id"` - AuthURI types.String `tfsdk:"auth_uri"` - TokenURI types.String `tfsdk:"token_uri"` - AuthProviderX509CertURL types.String `tfsdk:"auth_provider_x509_cert_url"` - ClientX509CertURL types.String `tfsdk:"client_x509_cert_url"` - Priority types.String `tfsdk:"priority"` - Retries types.Int64 `tfsdk:"retries"` - Location types.String `tfsdk:"location"` - MaximumBytesBilled types.Int64 `tfsdk:"maximum_bytes_billed"` - ExecutionProject types.String `tfsdk:"execution_project"` - ImpersonateServiceAccount types.String `tfsdk:"impersonate_service_account"` - JobRetryDeadlineSeconds types.Int64 `tfsdk:"job_retry_deadline_seconds"` - JobCreationTimeoutSeconds types.Int64 `tfsdk:"job_creation_timeout_seconds"` - ApplicationID types.String `tfsdk:"application_id"` - ApplicationSecret types.String `tfsdk:"application_secret"` - GcsBucket types.String `tfsdk:"gcs_bucket"` - DataprocRegion types.String `tfsdk:"dataproc_region"` - DataprocClusterName types.String `tfsdk:"dataproc_cluster_name"` - Scopes []types.String `tfsdk:"scopes"` + GCPProjectID types.String `tfsdk:"gcp_project_id"` + TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` + PrivateKeyID types.String `tfsdk:"private_key_id"` + PrivateKey types.String `tfsdk:"private_key"` + ClientEmail types.String `tfsdk:"client_email"` + ClientID types.String `tfsdk:"client_id"` + AuthURI types.String `tfsdk:"auth_uri"` + TokenURI types.String `tfsdk:"token_uri"` + AuthProviderX509CertURL types.String `tfsdk:"auth_provider_x509_cert_url"` + ClientX509CertURL types.String `tfsdk:"client_x509_cert_url"` + Retries types.Int64 `tfsdk:"retries"` + Scopes []types.String `tfsdk:"scopes"` + // nullable + Priority types.String `tfsdk:"priority"` + Location types.String `tfsdk:"location"` + MaximumBytesBilled types.Int64 `tfsdk:"maximum_bytes_billed"` + ExecutionProject types.String `tfsdk:"execution_project"` + ImpersonateServiceAccount types.String `tfsdk:"impersonate_service_account"` + JobRetryDeadlineSeconds types.Int64 `tfsdk:"job_retry_deadline_seconds"` + JobCreationTimeoutSeconds types.Int64 `tfsdk:"job_creation_timeout_seconds"` + ApplicationID types.String `tfsdk:"application_id"` + ApplicationSecret types.String `tfsdk:"application_secret"` + GcsBucket types.String `tfsdk:"gcs_bucket"` + DataprocRegion types.String `tfsdk:"dataproc_region"` + DataprocClusterName types.String `tfsdk:"dataproc_cluster_name"` } type SnowflakeConfig struct { @@ -51,18 +50,27 @@ type SnowflakeConfig struct { Database types.String `tfsdk:"database"` Warehouse types.String `tfsdk:"warehouse"` ClientSessionKeepAlive types.Bool `tfsdk:"client_session_keep_alive"` - Role types.String `tfsdk:"role"` AllowSso types.Bool `tfsdk:"allow_sso"` OauthClientID types.String `tfsdk:"oauth_client_id"` OauthClientSecret types.String `tfsdk:"oauth_client_secret"` + // nullable + Role types.String `tfsdk:"role"` } type DatabricksConfig struct{} -type FabricConfig struct{} +type RedshiftConfig struct{} type PostgresConfig struct{} +type FabricConfig struct{} + +type StarburstConfig struct{} + +type SparkConfig struct{} + +type SynapseConfig struct{} + type GlobalConnectionDataSourceModel struct { // TBD, and do we use the same as the for the Resource model? } diff --git a/pkg/framework/objects/global_connection/resource.go b/pkg/framework/objects/global_connection/resource.go index f9037834..c0c03342 100644 --- a/pkg/framework/objects/global_connection/resource.go +++ b/pkg/framework/objects/global_connection/resource.go @@ -10,13 +10,15 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/samber/lo" ) var ( - _ resource.Resource = &globalConnectionResource{} - _ resource.ResourceWithConfigure = &globalConnectionResource{} - _ resource.ResourceWithImportState = &globalConnectionResource{} + _ resource.Resource = &globalConnectionResource{} + _ resource.ResourceWithConfigure = &globalConnectionResource{} + // _ resource.ResourceWithImportState = &globalConnectionResource{} _ resource.ResourceWithConfigValidators = &globalConnectionResource{} + _ resource.ResourceWithModifyPlan = &globalConnectionResource{} ) func GlobalConnectionResource() resource.Resource { @@ -44,6 +46,66 @@ func (r globalConnectionResource) ConfigValidators(ctx context.Context) []resour } } +func (r globalConnectionResource) ModifyPlan( + ctx context.Context, + req resource.ModifyPlanRequest, + resp *resource.ModifyPlanResponse, +) { + + var plan, state GlobalConnectionResourceModel + + if req.Plan.Raw.IsNull() || req.State.Raw.IsNull() { + // we only check when both plan and state are not null + return + } + + // Read the current state and planned state + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + type ConfigState struct { + WasNull bool + IsNull bool + } + + configStates := map[string]ConfigState{ + "bigquery": { + WasNull: state.BigQueryConfig == nil, + IsNull: plan.BigQueryConfig == nil, + }, + "snowflake": { + WasNull: state.SnowflakeConfig == nil, + IsNull: plan.SnowflakeConfig == nil, + }, + // Add more types here as needed + } + + configStatesVals := lo.Keys(configStates) + left, right := lo.Difference(configStatesVals, supportedGlobalConfigTypes) + if len(left) > 0 || len(right) > 0 { + panic( + "ModifyPlan is missing some of the Data Warehouse types. The provider needs to be updated", + ) + } + + for configType, configState := range configStates { + if (configState.WasNull && !configState.IsNull) || + (!configState.WasNull && configState.IsNull) { + resp.RequiresReplace = append(resp.RequiresReplace, path.Root(configType)) + } + } + +} + func (r *globalConnectionResource) Read( ctx context.Context, req resource.ReadRequest, @@ -76,26 +138,166 @@ func (r *globalConnectionResource) Read( // global settings state.ID = types.Int64PointerValue(common.ID) + state.AdapterVersion = types.StringValue(snowflakeCfg.AdapterVersion()) state.Name = types.StringPointerValue(common.Name) state.IsSshTunnelEnabled = types.BoolPointerValue(common.IsSshTunnelEnabled) - state.PrivateLinkEndpointId = types.Int64PointerValue(common.PrivateLinkEndpointId) state.OauthConfigurationId = types.Int64PointerValue(common.OauthConfigurationId) + // nullable common fields + if !common.PrivateLinkEndpointId.IsNull() { + state.PrivateLinkEndpointId = types.StringValue(common.PrivateLinkEndpointId.MustGet()) + } else { + state.PrivateLinkEndpointId = types.StringNull() + } + // snowflake settings state.SnowflakeConfig.Account = types.StringPointerValue(snowflakeCfg.Account) state.SnowflakeConfig.Database = types.StringPointerValue(snowflakeCfg.Database) state.SnowflakeConfig.Warehouse = types.StringPointerValue(snowflakeCfg.Warehouse) - state.SnowflakeConfig.ClientSessionKeepAlive = types.BoolPointerValue(snowflakeCfg.ClientSessionKeepAlive) + state.SnowflakeConfig.ClientSessionKeepAlive = types.BoolPointerValue( + snowflakeCfg.ClientSessionKeepAlive, + ) state.SnowflakeConfig.AllowSso = types.BoolPointerValue(snowflakeCfg.AllowSso) // nullable optional fields // TODO: decide if it is better to read it as string, *string or nullable.Nullable[string] on the dbt_cloud side // in this case role can never be empty so this works but we might have cases where null and empty are different - state.SnowflakeConfig.Role = types.StringPointerValue(snowflakeCfg.Role) + if !snowflakeCfg.Role.IsNull() { + state.SnowflakeConfig.Role = types.StringValue(snowflakeCfg.Role.MustGet()) + } else { + state.SnowflakeConfig.Role = types.StringNull() + } // We don't set the sensitive fields when we read because those are secret and never returned by the API // sensitive fields: OauthClientID, OauthClientSecret + case state.BigQueryConfig != nil: + + c := dbt_cloud.NewGlobalConnectionClient[dbt_cloud.BigQueryConfig](r.client) + + common, bigqueryCfg, err := c.Get(connectionID) + if err != nil { + if strings.HasPrefix(err.Error(), "resource-not-found") { + resp.Diagnostics.AddWarning( + "Resource not found", + "The connection resource was not found and has been removed from the state.", + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Error getting the connection", err.Error()) + return + } + + // global settings + state.ID = types.Int64PointerValue(common.ID) + state.AdapterVersion = types.StringValue(bigqueryCfg.AdapterVersion()) + state.Name = types.StringPointerValue(common.Name) + state.IsSshTunnelEnabled = types.BoolPointerValue(common.IsSshTunnelEnabled) + state.OauthConfigurationId = types.Int64PointerValue(common.OauthConfigurationId) + + // nullable common fields + if !common.PrivateLinkEndpointId.IsNull() { + state.PrivateLinkEndpointId = types.StringValue(common.PrivateLinkEndpointId.MustGet()) + } else { + state.PrivateLinkEndpointId = types.StringNull() + } + + // BigQuery settings + state.BigQueryConfig.GCPProjectID = types.StringPointerValue(bigqueryCfg.ProjectID) + state.BigQueryConfig.TimeoutSeconds = types.Int64PointerValue(bigqueryCfg.TimeoutSeconds) + state.BigQueryConfig.PrivateKeyID = types.StringPointerValue(bigqueryCfg.PrivateKeyID) + state.BigQueryConfig.ClientEmail = types.StringPointerValue(bigqueryCfg.ClientEmail) + state.BigQueryConfig.ClientID = types.StringPointerValue(bigqueryCfg.ClientID) + state.BigQueryConfig.AuthURI = types.StringPointerValue(bigqueryCfg.AuthURI) + state.BigQueryConfig.TokenURI = types.StringPointerValue(bigqueryCfg.TokenURI) + state.BigQueryConfig.AuthProviderX509CertURL = types.StringPointerValue( + bigqueryCfg.AuthProviderX509CertURL, + ) + state.BigQueryConfig.ClientX509CertURL = types.StringPointerValue( + bigqueryCfg.ClientX509CertURL, + ) + state.BigQueryConfig.Retries = types.Int64PointerValue(bigqueryCfg.Retries) + state.BigQueryConfig.Scopes = helper.SliceStringToSliceTypesString(bigqueryCfg.Scopes) + + // nullable optional fields + if !bigqueryCfg.Priority.IsNull() { + state.BigQueryConfig.Priority = types.StringValue(bigqueryCfg.Priority.MustGet()) + } else { + state.BigQueryConfig.Priority = types.StringNull() + } + + if !bigqueryCfg.Location.IsNull() { + state.BigQueryConfig.Location = types.StringValue(bigqueryCfg.Location.MustGet()) + } else { + state.BigQueryConfig.Location = types.StringNull() + } + + if !bigqueryCfg.MaximumBytesBilled.IsNull() { + state.BigQueryConfig.MaximumBytesBilled = types.Int64Value( + bigqueryCfg.MaximumBytesBilled.MustGet(), + ) + } else { + state.BigQueryConfig.MaximumBytesBilled = types.Int64Null() + } + + if !bigqueryCfg.ExecutionProject.IsNull() { + state.BigQueryConfig.ExecutionProject = types.StringValue( + bigqueryCfg.ExecutionProject.MustGet(), + ) + } else { + state.BigQueryConfig.ExecutionProject = types.StringNull() + } + + if !bigqueryCfg.ImpersonateServiceAccount.IsNull() { + state.BigQueryConfig.ImpersonateServiceAccount = types.StringValue( + bigqueryCfg.ImpersonateServiceAccount.MustGet(), + ) + } else { + state.BigQueryConfig.ImpersonateServiceAccount = types.StringNull() + } + + if !bigqueryCfg.JobRetryDeadlineSeconds.IsNull() { + state.BigQueryConfig.JobRetryDeadlineSeconds = types.Int64Value( + bigqueryCfg.JobRetryDeadlineSeconds.MustGet(), + ) + } else { + state.BigQueryConfig.JobRetryDeadlineSeconds = types.Int64Null() + } + + if !bigqueryCfg.JobCreationTimeoutSeconds.IsNull() { + state.BigQueryConfig.JobCreationTimeoutSeconds = types.Int64Value( + bigqueryCfg.JobCreationTimeoutSeconds.MustGet(), + ) + } else { + state.BigQueryConfig.JobCreationTimeoutSeconds = types.Int64Null() + } + + if !bigqueryCfg.GcsBucket.IsNull() { + state.BigQueryConfig.GcsBucket = types.StringValue(bigqueryCfg.GcsBucket.MustGet()) + } else { + state.BigQueryConfig.GcsBucket = types.StringNull() + } + + if !bigqueryCfg.DataprocRegion.IsNull() { + state.BigQueryConfig.DataprocRegion = types.StringValue( + bigqueryCfg.DataprocRegion.MustGet(), + ) + } else { + state.BigQueryConfig.DataprocRegion = types.StringNull() + } + + if !bigqueryCfg.DataprocClusterName.IsNull() { + state.BigQueryConfig.DataprocClusterName = types.StringValue( + bigqueryCfg.DataprocClusterName.MustGet(), + ) + } else { + state.BigQueryConfig.DataprocClusterName = types.StringNull() + } + + // We don't set the sensitive fields when we read because those are secret and never returned by the API + // sensitive fields: ApplicationID, ApplicationSecret, PrivateKey + default: panic("Unknown connection type") } @@ -117,12 +319,15 @@ func (r *globalConnectionResource) Create( } commonCfg := dbt_cloud.GlobalConnectionCommon{ - Name: plan.Name.ValueStringPointer(), - IsSshTunnelEnabled: plan.IsSshTunnelEnabled.ValueBoolPointer(), - PrivateLinkEndpointId: helper.TypesInt64ToInt64Pointer(plan.PrivateLinkEndpointId), - OauthConfigurationId: helper.TypesInt64ToInt64Pointer(plan.OauthConfigurationId), + Name: plan.Name.ValueStringPointer(), + } + + // nullable common fields + if !plan.PrivateLinkEndpointId.IsNull() { + commonCfg.PrivateLinkEndpointId.Set(plan.PrivateLinkEndpointId.ValueString()) } + // data warehouse specific switch { case plan.SnowflakeConfig != nil: @@ -133,12 +338,16 @@ func (r *globalConnectionResource) Create( Database: plan.SnowflakeConfig.Database.ValueStringPointer(), Warehouse: plan.SnowflakeConfig.Warehouse.ValueStringPointer(), ClientSessionKeepAlive: plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBoolPointer(), - Role: plan.SnowflakeConfig.Role.ValueStringPointer(), AllowSso: plan.SnowflakeConfig.AllowSso.ValueBoolPointer(), OauthClientID: plan.SnowflakeConfig.OauthClientID.ValueStringPointer(), OauthClientSecret: plan.SnowflakeConfig.OauthClientSecret.ValueStringPointer(), } + // nullable fields + if !plan.SnowflakeConfig.Role.IsNull() { + snowflakeCfg.Role.Set(plan.SnowflakeConfig.Role.ValueString()) + } + commonResp, _, err := c.Create(commonCfg, snowflakeCfg) if err != nil { @@ -148,9 +357,89 @@ func (r *globalConnectionResource) Create( // we set the computed values that don't have any default plan.ID = types.Int64PointerValue(commonResp.ID) + plan.AdapterVersion = types.StringValue(snowflakeCfg.AdapterVersion()) + plan.OauthConfigurationId = types.Int64PointerValue(commonResp.OauthConfigurationId) plan.IsSshTunnelEnabled = types.BoolPointerValue(commonResp.IsSshTunnelEnabled) - // TODO(rest) + case plan.BigQueryConfig != nil: + + c := dbt_cloud.NewGlobalConnectionClient[dbt_cloud.BigQueryConfig](r.client) + + bigqueryCfg := dbt_cloud.BigQueryConfig{ + ProjectID: plan.BigQueryConfig.GCPProjectID.ValueStringPointer(), + TimeoutSeconds: plan.BigQueryConfig.TimeoutSeconds.ValueInt64Pointer(), + PrivateKeyID: plan.BigQueryConfig.PrivateKeyID.ValueStringPointer(), + PrivateKey: plan.BigQueryConfig.PrivateKey.ValueStringPointer(), + ClientEmail: plan.BigQueryConfig.ClientEmail.ValueStringPointer(), + ClientID: plan.BigQueryConfig.ClientID.ValueStringPointer(), + AuthURI: plan.BigQueryConfig.AuthURI.ValueStringPointer(), + TokenURI: plan.BigQueryConfig.TokenURI.ValueStringPointer(), + AuthProviderX509CertURL: plan.BigQueryConfig.AuthProviderX509CertURL.ValueStringPointer(), + ClientX509CertURL: plan.BigQueryConfig.ClientX509CertURL.ValueStringPointer(), + Retries: plan.BigQueryConfig.Retries.ValueInt64Pointer(), + Scopes: helper.TypesStringSliceToStringSlice( + plan.BigQueryConfig.Scopes, + ), + } + + // nullable fields + if !plan.BigQueryConfig.Priority.IsNull() { + bigqueryCfg.Priority.Set(plan.BigQueryConfig.Priority.ValueString()) + } + if !plan.BigQueryConfig.Location.IsNull() { + bigqueryCfg.Location.Set(plan.BigQueryConfig.Location.ValueString()) + } + if !plan.BigQueryConfig.MaximumBytesBilled.IsNull() { + bigqueryCfg.MaximumBytesBilled.Set(plan.BigQueryConfig.MaximumBytesBilled.ValueInt64()) + } + if !plan.BigQueryConfig.ExecutionProject.IsNull() { + bigqueryCfg.ExecutionProject.Set(plan.BigQueryConfig.ExecutionProject.ValueString()) + } + if !plan.BigQueryConfig.ImpersonateServiceAccount.IsNull() { + bigqueryCfg.ImpersonateServiceAccount.Set( + plan.BigQueryConfig.ImpersonateServiceAccount.ValueString(), + ) + } + if !plan.BigQueryConfig.JobRetryDeadlineSeconds.IsNull() { + bigqueryCfg.JobRetryDeadlineSeconds.Set( + plan.BigQueryConfig.JobRetryDeadlineSeconds.ValueInt64(), + ) + } + if !plan.BigQueryConfig.JobCreationTimeoutSeconds.IsNull() { + bigqueryCfg.JobCreationTimeoutSeconds.Set( + plan.BigQueryConfig.JobCreationTimeoutSeconds.ValueInt64(), + ) + } + if !plan.BigQueryConfig.ApplicationID.IsNull() { + bigqueryCfg.ApplicationID.Set(plan.BigQueryConfig.ApplicationID.ValueString()) + } + if !plan.BigQueryConfig.ApplicationSecret.IsNull() { + bigqueryCfg.ApplicationSecret.Set(plan.BigQueryConfig.ApplicationSecret.ValueString()) + } + if !plan.BigQueryConfig.GcsBucket.IsNull() { + bigqueryCfg.GcsBucket.Set(plan.BigQueryConfig.GcsBucket.ValueString()) + } + if !plan.BigQueryConfig.DataprocRegion.IsNull() { + bigqueryCfg.DataprocRegion.Set(plan.BigQueryConfig.DataprocRegion.ValueString()) + } + if !plan.BigQueryConfig.DataprocClusterName.IsNull() { + bigqueryCfg.DataprocClusterName.Set( + plan.BigQueryConfig.DataprocClusterName.ValueString(), + ) + } + + commonResp, _, err := c.Create(commonCfg, bigqueryCfg) + + if err != nil { + resp.Diagnostics.AddError("Error creating the connection", err.Error()) + return + } + + // we set the computed values that don't have any default + plan.ID = types.Int64PointerValue(commonResp.ID) + plan.AdapterVersion = types.StringValue(bigqueryCfg.AdapterVersion()) + plan.OauthConfigurationId = types.Int64PointerValue(commonResp.OauthConfigurationId) + plan.IsSshTunnelEnabled = types.BoolPointerValue(commonResp.IsSshTunnelEnabled) default: panic("Unknown connection type") @@ -200,8 +489,13 @@ func (r *globalConnectionResource) Update( if plan.Name != state.Name { globalConfigChanges.Name = plan.Name.ValueStringPointer() } + // nullable common fields if plan.PrivateLinkEndpointId != state.PrivateLinkEndpointId { - globalConfigChanges.PrivateLinkEndpointId = plan.PrivateLinkEndpointId.ValueInt64Pointer() + if plan.PrivateLinkEndpointId.IsNull() { + globalConfigChanges.PrivateLinkEndpointId.SetNull() + } else { + globalConfigChanges.PrivateLinkEndpointId.Set(plan.PrivateLinkEndpointId.ValueString()) + } } switch { @@ -224,11 +518,6 @@ func (r *globalConnectionResource) Update( if plan.SnowflakeConfig.ClientSessionKeepAlive != state.SnowflakeConfig.ClientSessionKeepAlive { warehouseConfigChanges.ClientSessionKeepAlive = plan.SnowflakeConfig.ClientSessionKeepAlive.ValueBoolPointer() } - // here we need to take care of the null case - // when Role is Null, we still want to send it as null to the PATCH payload, to remove it, otherwise the omitempty doesn't add it to the payload - if plan.SnowflakeConfig.Role != state.SnowflakeConfig.Role { - warehouseConfigChanges.Role = plan.SnowflakeConfig.Role.ValueStringPointer() - } if plan.SnowflakeConfig.AllowSso != state.SnowflakeConfig.AllowSso { warehouseConfigChanges.AllowSso = plan.SnowflakeConfig.AllowSso.ValueBoolPointer() } @@ -239,7 +528,21 @@ func (r *globalConnectionResource) Update( warehouseConfigChanges.OauthClientSecret = plan.SnowflakeConfig.OauthClientSecret.ValueStringPointer() } - updateCommon, _, err := c.Update(state.ID.ValueInt64(), globalConfigChanges, warehouseConfigChanges) + // nullable fields + // when the values are Null, we still want to send it as null to the PATCH payload, to remove it, otherwise the omitempty doesn't add it to the payload and it doesn't get updated + if plan.SnowflakeConfig.Role != state.SnowflakeConfig.Role { + if plan.SnowflakeConfig.Role.IsNull() { + warehouseConfigChanges.Role.SetNull() + } else { + warehouseConfigChanges.Role.Set(plan.SnowflakeConfig.Role.ValueString()) + } + } + + updateCommon, _, err := c.Update( + state.ID.ValueInt64(), + globalConfigChanges, + warehouseConfigChanges, + ) if err != nil { resp.Diagnostics.AddError("Error updating global connection", err.Error()) return @@ -247,23 +550,181 @@ func (r *globalConnectionResource) Update( // we set the computed values, no need to do it for ID as we use a PlanModifier with UseStateForUnknown() plan.IsSshTunnelEnabled = types.BoolPointerValue(updateCommon.IsSshTunnelEnabled) + plan.OauthConfigurationId = types.Int64PointerValue(updateCommon.OauthConfigurationId) + plan.AdapterVersion = types.StringValue(warehouseConfigChanges.AdapterVersion()) + + case plan.BigQueryConfig != nil: + + c := dbt_cloud.NewGlobalConnectionClient[dbt_cloud.BigQueryConfig](r.client) + + warehouseConfigChanges := dbt_cloud.BigQueryConfig{} + + // BigQuery specific ones + if plan.BigQueryConfig.GCPProjectID != state.BigQueryConfig.GCPProjectID { + warehouseConfigChanges.ProjectID = plan.BigQueryConfig.GCPProjectID.ValueStringPointer() + } + if plan.BigQueryConfig.TimeoutSeconds != state.BigQueryConfig.TimeoutSeconds { + warehouseConfigChanges.TimeoutSeconds = plan.BigQueryConfig.TimeoutSeconds.ValueInt64Pointer() + } + if plan.BigQueryConfig.PrivateKeyID != state.BigQueryConfig.PrivateKeyID { + warehouseConfigChanges.PrivateKeyID = plan.BigQueryConfig.PrivateKeyID.ValueStringPointer() + } + if plan.BigQueryConfig.PrivateKey != state.BigQueryConfig.PrivateKey { + warehouseConfigChanges.PrivateKey = plan.BigQueryConfig.PrivateKey.ValueStringPointer() + } + if plan.BigQueryConfig.ClientEmail != state.BigQueryConfig.ClientEmail { + warehouseConfigChanges.ClientEmail = plan.BigQueryConfig.ClientEmail.ValueStringPointer() + } + if plan.BigQueryConfig.ClientID != state.BigQueryConfig.ClientID { + warehouseConfigChanges.ClientID = plan.BigQueryConfig.ClientID.ValueStringPointer() + } + if plan.BigQueryConfig.AuthURI != state.BigQueryConfig.AuthURI { + warehouseConfigChanges.AuthURI = plan.BigQueryConfig.AuthURI.ValueStringPointer() + } + if plan.BigQueryConfig.TokenURI != state.BigQueryConfig.TokenURI { + warehouseConfigChanges.TokenURI = plan.BigQueryConfig.TokenURI.ValueStringPointer() + } + if plan.BigQueryConfig.AuthProviderX509CertURL != state.BigQueryConfig.AuthProviderX509CertURL { + warehouseConfigChanges.AuthProviderX509CertURL = plan.BigQueryConfig.AuthProviderX509CertURL.ValueStringPointer() + } + if plan.BigQueryConfig.ClientX509CertURL != state.BigQueryConfig.ClientX509CertURL { + warehouseConfigChanges.ClientX509CertURL = plan.BigQueryConfig.ClientX509CertURL.ValueStringPointer() + } + if plan.BigQueryConfig.Retries != state.BigQueryConfig.Retries { + warehouseConfigChanges.Retries = plan.BigQueryConfig.Retries.ValueInt64Pointer() + } + left, right := lo.Difference(plan.BigQueryConfig.Scopes, state.BigQueryConfig.Scopes) + if len(left) > 0 || len(right) > 0 { + warehouseConfigChanges.Scopes = helper.TypesStringSliceToStringSlice( + plan.BigQueryConfig.Scopes, + ) + } + + // nullable fields + // when the values are Null, we still want to send it as null to the PATCH payload, to remove it, otherwise the omitempty doesn't add it to the payload and it doesn't get updated + if plan.BigQueryConfig.Priority != state.BigQueryConfig.Priority { + if plan.BigQueryConfig.Priority.IsNull() { + warehouseConfigChanges.Priority.SetNull() + } else { + warehouseConfigChanges.Priority.Set(plan.BigQueryConfig.Priority.ValueString()) + } + } + if plan.BigQueryConfig.Location != state.BigQueryConfig.Location { + if plan.BigQueryConfig.Location.IsNull() { + warehouseConfigChanges.Location.SetNull() + } else { + warehouseConfigChanges.Location.Set(plan.BigQueryConfig.Location.ValueString()) + } + } + if plan.BigQueryConfig.MaximumBytesBilled != state.BigQueryConfig.MaximumBytesBilled { + if plan.BigQueryConfig.MaximumBytesBilled.IsNull() { + warehouseConfigChanges.MaximumBytesBilled.SetNull() + } else { + warehouseConfigChanges.MaximumBytesBilled.Set(plan.BigQueryConfig.MaximumBytesBilled.ValueInt64()) + } + } + if plan.BigQueryConfig.ExecutionProject != state.BigQueryConfig.ExecutionProject { + if plan.BigQueryConfig.ExecutionProject.IsNull() { + warehouseConfigChanges.ExecutionProject.SetNull() + } else { + warehouseConfigChanges.ExecutionProject.Set(plan.BigQueryConfig.ExecutionProject.ValueString()) + } + } + if plan.BigQueryConfig.ImpersonateServiceAccount != state.BigQueryConfig.ImpersonateServiceAccount { + if plan.BigQueryConfig.ImpersonateServiceAccount.IsNull() { + warehouseConfigChanges.ImpersonateServiceAccount.SetNull() + } else { + warehouseConfigChanges.ImpersonateServiceAccount.Set( + plan.BigQueryConfig.ImpersonateServiceAccount.ValueString(), + ) + } + } + if plan.BigQueryConfig.JobRetryDeadlineSeconds != state.BigQueryConfig.JobRetryDeadlineSeconds { + if plan.BigQueryConfig.JobRetryDeadlineSeconds.IsNull() { + warehouseConfigChanges.JobRetryDeadlineSeconds.SetNull() + } else { + warehouseConfigChanges.JobRetryDeadlineSeconds.Set( + plan.BigQueryConfig.JobRetryDeadlineSeconds.ValueInt64(), + ) + } + } + if plan.BigQueryConfig.JobCreationTimeoutSeconds != state.BigQueryConfig.JobCreationTimeoutSeconds { + if plan.BigQueryConfig.JobCreationTimeoutSeconds.IsNull() { + warehouseConfigChanges.JobCreationTimeoutSeconds.SetNull() + } else { + warehouseConfigChanges.JobCreationTimeoutSeconds.Set( + plan.BigQueryConfig.JobCreationTimeoutSeconds.ValueInt64(), + ) + } + } + if plan.BigQueryConfig.ApplicationID != state.BigQueryConfig.ApplicationID { + if plan.BigQueryConfig.ApplicationID.IsNull() { + warehouseConfigChanges.ApplicationID.SetNull() + } else { + warehouseConfigChanges.ApplicationID.Set(plan.BigQueryConfig.ApplicationID.ValueString()) + } + } + if plan.BigQueryConfig.ApplicationSecret != state.BigQueryConfig.ApplicationSecret { + if plan.BigQueryConfig.ApplicationSecret.IsNull() { + warehouseConfigChanges.ApplicationSecret.SetNull() + } else { + warehouseConfigChanges.ApplicationSecret.Set(plan.BigQueryConfig.ApplicationSecret.ValueString()) + } + } + if plan.BigQueryConfig.GcsBucket != state.BigQueryConfig.GcsBucket { + if plan.BigQueryConfig.GcsBucket.IsNull() { + warehouseConfigChanges.GcsBucket.SetNull() + } else { + warehouseConfigChanges.GcsBucket.Set(plan.BigQueryConfig.GcsBucket.ValueString()) + } + } + if plan.BigQueryConfig.DataprocRegion != state.BigQueryConfig.DataprocRegion { + if plan.BigQueryConfig.DataprocRegion.IsNull() { + warehouseConfigChanges.DataprocRegion.SetNull() + } else { + warehouseConfigChanges.DataprocRegion.Set(plan.BigQueryConfig.DataprocRegion.ValueString()) + } + } + if plan.BigQueryConfig.DataprocClusterName != state.BigQueryConfig.DataprocClusterName { + if plan.BigQueryConfig.DataprocClusterName.IsNull() { + warehouseConfigChanges.DataprocClusterName.SetNull() + } else { + warehouseConfigChanges.DataprocClusterName.Set( + plan.BigQueryConfig.DataprocClusterName.ValueString(), + ) + } + } - // Set the updated state - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + updateCommon, _, err := c.Update( + state.ID.ValueInt64(), + globalConfigChanges, + warehouseConfigChanges, + ) + if err != nil { + resp.Diagnostics.AddError("Error updating global connection", err.Error()) + return + } + + // we set the computed values, no need to do it for ID as we use a PlanModifier with UseStateForUnknown() + plan.IsSshTunnelEnabled = types.BoolPointerValue(updateCommon.IsSshTunnelEnabled) + plan.OauthConfigurationId = types.Int64PointerValue(updateCommon.OauthConfigurationId) + plan.AdapterVersion = types.StringValue(warehouseConfigChanges.AdapterVersion()) } + // Set the updated state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } -func (r *globalConnectionResource) ImportState( - ctx context.Context, - req resource.ImportStateRequest, - resp *resource.ImportStateResponse, -) { - // TODO:for the import we need to pass more than just the ID... - // Or we just pass the ID but we need to get the type of connection first - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) -} +// func (r *globalConnectionResource) ImportState( +// ctx context.Context, +// req resource.ImportStateRequest, +// resp *resource.ImportStateResponse, +// ) { +// // TODO:for the import we need to pass more than just the ID... +// // Or we just pass the ID but we need to get the type of connection first +// resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +// } func (r *globalConnectionResource) Configure( _ context.Context, diff --git a/pkg/framework/objects/global_connection/resource_acceptance_test.go b/pkg/framework/objects/global_connection/resource_acceptance_test.go index 91233ec5..25880382 100644 --- a/pkg/framework/objects/global_connection/resource_acceptance_test.go +++ b/pkg/framework/objects/global_connection/resource_acceptance_test.go @@ -1,15 +1,298 @@ package global_connection_test import ( + "fmt" + "strings" "testing" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/acctest_helper" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestAccDbtCloudGlobalConnectionResource(t *testing.T) { +func TestAccDbtCloudGlobalConnectionSnowflakeResource(t *testing.T) { + + // TODO: test the import as well when it is ready + + connectionName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + connectionName2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + oAuthClientID := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + oAuthClientSecret := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // create with just mandatory fields + { + Config: testAccDbtCloudSGlobalConnectionSnowflakeResourceBasicConfig( + connectionName, + oAuthClientID, + oAuthClientSecret, + ), + // we check the computed values, for the other ones the test suite already checks that the plan and state are the same + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "dbtcloud_global_connection.test", + "id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "adapter_version", + "snowflake_v0", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "is_ssh_tunnel_enabled", + "false", + ), + ), + }, + // modify, adding optional fields + { + Config: testAccDbtCloudSGlobalConnectionSnowflakeResourceFullConfig( + connectionName, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "dbtcloud_global_connection.test", + "id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "adapter_version", + "snowflake_v0", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "is_ssh_tunnel_enabled", + "false", + ), + ), + }, + // modify, removing optional fields to check PATCH when we remove fields + { + Config: testAccDbtCloudSGlobalConnectionSnowflakeResourceBasicConfig( + connectionName2, + oAuthClientID, + oAuthClientSecret, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "dbtcloud_global_connection.test", + "id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "adapter_version", + "snowflake_v0", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "is_ssh_tunnel_enabled", + "false", + ), + ), + }, + // TODO IMPORT when supported + }, + }) + +} + +func testAccDbtCloudSGlobalConnectionSnowflakeResourceBasicConfig( + connectionName, oAuthClientID, oAuthClientSecret string, +) string { + return fmt.Sprintf(` + +resource dbtcloud_global_connection test { + name = "%s" + + snowflake = { + account = "account" + warehouse = "warehouse" + database = "database" + allow_sso = true + oauth_client_id = "%s" + oauth_client_secret = "%s" + client_session_keep_alive = false + } +} + +`, connectionName, oAuthClientID, oAuthClientSecret) +} + +func testAccDbtCloudSGlobalConnectionSnowflakeResourceFullConfig( + connectionName string, +) string { + return fmt.Sprintf(` +resource dbtcloud_global_connection test { + name = "%s" + + snowflake = { + account = "account" + warehouse = "warehouse" + database = "database" + allow_sso = false + client_session_keep_alive = false + + // optional fields + role = "role" + } +} +`, connectionName) +} + +func TestAccDbtCloudGlobalConnectionBigQueryResource(t *testing.T) { - // TODO: - // - test that create/update works for all different connection types - // - have tests where we add/remove optional fields - // - have tests on the computed values - // - test the import as well + // TODO: test the import as well when it is ready + connectionName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + connectionName2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // create with just mandatory fields + { + Config: testAccDbtCloudSGlobalConnectionBigQueryResourceBasicConfig( + connectionName, + ), + // we check the computed values, for the other ones the test suite already checks that the plan and state are the same + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "dbtcloud_global_connection.test", + "id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "adapter_version", + "bigquery_v0", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "is_ssh_tunnel_enabled", + "false", + ), + ), + }, + // modify, adding optional fields + { + Config: testAccDbtCloudSGlobalConnectionBigQueryResourceFullConfig( + connectionName, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "dbtcloud_global_connection.test", + "id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "adapter_version", + "bigquery_v0", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "is_ssh_tunnel_enabled", + "false", + ), + ), + }, + // modify, removing optional fields to check PATCH when we remove fields + { + Config: testAccDbtCloudSGlobalConnectionBigQueryResourceBasicConfig( + connectionName2, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "dbtcloud_global_connection.test", + "id", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "adapter_version", + "bigquery_v0", + ), + resource.TestCheckResourceAttr( + "dbtcloud_global_connection.test", + "is_ssh_tunnel_enabled", + "false", + ), + ), + }, + // TODO IMPORT when supported + }, + }) + +} + +func testAccDbtCloudSGlobalConnectionBigQueryResourceBasicConfig( + connectionName string, +) string { + return fmt.Sprintf(` + +resource dbtcloud_global_connection test { + name = "%s" + + bigquery = { + + gcp_project_id = "my-gcp-project-id" + timeout_seconds = 1000 + private_key_id = "my-private-key-id" + private_key = "ABCDEFGHIJKL" + client_email = "my_client_email" + client_id = "my_client_id" + auth_uri = "my_auth_uri" + token_uri = "my_token_uri" + auth_provider_x509_cert_url = "my_auth_provider_x509_cert_url" + client_x509_cert_url = "my_client_x509_cert_url" + application_id = "oauth_application_id" + application_secret = "oauth_secret_id" + + } +} + +`, connectionName) +} + +func testAccDbtCloudSGlobalConnectionBigQueryResourceFullConfig( + connectionName string, +) string { + return fmt.Sprintf(` +resource dbtcloud_global_connection test { + name = "%s" + + bigquery = { + + gcp_project_id = "my-gcp-project-id" + timeout_seconds = 1000 + private_key_id = "my-private-key-id" + private_key = "ABCDEFGHIJKL" + client_email = "my_client_email" + client_id = "my_client_id" + auth_uri = "my_auth_uri" + token_uri = "my_token_uri" + auth_provider_x509_cert_url = "my_auth_provider_x509_cert_url" + client_x509_cert_url = "my_client_x509_cert_url" + application_id = "oauth_application_id" + application_secret = "oauth_secret_id" + timeout_seconds = 1000 + + dataproc_cluster_name = "dataproc" + dataproc_region = "region" + execution_project = "project" + gcs_bucket = "bucket" + impersonate_service_account = "service" + job_creation_timeout_seconds = 1000 + job_retry_deadline_seconds = 1000 + location = "us" + maximum_bytes_billed = 1000 + priority = "batch" + retries = 3 + scopes = ["dummyscope"] + + } +} +`, connectionName) } diff --git a/pkg/framework/objects/global_connection/schema.go b/pkg/framework/objects/global_connection/schema.go index 2b1aed0c..6baef7e0 100644 --- a/pkg/framework/objects/global_connection/schema.go +++ b/pkg/framework/objects/global_connection/schema.go @@ -3,6 +3,8 @@ package global_connection import ( "context" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -11,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -21,103 +24,145 @@ func (r *globalConnectionResource) Schema( ) { resp.Schema = schema.Schema{ + Description: helper.DocString( + `This resource can be used to create global connections as introduced in dbt Cloud in August 2024. + + Those connections are not linked to a project and can be linked to environments from different projects by using the ~~~connection_id~~~ field in the ~~~dbtcloud_environment~~~ resource. + + For now, only BigQuery and Snowflake connections are supported and the other Data Warehouses can continue using the existing resources ~~~dbtcloud_connection~~~ and ~~~dbtcloud_fabric_connection~~~ , + but all Data Warehouses will soon be supported under this resource and the other ones will be deprecated in the future.`, + ), Attributes: map[string]schema.Attribute{ "id": schema.Int64Attribute{ - Computed: true, + Computed: true, + Description: "Connection Identifier", PlanModifiers: []planmodifier.Int64{ int64planmodifier.UseStateForUnknown(), }, }, "adapter_version": schema.StringAttribute{ - Computed: true, + Computed: true, + Description: "Version of the adapter", }, "name": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Connection name", }, "is_ssh_tunnel_enabled": schema.BoolAttribute{ - Computed: true, + Computed: true, + Description: "Whether the connection can use an SSH tunnel", }, - "private_link_endpoint_id": schema.Int64Attribute{ - Optional: true, + "private_link_endpoint_id": schema.StringAttribute{ + Optional: true, + Description: "Private Link Endpoint ID. This ID can be found using the `privatelink_endpoint` data source", }, "oauth_configuration_id": schema.Int64Attribute{ - Optional: true, + Computed: true, }, "bigquery": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "gcp_project_id": schema.StringAttribute{ - Required: true, + Required: true, + Description: "The GCP project ID to use for the connection", }, "timeout_seconds": schema.Int64Attribute{ - Required: true, + Optional: true, + Computed: true, + Default: int64default.StaticInt64(300), + Description: "Timeout in seconds for queries", }, "private_key_id": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Private Key ID for the Service Account", }, "private_key": schema.StringAttribute{ - Required: true, + Required: true, + Sensitive: true, + Description: "Private Key for the Service Account", }, "client_email": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Service Account email", }, "client_id": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Client ID of the Service Account", }, "auth_uri": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Auth URI for the Service Account", }, "token_uri": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Token URI for the Service Account", }, "auth_provider_x509_cert_url": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Auth Provider X509 Cert URL for the Service Account", }, "client_x509_cert_url": schema.StringAttribute{ - Required: true, + Required: true, + Description: "Client X509 Cert URL for the Service Account", }, "priority": schema.StringAttribute{ Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf([]string{"batch", "interactive"}...), + }, + Description: "The priority with which to execute BigQuery queries (batch or interactive)", }, "retries": schema.Int64Attribute{ - Optional: true, - Computed: true, - Default: int64default.StaticInt64(1), + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), + Description: "Number of retries for queries", }, "location": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "Location to create new Datasets in", }, "maximum_bytes_billed": schema.Int64Attribute{ - Optional: true, + Optional: true, + Description: "Max number of bytes that can be billed for a given BigQuery query", }, "execution_project": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "Project to bill for query execution", }, "impersonate_service_account": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "Service Account to impersonate when running queries", }, "job_retry_deadline_seconds": schema.Int64Attribute{ - Optional: true, + Optional: true, + Description: "Total number of seconds to wait while retrying the same query", }, "job_creation_timeout_seconds": schema.Int64Attribute{ - Optional: true, + Optional: true, + Description: "Maximum timeout for the job creation step", }, "application_id": schema.StringAttribute{ - Required: true, + Optional: true, Description: "OAuth Client ID", + Sensitive: true, }, "application_secret": schema.StringAttribute{ - Required: true, + Optional: true, Description: "OAuth Client Secret", + Sensitive: true, }, "gcs_bucket": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "URI for a Google Cloud Storage bucket to host Python code executed via Datapro", }, "dataproc_region": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "Google Cloud region for PySpark workloads on Dataproc", }, "dataproc_cluster_name": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "Dataproc cluster name for PySpark workloads", }, "scopes": schema.SetAttribute{ Optional: true, @@ -135,43 +180,53 @@ func (r *globalConnectionResource) Schema( }, ), ), + Description: "OAuth scopes for the BigQuery connection", }, }, }, // this feels bad, but there is no error/warning when people add extra fields https://github.com/hashicorp/terraform/issues/33570 "snowflake": schema.SingleNestedAttribute{ - Optional: true, + Optional: true, + Description: "Snowflake connection configuration", Attributes: map[string]schema.Attribute{ "account": schema.StringAttribute{ - Required: true, + Required: true, + Description: "The Snowflake account name", }, "database": schema.StringAttribute{ - Required: true, + Required: true, + Description: "The default database for the connection", }, "warehouse": schema.StringAttribute{ - Required: true, + Required: true, + Description: "The default Snowflake Warehouse to use for the connection", }, "allow_sso": schema.BoolAttribute{ - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + Description: "Whether to allow Snowflake OAuth for the connection. If true, the `oauth_client_id` and `oauth_client_secret` fields must be set", }, // TODO: required if allow_sso is true "oauth_client_id": schema.StringAttribute{ - Optional: true, - Sensitive: true, + Optional: true, + Sensitive: true, + Description: "OAuth Client ID. Required to allow OAuth between dbt Cloud and Snowflake", }, "oauth_client_secret": schema.StringAttribute{ - Optional: true, - Sensitive: true, + Optional: true, + Sensitive: true, + Description: "OAuth Client Secret. Required to allow OAuth between dbt Cloud and Snowflake", }, "role": schema.StringAttribute{ - Optional: true, + Optional: true, + Description: "The Snowflake role to use when running queries on the connection", }, "client_session_keep_alive": schema.BoolAttribute{ - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + Description: "If true, the snowflake client will keep connections for longer than the default 4 hours. This is helpful when particularly long-running queries are executing (> 4 hours)", }, }, }, diff --git a/pkg/helper/helper.go b/pkg/helper/helper.go index 93fbf7d4..28cd525f 100644 --- a/pkg/helper/helper.go +++ b/pkg/helper/helper.go @@ -78,6 +78,14 @@ func TypesInt64ToInt64Pointer(value types.Int64) *int64 { return &fieldVal } +func TypesStringSliceToStringSlice(list []types.String) []string { + result := make([]string, len(list)) + for i, v := range list { + result[i] = v.ValueString() + } + return result +} + // useful for docs func DocString(inp string) string { newString := strings.ReplaceAll(inp, "~~~", "`") From 099da93f67f849e65ee5e7278a294c6f67b36899 Mon Sep 17 00:00:00 2001 From: Benoit Perigaud <8754100+b-per@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:56:42 +0200 Subject: [PATCH 19/19] Update docs and changelog --- CHANGELOG.md | 14 ++- docs/data-sources/privatelink_endpoint.md | 2 +- docs/data-sources/project.md | 2 +- docs/resources/extended_attributes.md | 2 +- docs/resources/global_connection.md | 129 ++++++++++++++-------- terraform_resources.d2 | 26 ++++- terraform_resources.png | Bin 864035 -> 922082 bytes 7 files changed, 125 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d106e29..35cef5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,24 @@ All notable changes to this project will be documented in this file. -## [Unreleased](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.10...HEAD) +## [Unreleased](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.11...HEAD) + +# [0.3.11](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.9...v0.3.11) ### Changes +- [#267](https://github.com/dbt-labs/terraform-provider-dbtcloud/issues/267) Support for global connections + - `dbtcloud_environment` now accepts a `connection_id` to link the environment to the connection. This is the new recommended way to link connections to environments instead of linking the connection to the project with `dbtcloud_project_connection` + - The `dbtcloud_project_connection` still works today and when used doesn't require setting up a `connection_id` in the `dbtcloud_environment` resource (i.e. , any current config/module should continue working), but the resource is flagged as deprecated and will be removed in a future version of the provider + - For now, people can continue using the project-scoped connection resources `dbtcloud_connection`, `dbtcloud_bigquery_connection` and `dbtcloud_fabric_connection` for creating and updating global connections. The parameter `project_id` in those connections still need to be a valid project id but doesn't mean that this connection is restricted to this project ID. The project-scoped connections created from Terraform are automatically converted to global connections + - A new resource `dbtcloud_global_connection` has been created and currently supports Snowflake and BigQuery connections. In the next weeks, support for all the Data Warehouses will be added to this resource + - When a data warehouse is supported in `dbtcloud_global_connection`, we recommend using this new resource instead of the legacy project-scoped connection resources. Those resources will be deprecated in a future version of the provider. - [#278](https://github.com/dbt-labs/terraform-provider-dbtcloud/pull/278) Deprecate `state` attribute in the resources and datasources that use it. It will be removed in the next major version of the provider. This attribute is used for soft-delete and isn't intended to be configured in the scope of the provider. +### Fix + +- [#281](https://github.com/dbt-labs/terraform-provider-dbtcloud/issues/281) Fix the datasource `dbcloud_environments` where the environment IDs were not being saved + # [0.3.10](https://github.com/dbt-labs/terraform-provider-dbtcloud/compare/v0.3.9...v0.3.10) ### Changes diff --git a/docs/data-sources/privatelink_endpoint.md b/docs/data-sources/privatelink_endpoint.md index 41108ac3..a43644dd 100644 --- a/docs/data-sources/privatelink_endpoint.md +++ b/docs/data-sources/privatelink_endpoint.md @@ -43,5 +43,5 @@ data "dbtcloud_privatelink_endpoint" "test_with_name_and_url" { - `cidr_range` (String) The CIDR range of the PrivateLink Endpoint - `id` (String) The internal ID of the PrivateLink Endpoint -- `state` (Number) PrivatelinkEndpoint state should be 1 = active, as 2 = deleted +- `state` (Number, Deprecated) PrivatelinkEndpoint state should be 1 = active, as 2 = deleted - `type` (String) Type of the PrivateLink Endpoint diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index 516b326d..dec875d5 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -43,4 +43,4 @@ data "dbtcloud_project" "test_project" { - `freshness_job_id` (Number) ID of Job for source freshness - `id` (String) The ID of this resource. - `repository_id` (Number) ID of the repository associated with the project -- `state` (Number) Project state should be 1 = active, as 2 = deleted +- `state` (Number, Deprecated) Project state should be 1 = active, as 2 = deleted diff --git a/docs/resources/extended_attributes.md b/docs/resources/extended_attributes.md index 9a9756cd..e4dd3341 100644 --- a/docs/resources/extended_attributes.md +++ b/docs/resources/extended_attributes.md @@ -51,7 +51,7 @@ resource "dbtcloud_environment" "issue_depl" { ### Optional -- `state` (Number) Extended Attributes state (1 is active, 2 is inactive) +- `state` (Number, Deprecated) Extended Attributes state (1 is active, 2 is inactive) ### Read-Only diff --git a/docs/resources/global_connection.md b/docs/resources/global_connection.md index b51bdbb2..38571dd8 100644 --- a/docs/resources/global_connection.md +++ b/docs/resources/global_connection.md @@ -2,68 +2,111 @@ page_title: "dbtcloud_global_connection Resource - dbtcloud" subcategory: "" description: |- - + This resource can be used to create global connections as introduced in dbt Cloud in August 2024. + Those connections are not linked to a project and can be linked to environments from different projects by using the connection_id field in the dbtcloud_environment resource. + For now, only BigQuery and Snowflake connections are supported and the other Data Warehouses can continue using the existing resources dbtcloud_connection and dbtcloud_fabric_connection , + but all Data Warehouses will soon be supported under this resource and the other ones will be deprecated in the future. --- # dbtcloud_global_connection (Resource) - - - +This resource can be used to create global connections as introduced in dbt Cloud in August 2024. + +Those connections are not linked to a project and can be linked to environments from different projects by using the `connection_id` field in the `dbtcloud_environment` resource. + +For now, only BigQuery and Snowflake connections are supported and the other Data Warehouses can continue using the existing resources `dbtcloud_connection` and `dbtcloud_fabric_connection` , +but all Data Warehouses will soon be supported under this resource and the other ones will be deprecated in the future. + +## Example Usage + +```terraform +resource "dbtcloud_global_connection" "snowflake" { + name = "My Snowflake connection" + // we can set Privatelink if needed + private_link_endpoint_id = data.dbtcloud_privatelink_endpoint.my_private_link.id + snowflake = { + account = "my-snowflake-account" + database = "MY_DATABASE" + warehouse = "MY_WAREHOUSE" + client_session_keep_alive = false + allow_sso = true + oauth_client_id = "yourclientid" + oauth_client_secret = "yourclientsecret" + } +} + +resource "dbtcloud_global_connection" "bigquery" { + name = "My BigQuery connection" + bigquery = { + gcp_project_id = "my-gcp-project-id" + timeout_seconds = 1000 + private_key_id = "my-private-key-id" + private_key = "ABCDEFGHIJKL" + client_email = "my_client_email" + client_id = "my_client_id" + auth_uri = "my_auth_uri" + token_uri = "my_token_uri" + auth_provider_x509_cert_url = "my_auth_provider_x509_cert_url" + client_x509_cert_url = "my_client_x509_cert_url" + application_id = "oauth_application_id" + application_secret = "oauth_secret_id" + } +} +``` ## Schema ### Required -- `name` (String) +- `name` (String) Connection name ### Optional - `bigquery` (Attributes) (see [below for nested schema](#nestedatt--bigquery)) -- `oauth_configuration_id` (Number) -- `private_link_endpoint_id` (Number) -- `snowflake` (Attributes) (see [below for nested schema](#nestedatt--snowflake)) +- `private_link_endpoint_id` (String) Private Link Endpoint ID. This ID can be found using the `privatelink_endpoint` data source +- `snowflake` (Attributes) Snowflake connection configuration (see [below for nested schema](#nestedatt--snowflake)) ### Read-Only -- `adapter_version` (String) -- `id` (Number) The ID of this resource. -- `is_ssh_tunnel_enabled` (Boolean) +- `adapter_version` (String) Version of the adapter +- `id` (Number) Connection Identifier +- `is_ssh_tunnel_enabled` (Boolean) Whether the connection can use an SSH tunnel +- `oauth_configuration_id` (Number) ### Nested Schema for `bigquery` Required: -- `application_id` (String) OAuth Client ID -- `application_secret` (String) OAuth Client Secret -- `auth_provider_x509_cert_url` (String) -- `auth_uri` (String) -- `client_email` (String) -- `client_id` (String) -- `client_x509_cert_url` (String) -- `gcp_project_id` (String) -- `private_key` (String) -- `private_key_id` (String) -- `timeout_seconds` (Number) -- `token_uri` (String) +- `auth_provider_x509_cert_url` (String) Auth Provider X509 Cert URL for the Service Account +- `auth_uri` (String) Auth URI for the Service Account +- `client_email` (String) Service Account email +- `client_id` (String) Client ID of the Service Account +- `client_x509_cert_url` (String) Client X509 Cert URL for the Service Account +- `gcp_project_id` (String) The GCP project ID to use for the connection +- `private_key` (String, Sensitive) Private Key for the Service Account +- `private_key_id` (String) Private Key ID for the Service Account +- `token_uri` (String) Token URI for the Service Account Optional: -- `dataproc_cluster_name` (String) -- `dataproc_region` (String) -- `execution_project` (String) -- `gcs_bucket` (String) -- `impersonate_service_account` (String) -- `job_creation_timeout_seconds` (Number) -- `job_retry_deadline_seconds` (Number) -- `location` (String) -- `maximum_bytes_billed` (Number) -- `priority` (String) -- `retries` (Number) -- `scopes` (Set of String) +- `application_id` (String, Sensitive) OAuth Client ID +- `application_secret` (String, Sensitive) OAuth Client Secret +- `dataproc_cluster_name` (String) Dataproc cluster name for PySpark workloads +- `dataproc_region` (String) Google Cloud region for PySpark workloads on Dataproc +- `execution_project` (String) Project to bill for query execution +- `gcs_bucket` (String) URI for a Google Cloud Storage bucket to host Python code executed via Datapro +- `impersonate_service_account` (String) Service Account to impersonate when running queries +- `job_creation_timeout_seconds` (Number) Maximum timeout for the job creation step +- `job_retry_deadline_seconds` (Number) Total number of seconds to wait while retrying the same query +- `location` (String) Location to create new Datasets in +- `maximum_bytes_billed` (Number) Max number of bytes that can be billed for a given BigQuery query +- `priority` (String) The priority with which to execute BigQuery queries (batch or interactive) +- `retries` (Number) Number of retries for queries +- `scopes` (Set of String) OAuth scopes for the BigQuery connection +- `timeout_seconds` (Number) Timeout in seconds for queries @@ -71,14 +114,14 @@ Optional: Required: -- `account` (String) -- `database` (String) -- `warehouse` (String) +- `account` (String) The Snowflake account name +- `database` (String) The default database for the connection +- `warehouse` (String) The default Snowflake Warehouse to use for the connection Optional: -- `allow_sso` (Boolean) -- `client_session_keep_alive` (Boolean) -- `oauth_client_id` (String, Sensitive) -- `oauth_client_secret` (String, Sensitive) -- `role` (String) +- `allow_sso` (Boolean) Whether to allow Snowflake OAuth for the connection. If true, the `oauth_client_id` and `oauth_client_secret` fields must be set +- `client_session_keep_alive` (Boolean) If true, the snowflake client will keep connections for longer than the default 4 hours. This is helpful when particularly long-running queries are executing (> 4 hours) +- `oauth_client_id` (String, Sensitive) OAuth Client ID. Required to allow OAuth between dbt Cloud and Snowflake +- `oauth_client_secret` (String, Sensitive) OAuth Client Secret. Required to allow OAuth between dbt Cloud and Snowflake +- `role` (String) The Snowflake role to use when running queries on the connection diff --git a/terraform_resources.d2 b/terraform_resources.d2 index 92f1ee12..26b5d9d1 100644 --- a/terraform_resources.d2 +++ b/terraform_resources.d2 @@ -2,7 +2,7 @@ *.*.style.font-size: 22 title: |md - # Terraform resources (v0.3.6) + # Terraform resources (v0.3.11) | {near: top-center} direction: right @@ -11,6 +11,13 @@ direction: right license_map partial_license_map +project_connection: { + style: { + fill: "#C5C6C7" + stroke: grey + } +} + privatelink_endpoint: {tooltip: Datasource only} group: {tooltip: Group permissions as well} group_partial_permissions @@ -60,14 +67,27 @@ webhook -- job: triggered by { stroke-dash: 3 } } +environment -- global_connection +environment -- conns +global_connection -- privatelink_endpoint + environment -- env_creds -project -- project_connection -project_connection -- conns conns -- privatelink_endpoint project -- project_repository project_repository -- repository environment -- environment_variable environment -- extended_attributes +project -- project_connection { + style: { + stroke: "#C5C6C7" + } +} +project_connection -- conns { + style: { + stroke: "#C5C6C7" + } +} + (job -- *)[*].style.stroke: green (* -- job)[*].style.stroke: green diff --git a/terraform_resources.png b/terraform_resources.png index c124d427390cf57d75cb4f95411b502b6ebb9878..5c957a48b92baf07098a2a2c0701c0eac9697b44 100644 GIT binary patch literal 922082 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%0Afc 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⁡fqYA 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

F)h6<6p6Sm+^?Wcki$&o>+^t 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%0DqWoFF`f@s>(`yju3F8!6O?|}gvmI- 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

  • b=D84p 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