1
1
import json
2
- import logging
3
- import sys
4
2
import webbrowser
5
3
6
- from databricks .sdk import WorkspaceClient
4
+ from databricks .labs .blueprint .cli import App
5
+ from databricks .labs .blueprint .entrypoint import get_logger
6
+ from databricks .labs .blueprint .tui import Prompts
7
+ from databricks .sdk import AccountClient , WorkspaceClient
7
8
8
9
from databricks .labs .ucx .account import AccountWorkspaces , WorkspaceInfo
9
10
from databricks .labs .ucx .config import AccountConfig , ConnectConfig
10
11
from databricks .labs .ucx .framework .crawlers import StatementExecutionBackend
11
- from databricks .labs .ucx .framework .tui import Prompts
12
12
from databricks .labs .ucx .hive_metastore import ExternalLocations , TablesCrawler
13
13
from databricks .labs .ucx .hive_metastore .mapping import TableMapping
14
14
from databricks .labs .ucx .hive_metastore .table_migrate import TablesMigrate
15
15
from databricks .labs .ucx .install import WorkspaceInstaller
16
16
from databricks .labs .ucx .installer import InstallationManager
17
17
18
- logger = logging .getLogger ("databricks.labs.ucx" )
18
+ ucx = App (__file__ )
19
+ logger = get_logger (__file__ )
19
20
20
21
CANT_FIND_UCX_MSG = (
21
22
"Couldn't find UCX configuration in the user's home folder. "
22
23
"Make sure the current user has configured and installed UCX."
23
24
)
24
25
25
26
26
- def workflows ():
27
- ws = WorkspaceClient ()
28
- installer = WorkspaceInstaller (ws )
27
+ @ucx .command
28
+ def workflows (w : WorkspaceClient ):
29
+ """Show deployed workflows and their state"""
30
+ installer = WorkspaceInstaller (w )
29
31
logger .info ("Fetching deployed jobs..." )
30
32
print (json .dumps (installer .latest_job_status ()))
31
33
32
34
33
- def open_remote_config ():
34
- ws = WorkspaceClient ()
35
- installer = WorkspaceInstaller (ws )
35
+ @ucx .command
36
+ def open_remote_config (w : WorkspaceClient ):
37
+ """Opens remote configuration in the browser"""
38
+ installer = WorkspaceInstaller (w )
36
39
37
40
ws_file_url = installer .notebook_link (installer .config_file )
38
41
webbrowser .open (ws_file_url )
39
42
40
43
41
- def list_installations ():
42
- ws = WorkspaceClient ()
43
- installation_manager = InstallationManager (ws )
44
+ @ucx .command
45
+ def installations (w : WorkspaceClient ):
46
+ """Show installations by different users on the same workspace"""
47
+ installation_manager = InstallationManager (w )
44
48
logger .info ("Fetching installations..." )
45
49
all_users = [_ .as_summary () for _ in installation_manager .user_installations ()]
46
50
print (json .dumps (all_users ))
47
51
48
52
49
- def skip (schema : str , table : str | None = None ):
53
+ @ucx .command
54
+ def skip (w : WorkspaceClient , schema : str | None = None , table : str | None = None ):
55
+ """Create a skip comment on a schema or a table"""
50
56
logger .info ("Running skip command" )
51
57
if not schema :
52
- logger .error ("--Schema is a required parameter." )
58
+ logger .error ("--schema is a required parameter." )
53
59
return None
54
- ws = WorkspaceClient ()
55
- installation_manager = InstallationManager (ws )
56
- installation = installation_manager .for_user (ws .current_user .me ())
60
+ installation_manager = InstallationManager (w )
61
+ installation = installation_manager .for_user (w .current_user .me ())
57
62
if not installation :
58
63
logger .error (CANT_FIND_UCX_MSG )
59
64
return None
60
65
warehouse_id = installation .config .warehouse_id
61
- sql_backend = StatementExecutionBackend (ws , warehouse_id )
62
- mapping = TableMapping (ws )
66
+ sql_backend = StatementExecutionBackend (w , warehouse_id )
67
+ mapping = TableMapping (w )
63
68
if table :
64
69
mapping .skip_table (sql_backend , schema , table )
65
70
else :
66
71
mapping .skip_schema (sql_backend , schema )
67
72
68
73
69
- def sync_workspace_info ():
74
+ @ucx .command (is_account = True )
75
+ def sync_workspace_info (a : AccountClient ):
76
+ """upload workspace config to all workspaces in the account where ucx is installed"""
77
+ logger .info (f"Account ID: { a .config .account_id } " )
70
78
workspaces = AccountWorkspaces (AccountConfig (connect = ConnectConfig ()))
71
79
workspaces .sync_workspace_info ()
72
80
73
81
74
- def manual_workspace_info ():
75
- ws = WorkspaceClient ()
82
+ @ucx .command
83
+ def manual_workspace_info (w : WorkspaceClient ):
84
+ """only supposed to be run if cannot get admins to run `databricks labs ucx sync-workspace-info`"""
76
85
prompts = Prompts ()
77
- workspace_info = WorkspaceInfo (ws )
86
+ workspace_info = WorkspaceInfo (w )
78
87
workspace_info .manual_workspace_info (prompts )
79
88
80
89
81
- def create_table_mapping ():
82
- ws = WorkspaceClient ()
83
- table_mapping = TableMapping (ws )
84
- workspace_info = WorkspaceInfo (ws )
85
- installation_manager = InstallationManager (ws )
86
- installation = installation_manager .for_user (ws .current_user .me ())
87
- sql_backend = StatementExecutionBackend (ws , installation .config .warehouse_id )
90
+ @ucx .command
91
+ def create_table_mapping (w : WorkspaceClient ):
92
+ """create initial table mapping for review"""
93
+ table_mapping = TableMapping (w )
94
+ workspace_info = WorkspaceInfo (w )
95
+ installation_manager = InstallationManager (w )
96
+ installation = installation_manager .for_user (w .current_user .me ())
97
+ sql_backend = StatementExecutionBackend (w , installation .config .warehouse_id )
88
98
tables_crawler = TablesCrawler (sql_backend , installation .config .inventory_database )
89
99
path = table_mapping .save (tables_crawler , workspace_info )
90
- webbrowser .open (f"{ ws .config .host } /#workspace{ path } " )
100
+ webbrowser .open (f"{ w .config .host } /#workspace{ path } " )
91
101
92
102
93
- def validate_external_locations ():
94
- ws = WorkspaceClient ()
103
+ @ucx .command
104
+ def validate_external_locations (w : WorkspaceClient ):
105
+ """validates and provides mapping to external table to external location and shared generation tf scripts"""
95
106
prompts = Prompts ()
96
- installation_manager = InstallationManager (ws )
97
- installation = installation_manager .for_user (ws .current_user .me ())
98
- sql_backend = StatementExecutionBackend (ws , installation .config .warehouse_id )
99
- location_crawler = ExternalLocations (ws , sql_backend , installation .config .inventory_database )
107
+ installation_manager = InstallationManager (w )
108
+ installation = installation_manager .for_user (w .current_user .me ())
109
+ sql_backend = StatementExecutionBackend (w , installation .config .warehouse_id )
110
+ location_crawler = ExternalLocations (w , sql_backend , installation .config .inventory_database )
100
111
path = location_crawler .save_as_terraform_definitions_on_workspace (installation .path )
101
112
if path and prompts .confirm (f"external_locations.tf file written to { path } . Do you want to open it?" ):
102
- webbrowser .open (f"{ ws .config .host } /#workspace{ path } " )
113
+ webbrowser .open (f"{ w .config .host } /#workspace{ path } " )
103
114
104
115
105
- def ensure_assessment_run ():
106
- ws = WorkspaceClient ()
107
- installation_manager = InstallationManager (ws )
108
- installation = installation_manager .for_user (ws .current_user .me ())
116
+ @ucx .command
117
+ def ensure_assessment_run (w : WorkspaceClient ):
118
+ """ensure the assessment job was run on a workspace"""
119
+ installation_manager = InstallationManager (w )
120
+ installation = installation_manager .for_user (w .current_user .me ())
109
121
if not installation :
110
122
logger .error (CANT_FIND_UCX_MSG )
111
123
return None
112
124
else :
113
- workspace_installer = WorkspaceInstaller (ws )
125
+ workspace_installer = WorkspaceInstaller (w )
114
126
workspace_installer .validate_and_run ("assessment" )
115
127
116
128
117
- def repair_run (step ):
129
+ @ucx .command
130
+ def repair_run (w : WorkspaceClient , step ):
131
+ """Repair Run the Failed Job"""
118
132
if not step :
119
133
raise KeyError ("You did not specify --step" )
120
- ws = WorkspaceClient ()
121
- installer = WorkspaceInstaller (ws )
134
+ installer = WorkspaceInstaller (w )
122
135
logger .info (f"Repair Running { step } Job" )
123
136
installer .repair_run (step )
124
137
125
138
126
- def revert_migrated_tables (schema : str , table : str , * , delete_managed : bool = False ):
127
- ws = WorkspaceClient ()
139
+ @ucx .command
140
+ def revert_migrated_tables (w : WorkspaceClient , schema : str , table : str , * , delete_managed : bool = False ):
141
+ """remove notation on a migrated table for re-migration"""
128
142
prompts = Prompts ()
129
- installation_manager = InstallationManager (ws )
130
- installation = installation_manager .for_user (ws .current_user .me ())
143
+ installation_manager = InstallationManager (w )
144
+ installation = installation_manager .for_user (w .current_user .me ())
131
145
if not schema and not table :
132
146
if not prompts .confirm (
133
147
"You haven't specified a schema or a table. All migrated tables will be reverted."
@@ -139,46 +153,15 @@ def revert_migrated_tables(schema: str, table: str, *, delete_managed: bool = Fa
139
153
logger .error (CANT_FIND_UCX_MSG )
140
154
return None
141
155
warehouse_id = installation .config .warehouse_id
142
- sql_backend = StatementExecutionBackend (ws , warehouse_id )
156
+ sql_backend = StatementExecutionBackend (w , warehouse_id )
143
157
table_crawler = TablesCrawler (sql_backend , installation .config .inventory_database )
144
- tmp = TableMapping (ws )
145
- tm = TablesMigrate (table_crawler , ws , sql_backend , tmp )
158
+ tmp = TableMapping (w )
159
+ tm = TablesMigrate (table_crawler , w , sql_backend , tmp )
146
160
if tm .print_revert_report (delete_managed = delete_managed ) and prompts .confirm (
147
161
"Would you like to continue?" , max_attempts = 2
148
162
):
149
163
tm .revert_migrated_tables (schema , table , delete_managed = delete_managed )
150
164
151
165
152
- MAPPING = {
153
- "open-remote-config" : open_remote_config ,
154
- "installations" : list_installations ,
155
- "workflows" : workflows ,
156
- "sync-workspace-info" : sync_workspace_info ,
157
- "manual-workspace-info" : manual_workspace_info ,
158
- "create-table-mapping" : create_table_mapping ,
159
- "validate-external-locations" : validate_external_locations ,
160
- "ensure-assessment-run" : ensure_assessment_run ,
161
- "skip" : skip ,
162
- "repair-run" : repair_run ,
163
- "revert-migrated-tables" : revert_migrated_tables ,
164
- }
165
-
166
-
167
- def main (raw ):
168
- payload = json .loads (raw )
169
- command = payload ["command" ]
170
- if command not in MAPPING :
171
- msg = f"cannot find command: { command } "
172
- raise KeyError (msg )
173
- flags = payload ["flags" ]
174
- log_level = flags .pop ("log_level" )
175
- if log_level == "disabled" :
176
- log_level = "info"
177
- databricks_logger = logging .getLogger ("databricks" )
178
- databricks_logger .setLevel (log_level .upper ())
179
- kwargs = {k .replace ("-" , "_" ): v for k , v in flags .items ()}
180
- MAPPING [command ](** kwargs )
181
-
182
-
183
- if __name__ == "__main__" :
184
- main (* sys .argv [1 :])
166
+ if "__main__" == __name__ :
167
+ ucx ()
0 commit comments