-
Notifications
You must be signed in to change notification settings - Fork 0
#220 SLC UX Gen 2 #223
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
#220 SLC UX Gen 2 #223
Changes from all commits
b841a30
1272063
e214890
3834fc4
3086626
6dbc202
d6f62a1
0300400
de30081
26d3029
64998fb
4e45cde
c3060c6
c4d7341
d221fac
3ae7aea
39d3c7f
75f2e2e
0201d0b
7a57943
b0b7cae
f2c2993
393def2
dd31d86
3653218
78787b5
f7f38ec
6e3258b
a37e914
a19a77b
0604a1d
e4a3439
d249534
ccece53
beb8457
e39b3d7
a2fa252
a25a830
e5d98d7
18944dc
23c65c2
fc2f297
a80daf3
5090bbc
82400ec
616004e
51989cd
fc1109f
4f2cbb2
6ce7d1d
594453d
1753329
4b5e32c
0796ffd
d86e254
dd2ff64
11f2f23
e8bdc74
5fe09e9
a775d54
eaa9c1b
d24720d
06f8080
c200a67
ea04890
dd9dff8
07ea46a
3e5c8b4
fc60725
8ee8771
24cfd51
035811d
5657165
e048c26
e2d27b3
6b22864
426f7f7
b05572a
d1f8d0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,25 @@ | ||
| # Notebook Connector User Guide | ||
|
|
||
| ## SlctManager | ||
| ## Managing Script Language Containers (SLCs) | ||
|
|
||
| Class `SlctManager` in the Notebook Connector (NC) supports building different flavors of [Exasol Script Language Containers](https://github.com/exasol/script-languages-release) (SLCs). SlctManager uses [script-languages-container-tool](https://github.com/exasol/script-languages-container-tool) internally. | ||
| The Notebook Connector (NC) supports building different flavors of [Exasol Script Language Containers](https://github.com/exasol/script-languages-release) (SLCs) using the [script-languages-container-tool](https://github.com/exasol/script-languages-container-tool). | ||
|
|
||
| * The name of the SLC flavor must be provided in the Secure Configuration Storage (SCS) passed as parameter `secrets` to the constructor of SlctManager. | ||
| * Additionally the caller must specify the *key* in the SCS for finding the flavor name. | ||
| The specific options for building an SLC are stored in the Secure Configuration Storage (SCS). Each SLC is identified by an arbitrary unique name used as index into the SCS for finding the related options. | ||
|
|
||
| The constructor therefore supports the additional optional parameter `session`: | ||
| You can set the SLC options using the class method `ScriptLanguageContainer.create()`, with parameters | ||
| * `secrets`: The SCS | ||
| * `name`: The name of the SLC instance | ||
| * will be converted to upper-case and must be unique | ||
| * `flavor`: The name of a template as provided by the [Exasol Script Language Containers](https://github.com/exasol/script-languages-release). | ||
|
|
||
|  | ||
| Method `create()` will then | ||
| * Select a Language Alias for executing UDF scripts inside the SLC | ||
| * See section _Define your own script aliases_ on [docs.exasol.com](https://docs.exasol.com/db/latest/database_concepts/udf_scripts/adding_new_packages_script_languages.htm). | ||
| * The Language Alias will use prefix `custom_slc_` followed by the specified name | ||
| * Consecutive call to method `deploy()` will overwrite the SLC using the same Language Alias. | ||
| * Save the `flavor` to the SCS indexed by the SLC's name. | ||
| * Raise an error if the name has already been used. | ||
| * Clone the SLC Git repository to the local file system. | ||
|
|
||
| The constructor of class `ScriptLanguageContainer` verifies the SCS to contain the flavor and the SLC repository to be cloned to the local file system. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from exasol.nb_connector.slc.script_language_container import ( | ||
| PipPackageDefinition, | ||
| ScriptLanguageContainer, | ||
| ) | ||
| from exasol.nb_connector.slc.slc_flavor import SlcError |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| from pathlib import Path | ||
|
|
||
| from exasol.nb_connector.language_container_activation import ACTIVATION_KEY_PREFIX | ||
|
|
||
| DEFAULT_ALIAS = "ai_lab_default" # To be used in the Jupyter notebooks of the AI Lab | ||
| PATH_IN_BUCKET = "container" | ||
|
|
||
| SLC_DOCKER_IMG_NAME = "exasol/script-language-container" | ||
|
|
||
| SLC_ACTIVATION_KEY_PREFIX = ACTIVATION_KEY_PREFIX + "slc_" | ||
| """ | ||
| Activation SQL for the Custom SLC will be saved in the Secure | ||
| Configuration Storage with this key. | ||
| """ | ||
|
|
||
| FLAVORS_PATH_IN_SLC_REPO = Path("flavors") | ||
| """Path to flavors within the script-languages-release repository""" | ||
|
|
||
| SLC_RELEASE_TAG = "9.7.0" | ||
| """ | ||
| Using the SLC_RELEASE 9.7.0 because we are limited to slc-tool 3.*. (see pyproject.toml) | ||
| Check the developer guide (./doc/developer-guide.md) for more information. | ||
| """ | ||
|
|
||
| WORKSPACE_DIR = ".workspace" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,212 @@ | ||||||
| from __future__ import annotations | ||||||
|
|
||||||
| import re | ||||||
| from collections import namedtuple | ||||||
| from pathlib import ( | ||||||
| Path, | ||||||
| ) | ||||||
|
|
||||||
| from exasol.slc import api as exaslct_api | ||||||
| from exasol_integration_test_docker_environment.lib.docker import ( | ||||||
| ContextDockerClient, | ||||||
| ) | ||||||
|
|
||||||
| from exasol.nb_connector.ai_lab_config import AILabConfig as CKey | ||||||
| from exasol.nb_connector.language_container_activation import ACTIVATION_KEY_PREFIX | ||||||
| from exasol.nb_connector.secret_store import Secrets | ||||||
| from exasol.nb_connector.slc import constants | ||||||
| from exasol.nb_connector.slc.slc_flavor import ( | ||||||
| SlcError, | ||||||
| SlcFlavor, | ||||||
| ) | ||||||
| from exasol.nb_connector.slc.workspace import ( | ||||||
| Workspace, | ||||||
| current_directory, | ||||||
| ) | ||||||
|
|
||||||
| PipPackageDefinition = namedtuple("PipPackageDefinition", ["pkg", "version"]) | ||||||
|
|
||||||
|
|
||||||
| class ScriptLanguageContainer: | ||||||
| """ | ||||||
| Support building different flavors of Exasol Script Language | ||||||
| Containers (SLCs) using the SLCT. | ||||||
|
|
||||||
| Parameter ``name`` serves as base of the language alias and a key for the related flavor stored in the | ||||||
| Secure Configuration Storage (SCS / secrets / conf). The flavor is used | ||||||
| as a template for building the SLC. | ||||||
|
|
||||||
| If the flavor is missing in the SCS or the SLC Git repository has not been | ||||||
| checked out (i.e. cloned) into the checkout_dir, then the constructor will | ||||||
| raise an SlcError. | ||||||
|
|
||||||
| Additionally, the caller needs to ensure, that a flavor with this name is | ||||||
| contained in the SLC release specified in variable | ||||||
| constants.SLC_RELEASE_TAG. | ||||||
| """ | ||||||
|
|
||||||
| def __init__( | ||||||
| self, | ||||||
| secrets: Secrets, | ||||||
| name: str, | ||||||
| ): | ||||||
| self.secrets = secrets | ||||||
| self.name = name | ||||||
| self.flavor = SlcFlavor(name).verify(secrets) | ||||||
| self.workspace = Workspace.for_slc(name) | ||||||
| if not self.flavor_path.is_dir(): | ||||||
| raise SlcError( | ||||||
| f"SLC Git repository not checked out to {self.checkout_dir}." | ||||||
| ) | ||||||
|
|
||||||
| @classmethod | ||||||
| def create( | ||||||
| cls, | ||||||
| secrets: Secrets, | ||||||
| name: str, | ||||||
| flavor: str, | ||||||
| ) -> ScriptLanguageContainer: | ||||||
| slc_flavor = SlcFlavor(name) | ||||||
| if slc_flavor.exists(secrets): | ||||||
| raise SlcError( | ||||||
| "Secure Configuration Storage already contains a" | ||||||
| f" flavor for SLC name {name}." | ||||||
| ) | ||||||
| slc_flavor.save(secrets, flavor) | ||||||
| workspace = Workspace.for_slc(name) | ||||||
| workspace.clone_slc_repo() | ||||||
| return cls(secrets=secrets, name=name) | ||||||
ckunki marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| @property | ||||||
| def language_alias(self) -> str: | ||||||
| """ | ||||||
| Is case-insensitive. | ||||||
| """ | ||||||
| return f"custom_slc_{self.name}" | ||||||
|
|
||||||
| @property | ||||||
| def checkout_dir(self) -> Path: | ||||||
| return self.workspace.git_clone_path | ||||||
|
|
||||||
| @property | ||||||
| def _flavor_path_rel(self) -> str: | ||||||
| return str(self.flavor_path.relative_to(self.checkout_dir)) | ||||||
|
|
||||||
| @property | ||||||
| def flavor_path(self) -> Path: | ||||||
| return self.checkout_dir / constants.FLAVORS_PATH_IN_SLC_REPO / self.flavor | ||||||
|
|
||||||
| @property | ||||||
| def custom_pip_file(self) -> Path: | ||||||
| """ | ||||||
| Returns the path to the custom pip file of the flavor | ||||||
| """ | ||||||
| return ( | ||||||
| self.flavor_path | ||||||
| / "flavor_customization" | ||||||
| / "packages" | ||||||
| / "python3_pip_packages" | ||||||
| ) | ||||||
|
|
||||||
| def export(self): | ||||||
| """ | ||||||
| Exports the current SLC to the export directory. | ||||||
| """ | ||||||
| with current_directory(self.checkout_dir): | ||||||
| exaslct_api.export( | ||||||
| flavor_path=(str(self._flavor_path_rel),), | ||||||
| export_path=str(self.workspace.export_path), | ||||||
| output_directory=str(self.workspace.output_path), | ||||||
| release_name=self.language_alias, | ||||||
| ) | ||||||
|
|
||||||
| def deploy(self): | ||||||
| """ | ||||||
| Deploys the current script-languages-container to the database and | ||||||
| stores the activation string in the Secure Configuration Storage. | ||||||
| """ | ||||||
| bfs_params = { | ||||||
| k: self.secrets.get(v) | ||||||
| for k, v in [ | ||||||
| ("bucketfs_host", CKey.bfs_host_name), | ||||||
| ("bucketfs_port", CKey.bfs_port), | ||||||
| ("bucketfs_user", CKey.bfs_user), | ||||||
| ("bucketfs_password", CKey.bfs_password), | ||||||
| ("bucketfs_name", CKey.bfs_service), | ||||||
| ("bucket", CKey.bfs_bucket), | ||||||
| ] | ||||||
| } | ||||||
|
|
||||||
| with current_directory(self.checkout_dir): | ||||||
| exaslct_api.deploy( | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deploy_result[flavor_name]["release"]
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed this from the current PR, |
||||||
| flavor_path=(str(self._flavor_path_rel),), | ||||||
| **bfs_params, | ||||||
| path_in_bucket=constants.PATH_IN_BUCKET, | ||||||
| release_name=self.language_alias, | ||||||
| output_directory=str(self.workspace.output_path), | ||||||
| ) | ||||||
| container_name = f"{self.flavor}-release-{self.language_alias}" | ||||||
| result = exaslct_api.generate_language_activation( | ||||||
| flavor_path=str(self._flavor_path_rel), | ||||||
| bucketfs_name=bfs_params["bucketfs_name"], | ||||||
| bucket_name=bfs_params["bucket"], | ||||||
| container_name=container_name, | ||||||
| path_in_bucket=constants.PATH_IN_BUCKET, | ||||||
| ) | ||||||
| alter_session_cmd = result[0] | ||||||
| re_res = re.search( | ||||||
| r"ALTER SESSION SET SCRIPT_LANGUAGES='(.*)'", alter_session_cmd | ||||||
| ) | ||||||
| activation_key = re_res.groups()[0] | ||||||
| _, url = activation_key.split("=", maxsplit=1) | ||||||
|
Comment on lines
+149
to
+161
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use LanguageDefinitionsBuilder from the DeployResult which exaslct_api.deploy returns
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed this from the current PR, |
||||||
| self.secrets.save(self._alias_key, f"{self.language_alias}={url}") | ||||||
|
|
||||||
| @property | ||||||
| def _alias_key(self): | ||||||
| return constants.SLC_ACTIVATION_KEY_PREFIX + self.language_alias | ||||||
|
|
||||||
| @property | ||||||
| def activation_key(self) -> str: | ||||||
| """ | ||||||
| Returns the language activation string for the uploaded script-language-container. | ||||||
| Can be used in `ALTER SESSION` or `ALTER_SYSTEM` SQL commands to activate | ||||||
| the language of the uploaded script-language-container. | ||||||
| """ | ||||||
| try: | ||||||
| return self.secrets[self._alias_key] | ||||||
| except AttributeError as ex: | ||||||
| raise SlcError( | ||||||
| "Secure Configuration Storage does not contains an activation key." | ||||||
| ) from ex | ||||||
|
|
||||||
| def append_custom_packages(self, pip_packages: list[PipPackageDefinition]): | ||||||
| """ | ||||||
| Appends packages to the custom pip file. | ||||||
|
|
||||||
| Note: This method is not idempotent: Multiple calls with the same | ||||||
| package definitions will result in duplicate entries. | ||||||
| """ | ||||||
| with open(self.custom_pip_file, "a") as f: | ||||||
| for p in pip_packages: | ||||||
| print(f"{p.pkg}|{p.version}", file=f) | ||||||
|
|
||||||
| @property | ||||||
| def docker_image_tags(self) -> list[str]: | ||||||
| """ | ||||||
| Return list of Docker image tags related to the current SLC. | ||||||
| """ | ||||||
| image_name = constants.SLC_DOCKER_IMG_NAME | ||||||
| prefix = f"{image_name}:{self.flavor}" | ||||||
| with ContextDockerClient() as docker_client: | ||||||
| images = docker_client.images.list(name=image_name) | ||||||
| return [tag for img in images if (tag := img.tags[0]).startswith(prefix)] | ||||||
|
|
||||||
| def clean_docker_images(self): | ||||||
| """ | ||||||
| Deletes local docker images related to the current SLC. | ||||||
| """ | ||||||
| with current_directory(self.checkout_dir): | ||||||
| exaslct_api.clean_flavor_images( | ||||||
| flavor_path=(str(self._flavor_path_rel),), | ||||||
| output_directory=str(self.workspace.output_path), | ||||||
| ) | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should change the documentation sphinx and rst, then then it would also generate the api doc already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was about thinking in the same direction 👍
I propose to address this in a separate PR