Skip to content

Commit 81c26a8

Browse files
authored
Add more configurable flags for UC metastore, external locations, and storage credentials. (#562)
Added all configurable flags for create and update operations. Added unit tests for all CLI commands relevant to the 3 securables. Added .vscode folder to automatically configure lint and pytest. Included GCP credentials in preparation for private preview.
1 parent 64c370f commit 81c26a8

File tree

9 files changed

+1001
-44
lines changed

9 files changed

+1001
-44
lines changed

.vscode/settings.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"tests"
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.pytestEnabled": true,
7+
"python.linting.pylintEnabled": false,
8+
"python.linting.prospectorEnabled": true,
9+
"python.linting.prospectorArgs": [
10+
"-t", "dodgy",
11+
"-t", "mccabe",
12+
"-t", "profile-validator",
13+
"-t", "pyflakes",
14+
"-t", "pylint"
15+
],
16+
"python.linting.enabled": true
17+
}

databricks_cli/unity_catalog/cred_cli.py

Lines changed: 153 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
# See the License for the specific language governing permissions and
2222
# limitations under the License.
2323

24+
import functools
25+
2426
import click
2527

2628
from databricks_cli.click_types import JsonClickType
@@ -33,9 +35,91 @@
3335

3436
############# Storage Credential Commands ############
3537

38+
def fill_credential(
39+
data, aws_iam_role_arn, az_sp_directory_id, az_sp_application_id,
40+
az_sp_client_secret, az_mi_access_connector_id, az_mi_id, gcp_sak_email,
41+
gcp_sak_private_key_id, gcp_sak_private_key):
42+
if aws_iam_role_arn is not None:
43+
data['aws_iam_role'] = {
44+
'role_arn': aws_iam_role_arn
45+
}
46+
47+
if ((az_sp_directory_id is not None) or (az_sp_application_id is not None) or
48+
(az_sp_client_secret is not None)):
49+
data['azure_service_principal'] = {
50+
'directory_id': az_sp_directory_id,
51+
'application_id': az_sp_application_id,
52+
'client_secret': az_sp_client_secret
53+
}
54+
55+
if (az_mi_access_connector_id is not None) or (az_mi_id is not None):
56+
data['azure_managed_identity'] = {
57+
'access_connector_id': az_mi_access_connector_id,
58+
'managed_identity_id': az_mi_id
59+
}
60+
61+
if ((gcp_sak_email is not None) or (gcp_sak_private_key_id is not None) or
62+
(gcp_sak_private_key is not None)):
63+
data['gcp_service_account_key'] = {
64+
'email': gcp_sak_email,
65+
'private_key_id': gcp_sak_private_key_id,
66+
'private_key': gcp_sak_private_key
67+
}
68+
69+
70+
def create_update_common_options(f):
71+
@click.option('--aws-iam-role-arn', default=None,
72+
help='The Amazon Resource Name (ARN) of the AWS IAM role for S3 data access.')
73+
@click.option('--az-sp-directory-id', default=None,
74+
help=(
75+
'The directory ID corresponding to the Azure Active Directory (AAD) '
76+
'tenant of the application.'))
77+
@click.option('--az-sp-application-id', default=None,
78+
help=(
79+
'The application ID of the application registration within the referenced '
80+
'AAD tenant.'))
81+
@click.option('--az-sp-client-secret', default=None,
82+
help='The client secret generated for the above app ID in AAD.')
83+
@click.option('--az-mi-access-connector-id', default=None,
84+
help=(
85+
'The Azure resource ID of the Azure Databricks Access Connector. '
86+
'Use the format, '
87+
'/subscriptions/{guid}/resourceGroups/{rg-name}/providers/Microsoft.Databricks'
88+
'/accessConnectors/{connector-name} .'))
89+
@click.option('--az-mi-id', default=None,
90+
help=(
91+
'The Azure resource ID of the managed identity. Use the format, '
92+
'/subscriptions/{guid}/resourceGroups/{rg-name}/providers'
93+
'/Microsoft.ManagedIdentity/userAssignedIdentities/{identity-name} .'
94+
'This is only available for user-assigned identities. '
95+
'For system-assigned identities, access-connector-id is used to identify '
96+
'the identity. If this flag is not provided, '
97+
'then we assume that it is using the system-assigned identity.'))
98+
@click.option('--gcp-sak-email', default=None,
99+
help=(
100+
'Credential for GCP Service Account Key. '
101+
'The email of the service account.'))
102+
@click.option('--gcp-sak-private-key-id', default=None,
103+
help=(
104+
'Credential for GCP Service Account Key. '
105+
'The ID of the service account\'s private key.'))
106+
@click.option('--gcp-sak-private-key', default=None,
107+
help=(
108+
'Credential for GCP Service Account Key. '
109+
'The service account\'s RSA private key.'))
110+
@click.option('--comment', default=None,
111+
help='Free-form text description.')
112+
@functools.wraps(f)
113+
def wrapper(*args, **kwargs):
114+
f(*args, **kwargs)
115+
return wrapper
116+
36117

37118
@click.command(context_settings=CONTEXT_SETTINGS,
38119
short_help='Create storage credential.')
120+
@click.option('--name', default=None,
121+
help='Name of new storage credential')
122+
@create_update_common_options
39123
@click.option('--skip-validation', '-s', 'skip_val', is_flag=True, default=False,
40124
help='Skip the validation of new credential info before creation')
41125
@click.option('--json-file', default=None, type=click.Path(),
@@ -50,16 +134,42 @@
50134
# Until that is fixed (should return a 400), show full error trace.
51135
#@eat_exceptions
52136
@provide_api_client
53-
def create_credential_cli(api_client, skip_val, json_file, json):
137+
def create_credential_cli(api_client, name, aws_iam_role_arn,
138+
az_sp_directory_id, az_sp_application_id, az_sp_client_secret,
139+
az_mi_access_connector_id, az_mi_id, gcp_sak_email,
140+
gcp_sak_private_key_id, gcp_sak_private_key, comment,
141+
skip_val, json_file, json):
54142
"""
55143
Create new storage credential.
56144
57145
The public specification for the JSON request is in development.
58146
"""
59-
json_cli_base(json_file, json,
60-
lambda json: UnityCatalogApi(api_client).create_storage_credential(json,
61-
skip_val),
62-
encode_utf8=True)
147+
has_credential_flag = (
148+
(aws_iam_role_arn is not None) or
149+
(az_sp_directory_id is not None) or (az_sp_application_id is not None) or
150+
(az_sp_client_secret is not None) or (az_mi_access_connector_id is not None) or
151+
(az_mi_id is not None) or (gcp_sak_email is not None) or
152+
(gcp_sak_private_key_id is not None) or (gcp_sak_private_key is not None))
153+
if ((name is not None) or has_credential_flag or (comment is not None)):
154+
if (json_file is not None) or (json is not None):
155+
raise ValueError('Cannot specify JSON if any other creation flags are specified')
156+
data = {
157+
'name': name,
158+
'comment': comment
159+
}
160+
161+
fill_credential(
162+
data, aws_iam_role_arn, az_sp_directory_id, az_sp_application_id,
163+
az_sp_client_secret, az_mi_access_connector_id, az_mi_id, gcp_sak_email,
164+
gcp_sak_private_key_id, gcp_sak_private_key)
165+
166+
cred_json = UnityCatalogApi(api_client).create_storage_credential(data, skip_val)
167+
click.echo(mc_pretty_format(cred_json))
168+
else:
169+
json_cli_base(json_file, json,
170+
lambda json: UnityCatalogApi(api_client).create_storage_credential(json,
171+
skip_val),
172+
encode_utf8=True)
63173

64174

65175
@click.command(context_settings=CONTEXT_SETTINGS,
@@ -98,6 +208,10 @@ def get_credential_cli(api_client, name):
98208
short_help='Update a storage credential.')
99209
@click.option('--name', required=True,
100210
help='Name of the storage credential to update.')
211+
@click.option('--new-name', default=None, help='New name of the storage credential.')
212+
@create_update_common_options
213+
@click.option('--owner', default=None,
214+
help='Owner of the storage credential.')
101215
@click.option('--skip-validation', '-s', 'skip_val', is_flag=True, default=False,
102216
help='Skip the validation of new credential info before update')
103217
@click.option('--json-file', default=None, type=click.Path(),
@@ -109,17 +223,45 @@ def get_credential_cli(api_client, name):
109223
# See comment for create-storage-credential
110224
#@eat_exceptions
111225
@provide_api_client
112-
def update_credential_cli(api_client, name, skip_val, json_file, json):
226+
def update_credential_cli(api_client, name, new_name, aws_iam_role_arn,
227+
az_sp_directory_id, az_sp_application_id, az_sp_client_secret,
228+
az_mi_access_connector_id, az_mi_id, gcp_sak_email,
229+
gcp_sak_private_key_id, gcp_sak_private_key, comment, owner,
230+
skip_val, json_file, json):
113231
"""
114232
Update a storage credential.
115233
116234
The public specification for the JSON request is in development.
117235
"""
118-
json_cli_base(json_file, json,
119-
lambda json: UnityCatalogApi(api_client).update_storage_credential(name,
120-
json,
121-
skip_val),
122-
encode_utf8=True)
236+
has_credential_flag = (
237+
(aws_iam_role_arn is not None) or
238+
(az_sp_directory_id is not None) or (az_sp_application_id is not None) or
239+
(az_sp_client_secret is not None) or (az_mi_access_connector_id is not None) or
240+
(az_mi_id is not None) or (gcp_sak_email is not None) or
241+
(gcp_sak_private_key_id is not None) or (gcp_sak_private_key is not None))
242+
if ((new_name is not None) or has_credential_flag or
243+
(comment is not None) or (owner is not None)):
244+
if (json_file is not None) or (json is not None):
245+
raise ValueError('Cannot specify JSON if any other update flags are specified')
246+
data = {
247+
'name': new_name,
248+
'comment': comment,
249+
'owner': owner
250+
}
251+
252+
fill_credential(
253+
data, aws_iam_role_arn, az_sp_directory_id, az_sp_application_id,
254+
az_sp_client_secret, az_mi_access_connector_id, az_mi_id, gcp_sak_email,
255+
gcp_sak_private_key_id, gcp_sak_private_key)
256+
257+
cred_json = UnityCatalogApi(api_client).update_storage_credential(name, data, skip_val)
258+
click.echo(mc_pretty_format(cred_json))
259+
else:
260+
json_cli_base(json_file, json,
261+
lambda json: UnityCatalogApi(api_client).update_storage_credential(name,
262+
json,
263+
skip_val),
264+
encode_utf8=True)
123265

124266

125267
@click.command(context_settings=CONTEXT_SETTINGS,

0 commit comments

Comments
 (0)