Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add dbt-cloud connection create and dbt-cloud connection delete commands #95

Merged
merged 13 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ For more information on a command, run `dbt-cloud <command> --help`. For more in
| Environment | [dbt-cloud environment get](#dbt-cloud-environment-get) | ✅ | GET `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/environments/{id}/` |
| Environment | [dbt-cloud environment list](#dbt-cloud-environment-list) | ✅ | GET `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/environments/` |
| Environment | [dbt-cloud environment update](#dbt-cloud-environment-update) | ❌ | POST `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/environments/{id}/` |
| Connection | [dbt-cloud connection create](#dbt-cloud-connection-create) | | POST `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/` |
| Connection | [dbt-cloud connection delete](#dbt-cloud-connection-delete) | | DELETE `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/{id}/` |
| Connection | [dbt-cloud connection create](#dbt-cloud-connection-create) | | POST `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/` |
| Connection | [dbt-cloud connection delete](#dbt-cloud-connection-delete) | | DELETE `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/{id}/` |
| Connection | [dbt-cloud connection get](#dbt-cloud-connection-get) | ✅ | GET `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/{id}/` |
| Connection | [dbt-cloud connection list](#dbt-cloud-connection-list) | ✅ | GET `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/` |
| Connection | [dbt-cloud connection update](#dbt-cloud-connection-update) | ❌ | POST `https://{dbt_cloud_host}/api/v3/accounts/{account_id}/projects/{project_id}/connections/{id}/` |
Expand Down Expand Up @@ -207,6 +207,34 @@ dbt-cloud environment get --account-id 123456 --project-id 123457 --environment-
[Click to view sample response](tests/data/environment_get_response.json)


## dbt-cloud connection create
This command creates a new database connection in a given project. Supported connection types:

* `snowflake`: Connection to a Snowflake database. Has inout validation for connection parameters.
* `bigquery`: Connection to a Google BigQuery database. No input validation.
* `postgres`: Connection to a PostgreSQL database. No input validation.
* `redshift`: Connection to an Amazon Redshift database. No input validation.
* `adapter`: Connection to a database using a custom dbt Cloud adapter. No input validation.


### Usage
```bash
dbt-cloud connection create --account-id 54321 --project-id 123467 --name Snowflake --type snowflake --account snowflake_account --database analytics --warehouse transforming --role transformer --allow-sso False --client-session-keep-alive False
```

[Click to view sample response](tests/data/connection_create_response.json)


## dbt-cloud connection delete
This command deletes a database connection in a given project.

### Usage
```bash
dbt-cloud connection delete --account-id 54321 --project-id 123467 --connection-id 56901
```

[Click to view sample response](tests/data/connection_delete_response.json)

## dbt-cloud connection list
This command retrievies details of dbt Cloud database connections in a given project.

Expand Down
1 change: 0 additions & 1 deletion dbt_cloud/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from .command import DbtCloudRunStatus
27 changes: 26 additions & 1 deletion dbt_cloud/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import logging
import time
import click
from dbt_cloud import DbtCloudRunStatus
from dbt_cloud.command import (
DbtCloudRunStatus,
DbtCloudJobGetCommand,
DbtCloudJobCreateCommand,
DbtCloudJobDeleteCommand,
Expand All @@ -27,6 +27,8 @@
DbtCloudAccountListCommand,
DbtCloudAccountGetCommand,
DbtCloudAuditLogGetCommand,
DbtCloudConnectionCreateCommand,
DbtCloudConnectionDeleteCommand,
DbtCloudConnectionGetCommand,
DbtCloudConnectionListCommand,
)
Expand Down Expand Up @@ -412,6 +414,29 @@ def delete(**kwargs):
response = execute_and_print(command)


@connection.command(
help=DbtCloudConnectionCreateCommand.get_description(),
context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
)
@click.pass_context
@DbtCloudConnectionCreateCommand.click_options
def create(ctx, **kwargs):
keys = ctx.args[::2] # Every even element is a key
values = ctx.args[1::2] # Every odd element is a value
kwargs["details"] = {
key.lstrip("-").replace("-", "_"): value for key, value in zip(keys, values)
}
command = DbtCloudConnectionCreateCommand.from_click_options(**kwargs)
response = execute_and_print(command)


@connection.command(help=DbtCloudConnectionDeleteCommand.get_description())
@DbtCloudConnectionDeleteCommand.click_options
def delete(**kwargs):
command = DbtCloudConnectionDeleteCommand.from_click_options(**kwargs)
response = execute_and_print(command)


@connection.command(help=DbtCloudConnectionGetCommand.get_description())
@DbtCloudConnectionGetCommand.click_options
def get(**kwargs):
Expand Down
7 changes: 6 additions & 1 deletion dbt_cloud/command/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@
from .audit_log import DbtCloudAuditLogGetCommand
from .metadata import DbtCloudMetadataQueryCommand
from .command import DbtCloudAccountCommand
from .connection import DbtCloudConnectionGetCommand, DbtCloudConnectionListCommand
from .connection import (
DbtCloudConnectionGetCommand,
DbtCloudConnectionListCommand,
DbtCloudConnectionCreateCommand,
DbtCloudConnectionDeleteCommand,
)
1 change: 1 addition & 0 deletions dbt_cloud/command/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def click_options(cls, function, key_prefix: str = ""):
is_nested_object = False

if is_nested_object:
# If the field is a ClickBaseModel, recursively call click_options
function = field.type_.click_options(
function, key_prefix=f"{key_prefix}__{key}".strip("_")
)
Expand Down
2 changes: 2 additions & 0 deletions dbt_cloud/command/connection/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
from .create import DbtCloudConnectionCreateCommand
from .delete import DbtCloudConnectionDeleteCommand
from .get import DbtCloudConnectionGetCommand
from .list import DbtCloudConnectionListCommand
53 changes: 53 additions & 0 deletions dbt_cloud/command/connection/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import requests
from typing import Optional, Union
from pydantic import Field, validator, BaseModel
from dbt_cloud.command.command import DbtCloudProjectCommand


class DbtCloudSnowflakeConnection(BaseModel):
account: str = Field(description="Snowflake account name.")
database: str = Field(description="Snowflake database name.")
warehouse: str = Field(description="Snowflake warehouse name.")
role: str = Field(description="Snowflake role name.")
allow_sso: bool = Field(False, description="Allow SSO.")
client_session_keep_alive: bool = Field(
False, description="Keep client session alive."
)


class DbtCloudConnectionCreateCommand(DbtCloudProjectCommand):
"""Creates a new database connection in a given project."""

name: str = Field(description="Name of the connection.")
type: str = Field(
description="Type of the connection (e.g., 'snowflake'). Connection parameters go to the details field.",
)
id: Optional[int] = Field(description="ID of the connection.")
created_by_id: Optional[int] = Field(
description="ID of the user who created the connection."
)
created_by_service_token_id: Optional[int] = Field(
description="ID of the service token that created the connection."
)
state: int = Field(1, description="State of the connection. 1 = Active.")

details: Union[DbtCloudSnowflakeConnection, dict] = Field(
description="Connection details specific to the connection type.",
exclude_from_click_options=True,
)

@validator("type")
def is_valid_type(cls, value):
if value not in ["snowflake", "bigquery", "redshift", "postgres", "adapter"]:
raise ValueError("Invalid connection type.")
return value

@property
def api_url(self) -> str:
return f"{super().api_url}/connections/"

def execute(self) -> requests.Response:
response = requests.post(
url=self.api_url, headers=self.request_headers, json=self.get_payload()
)
return response
19 changes: 19 additions & 0 deletions dbt_cloud/command/connection/delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import requests
from pydantic import Field
from dbt_cloud.command.command import DbtCloudProjectCommand


class DbtCloudConnectionDeleteCommand(DbtCloudProjectCommand):
"""Deletes a database connection in a given project."""

connection_id: int = Field(
description="ID of the dbt Cloud database connection to delete.",
)

@property
def api_url(self) -> str:
return f"{super().api_url}/connections/{self.connection_id}/"

def execute(self) -> requests.Response:
response = requests.delete(url=self.api_url, headers=self.request_headers)
return response
35 changes: 35 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
DbtCloudAuditLogGetCommand,
DbtCloudConnectionListCommand,
DbtCloudConnectionGetCommand,
DbtCloudConnectionCreateCommand,
DbtCloudConnectionDeleteCommand,
)


Expand Down Expand Up @@ -279,6 +281,39 @@ def load_response(response_name):
"get",
marks=pytest.mark.connection,
),
pytest.param(
"connection_create",
DbtCloudConnectionCreateCommand(
api_token=API_TOKEN,
account_id=ACCOUNT_ID,
project_id=PROJECT_ID,
name="pytest connection",
type="snowflake",
details={
"account": "foo",
"database": "bar",
"warehouse": "baz",
"role": "qux",
"allow_sso": True,
"client_session_keep_alive": True,
},
),
load_response("connection_create_response"),
"post",
marks=pytest.mark.connection,
),
pytest.param(
"connection_delete",
DbtCloudConnectionDeleteCommand(
api_token=API_TOKEN,
account_id=ACCOUNT_ID,
project_id=PROJECT_ID,
connection_id=123,
),
load_response("connection_delete_response"),
"delete",
marks=pytest.mark.connection,
),
pytest.param(
"audit_log_get",
DbtCloudAuditLogGetCommand(
Expand Down
29 changes: 29 additions & 0 deletions tests/data/connection_create_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"status": {
"code": 201,
"is_success": true,
"user_message": "Success!",
"developer_message": ""
},
"data": {
"id": 152196,
"account_id": 123456,
"project_id": 274720,
"name": "pytest connection",
"type": "snowflake",
"created_by_id": null,
"created_by_service_token_id": 123451,
"details": {
"account": "snowflake_account",
"database": "snowflake_database",
"warehouse": "snowflake_warehouse",
"allow_sso": false,
"client_session_keep_alive": false,
"role": "snowflake_role"
},
"state": 1,
"created_at": "2023-08-04 06:00:35.021162+00:00",
"updated_at": "2023-08-04 06:00:35.021184+00:00",
"private_link_endpoint_id": null
}
}
29 changes: 29 additions & 0 deletions tests/data/connection_delete_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"status": {
"code": 200,
"is_success": true,
"user_message": "Success!",
"developer_message": ""
},
"data": {
"id": 152196,
"account_id": 123456,
"project_id": 274720,
"name": "pytest connection",
"type": "snowflake",
"created_by_id": null,
"created_by_service_token_id": 123451,
"details": {
"account": "snowflake_account",
"database": "snowflake_database",
"warehouse": "snowflake_warehouse",
"allow_sso": false,
"client_session_keep_alive": false,
"role": "snowflake_role"
},
"state": 2,
"created_at": "2023-08-04 06:00:35.021162+00:00",
"updated_at": "2023-08-04 06:00:35.895775+00:00",
"private_link_endpoint_id": null
}
}
Loading