Skip to content

Commit ac8855f

Browse files
authored
Added interactive installation wizard (#184)
This PR adds interactive command-line installation frontend for UCX on a Databricks Workspace. It allows to create configuration file interactively: ![..](https://raw.githubusercontent.com/databricks/ucx/97b6d8bd66b08f4777bd11057e51698f5bcf2519/examples/ucx-install.gif?token=AAB7M4IIZOS242IZ26SWDZDE74OMY)
1 parent 68132f9 commit ac8855f

File tree

9 files changed

+318
-122
lines changed

9 files changed

+318
-122
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,7 @@ cython_debug/
145145
# dev files and scratches
146146
dev/cleanup.py
147147

148-
Support
148+
Support
149+
150+
.databricks
151+
.vscode

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
# UCX - Unity Catalog Migration Toolkit
22

3-
This repo contains various functions and utilities for UC Upgrade.
3+
Your best companion for enabling the Unity Catalog.
4+
5+
## Installation
6+
7+
The `./install.sh` script will guide you through installation process. Make sure you have Python 3.10 (or greater)
8+
installed on your workstation, and you've configured authentication for
9+
the [Databricks Workspace](https://databricks-sdk-py.readthedocs.io/en/latest/authentication.html#default-authentication-flow).
10+
11+
![install wizard](./examples/ucx-install.gif)
12+
13+
The easiest way to install and authenticate is through a [Databricks configuration profile](https://docs.databricks.com/en/dev-tools/auth.html#databricks-client-unified-authentication):
14+
15+
```shell
16+
export DATABRICKS_CONFIG_PROFILE=ABC
17+
./install.sh
18+
```
19+
20+
You can also specify environment variables in a more direct way, like in this example for installing
21+
on a Azure Databricks Workspace using the Azure CLI authentication:
22+
23+
```shell
24+
az login
25+
export DATABRICKS_HOST=https://adb-123....azuredatabricks.net/
26+
./install.sh
27+
```
428

529
## Latest working version and how-to
630

bin/install.py

Lines changed: 0 additions & 119 deletions
This file was deleted.

examples/ucx-install.gif

636 KB
Loading

install.sh

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/bin/bash
2+
3+
# This script will eventually be replaced with `databricks labs install ucx` command.
4+
5+
# Initialize an empty array to store Python 3 binary paths
6+
python3_binaries=()
7+
8+
# Split the $PATH variable into an array using ':' as the delimiter
9+
IFS=':' read -ra path_dirs <<< "$PATH"
10+
11+
# Iterate over each directory in the $PATH
12+
for dir in "${path_dirs[@]}"; do
13+
# Construct the full path to the python3 binary in the current directory
14+
python3_path="${dir}/python3"
15+
16+
# Check if the python3 binary exists and is executable
17+
if [ -x "$python3_path" ]; then
18+
python3_binaries+=("$python3_path")
19+
fi
20+
done
21+
22+
if [ -z "${python3_binaries[*]}" ]; then
23+
echo "[!] No Python binaries detected"
24+
exit 1
25+
fi
26+
27+
# Check versions for all Python binaries found
28+
python_versions=()
29+
for python_binary in "${python3_binaries[@]}"; do
30+
python_version=$("$python_binary" --version | awk '{print $2}')
31+
python_versions+=("$python_version -> $(realpath "$python_binary")")
32+
done
33+
34+
IFS=$'\n' python_versions=($(printf "%s\n" "${python_versions[@]}" | sort -V))
35+
36+
py="/dev/null"
37+
for version_and_binary in "${python_versions[@]}"; do
38+
echo "[i] found Python $version_and_binary"
39+
IFS=" -> " read -ra parts <<< "$version_and_binary"
40+
py="${parts[2]}"
41+
done
42+
43+
echo "[i] latest python is $py"
44+
45+
tmp_dir=$(mktemp -d)
46+
47+
# Create isolated Virtualenv with the latest Python version
48+
# in the ephemeral temporary directory
49+
$py -m venv "$tmp_dir"
50+
51+
. "$tmp_dir/bin/activate"
52+
53+
# Use the Python from Virtualenv
54+
py="$tmp_dir/bin/python"
55+
56+
echo "[+] installing dependencies within ephemeral Virtualenv: $tmp_dir"
57+
# Install all project dependencies, so that installer can proceed
58+
$py -m pip install --quiet -e .
59+
60+
# Invoke python module of the install app directly,
61+
# without console_scripts entrypoint
62+
$py -m databricks.labs.ucx.cli.app install
63+
64+
rm -r "$tmp_dir"

src/databricks/labs/ucx/cli/app.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1+
import logging
12
import os
23
from pathlib import Path
34
from typing import Annotated
45

56
import typer
7+
from databricks.sdk import WorkspaceClient
68
from typer import Typer
79

10+
from databricks.labs.ucx.__about__ import __version__
11+
from databricks.labs.ucx.logger import _install
12+
13+
_install()
14+
logging.root.setLevel("INFO")
15+
logger = logging.getLogger(__name__)
16+
817
app = Typer(name="UC Migration Toolkit", pretty_exceptions_show_locals=True)
918

1019

20+
@app.command()
21+
def install():
22+
from databricks.labs.ucx.install import main
23+
24+
ws = WorkspaceClient(product="ucx", product_version=__version__)
25+
main(ws, verbose=False)
26+
27+
1128
@app.command()
1229
def migrate_groups(config_file: Annotated[Path, typer.Argument(help="Path to config file")] = "migration_config.yml"):
1330
from databricks.labs.ucx.config import MigrationConfig

src/databricks/labs/ucx/config.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ def from_dict(cls, raw: dict):
8282
return cls(**raw)
8383

8484

85+
# Used to set the right expectation about configuration file schema
86+
_CONFIG_VERSION = 1
87+
88+
8589
@dataclass
8690
class MigrationConfig:
8791
inventory_database: str
@@ -112,10 +116,19 @@ def inner(x):
112116
return dict(result)
113117
return x
114118

115-
return inner(self)
119+
serialized = inner(self)
120+
serialized["version"] = _CONFIG_VERSION
121+
return serialized
116122

117123
@classmethod
118124
def from_dict(cls, raw: dict) -> "MigrationConfig":
125+
stored_version = raw.get("version", None)
126+
if stored_version != _CONFIG_VERSION:
127+
msg = (
128+
f"Unsupported config version: {stored_version}. "
129+
f"UCX v{__version__} expects config version to be {_CONFIG_VERSION}"
130+
)
131+
raise ValueError(msg)
119132
return cls(
120133
inventory_database=raw.get("inventory_database"),
121134
tacl=TaclConfig.from_dict(raw.get("tacl", {})),

0 commit comments

Comments
 (0)