Skip to content

mag_core module

realm = click.option('-r', '--realm', default='magasin', show_default=True, help='magasin realm', callback=validate_realm_callback) module-attribute

Adds the --realm option.

forward_port(realm, component, service_name, ports, verbose=False)

Forward ports for the specified realm, component, and service name.

Parameters:

Name Type Description Default
realm str

A string representing the realm.

required
component str

A string representing the component.

required
service_name str

(str) A string representing the service name. The service name can be obtained using kubectl get services --namespace magasin-superset).

required
ports str

A string representing the ports to be forwarded (example: "8000:8000").

required
verbose bool

A boolean indicating whether to enable verbose mode (default is False).

False

Returns: None

Usage:

forward_port(realm, component, service_name, ports, verbose)

Example:

# Given this
kubectl get service -n magasin-superset
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
superset                  ClusterIP   10.100.96.47     <none>        8088/TCP   7d22h
You can forward this service
forward_port("magasin", "superset", "superset", "8088:8088")

Notes:

  • Assumes the port_forward_command function is defined elsewhere in the code.
  • Uses subprocess.Popen to launch the port forwarding command in a subprocess.
  • Registers the terminate_process function using atexit.register, ensuring that the port forwarding process is terminated when the script exits.
Source code in mag/mag_core/launch.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def forward_port(realm: str, component: str, service_name: str, ports: str, verbose=False) -> None:
    """
    Forward ports for the specified realm, component, and service name.

    Args:
        realm (str): A string representing the realm.
        component (str): A string representing the component.
        service_name: (str) A string representing the service name. The service name can be obtained using kubectl get services --namespace magasin-superset).
        ports (str): A string representing the ports to be forwarded (example: "8000:8000").
        verbose (bool): A boolean indicating whether to enable verbose mode (default is False).

    Returns:
    None

    Usage:

    forward_port(realm, component, service_name, ports, verbose)

    Example:
    ```
    # Given this
    kubectl get service -n magasin-superset
    NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
    superset                  ClusterIP   10.100.96.47     <none>        8088/TCP   7d22h
    ```
    You can forward this service
    ```
    forward_port("magasin", "superset", "superset", "8088:8088")
    ```

    Notes:

    - Assumes the port_forward_command function is defined elsewhere in the code.
    - Uses subprocess.Popen to launch the port forwarding command in a subprocess.
    - Registers the terminate_process function using atexit.register, ensuring that the port forwarding process
      is terminated when the script exits.
    """
    port_forward_command = port_forward_command_arr(
        realm, component, service_name, ports, verbose)
    click.echo("forward_port command: " + " ".join(port_forward_command))
    process = subprocess.Popen(port_forward_command, shell=False)

    local, _ = split_ports(ports)
    click.echo("Waiting for port to be open...")
    if not is_port_open(host='localhost', port=local):
        click.echo("Port could not be opened.")
        exit(-1)
    click.echo("Port ready.")

    atexit.register(terminate_process, process)

generate_random_string(length=7)

Generate a random alphanumeric lowercase string of a specified length.

Parameters: - length (int): The desired length of the random string.

Returns: - str: A random string containing letters (both lowercase and uppercase) and digits.

Source code in mag/mag_core/random.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def generate_random_string(length=7):
    """
    Generate a random alphanumeric lowercase string of a specified length.

    Parameters:
    - length (int): The desired length of the random string.

    Returns:
    - str: A random string containing letters (both lowercase and uppercase) and digits.
    """
    characters = string.ascii_lowercase + string.digits
    return ''.join(random.choice(characters) for _ in range(length))

get_namespace(component_name, realm='magasin')

Generate a namespace based on the component name and realm.

Parameters:

Name Type Description Default
component_name str

The magasin component name (superset, daskhub, drill, ...)

required
realm str

The realm. Defaults to 'magasin'.

'magasin'
Example
  • get_namespace("superset", "magasin") -> "magasin-superset"
  • get_namespace("superset", "magasin-postfix") -> "magasin-superset-postfix"
Reference

For more information about magasin realms, please see the magasin realms documentation.

Source code in mag/mag_core/realm.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def get_namespace(component_name: str, realm='magasin') -> str:
    """
    Generate a namespace based on the component name and realm.


    Args:
        component_name (str): The magasin component name (superset, daskhub, drill, ...)
        realm (str, optional): The realm. Defaults to 'magasin'.

    Example:
        * `get_namespace("superset", "magasin")` -> "magasin-superset"
        * `get_namespace("superset", "magasin-postfix")` -> "magasin-superset-postfix"

    Reference:
        For more information about magasin realms, please see the [magasin realms documentation](https://unicef.github.io/magasin/install/advanced.html#magasin-realms).
    """
    prefix, suffix = split_realm(realm)
    namespace = ""
    if prefix:
        namespace = prefix + '-' + component_name
    if suffix:
        namespace = namespace + "-" + suffix
    return namespace

is_port_open(host, port, timeout=15)

Check if a TCP port is open and responding.

Parameters:

Name Type Description Default
host str

A string representing the host to check. Example: localhost

required
port int

An integer representing the port to check. Example: 8080

required
timeout int

An integer representing the timeout in seconds. Default is 15 seconds.

15

Returns:

Name Type Description
bool

Indicates whether the port is open and responding.

Source code in mag/mag_core/launch.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def is_port_open(host, port, timeout=15):
    """
    Check if a TCP port is open and responding.

    Args:
        host (str): A string representing the host to check. Example: localhost
        port (int): An integer representing the port to check. Example: 8080
        timeout (int): An integer representing the timeout in seconds. Default is 15 seconds.

    Returns:
        bool: Indicates whether the port is open and responding.

    """
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            with socket.create_connection((host, port), timeout=1) as _:
                return True
        except (socket.timeout, ConnectionRefusedError):
            time.sleep(1)  # Wait for 1 second before retrying
        except OSError:
            return False
    return False

launch_command(realm, component, pod_name, command='/bin/bash')

Launches a command in a Kubernetes pod.

Parameters:

Name Type Description Default
realm str

The magasin realm (e.g., magasin).

required
component str

The name of the magasin component.

required
pod_name str

The name of the pod.

required
command str

The command to be executed in the pod. Defaults to '/bin/bash'.

'/bin/bash'

Returns:

Type Description

None

Example

launch_command('magasin', 'component_name', 'pod-1', 'ls -l') Running: kubectl exec pod-1 --namespace magasin -ti -- ls -l

Note

This function uses the kubectl command-line tool to execute a command in a Kubernetes pod. Make sure you have kubectl installed and configured properly before using this function.

Source code in mag/mag_core/launch.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def launch_command(realm: str, component: str, pod_name: str, command: str = '/bin/bash'):
    """
    Launches a command in a Kubernetes pod.

    Args:
        realm (str): The magasin realm (e.g., magasin).
        component (str): The name of the magasin component.
        pod_name (str): The name of the pod.
        command (str, optional): The command to be executed in the pod. Defaults to '/bin/bash'.

    Returns:
        None

    Raises:
        None

    Example:
        >>> launch_command('magasin', 'component_name', 'pod-1', 'ls -l')
        Running: kubectl exec pod-1 --namespace magasin -ti -- ls -l
        <output of the command>

    Note:
        This function uses the `kubectl` command-line tool to execute a command in a Kubernetes pod.
        Make sure you have `kubectl` installed and configured properly before using this function.
    """
    namespace = get_namespace(component_name=component, realm=realm)
    user_root = ''

    command = f"kubectl exec {pod_name} --namespace {namespace} -ti -- {command}"
    click.echo(f"Running: {command}")
    subprocess.run(command, shell=True)

launch_ui(realm, component, service_name, ports, protocol='http', verbose=False)

Launches the user interface for a given realm, component, and service.

Parameters:

Name Type Description Default
realm str

The realm of the magasin instance.

required
component str

The magasin component (f.i superset, daskhub, drill, ...)

required
service_name str

The name of the kubernetes service to forward.

required
ports str

The ports to forward, using the format "local_port:remote_port".

required
protocol str

The protocol to use (default is "http").

'http'
verbose bool

Whether to display verbose output (default is False).

False

Returns:

Name Type Description
None None

Nothing

Source code in mag/mag_core/launch.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def launch_ui(realm: str, component: str, service_name: str, ports: str, protocol: str = "http", verbose=False) -> None:
    """
    Launches the user interface for a given realm, component, and service.

    Args:
        realm (str): The realm of the magasin instance.
        component (str): The magasin component (f.i superset, daskhub, drill, ...)
        service_name (str): The name of the kubernetes service to forward.
        ports (str): The ports to forward, using the format "local_port:remote_port".
        protocol (str, optional): The protocol to use (default is "http").
        verbose (bool, optional): Whether to display verbose output (default is False).

    Returns:
        None: Nothing
    """    
    forward_port(realm=realm, component=component,
                 service_name=service_name, ports=ports, verbose=verbose)

    localhost_port, _ = split_ports(ports)
    url = f"{protocol}://localhost:{localhost_port}"
    click.echo(f"Open browser at: {url}")
    click.launch(url)
    click.echo("launch ui")

    try:
        # Wait for user to press Ctrl+C
        signal.pause()
    except KeyboardInterrupt:
        # Handle Ctrl+C: terminate the server and clean up
        process.terminate()
        os.waitpid(process.pid, 0)
        click.echo("\nServer terminated. Exiting.")

port_forward_command_arr(realm, component, service_name, ports, verbose=False)

Generate a command array for port forwarding.

This function generates a command array for port forwarding using kubectl command.

Parameters:

Name Type Description Default
realm str

The magasin realm.

required
component str

The component of the command (f.i superset, daskhub, drill,...).

required
service_name str

The Kubernetes service name to forward.

required
ports str

The ports to forward. Follows the format "local_port:remote_port".

required
verbose bool

Whether to include verbose output. Defaults to False.

False

Returns:

Name Type Description
List List

The generated command array.

Source code in mag/mag_core/launch.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def port_forward_command_arr(realm: str, component: str, service_name: str, ports: str, verbose=False) -> List:
    """Generate a command array for port forwarding.

    This function generates a command array for port forwarding using `kubectl` command.

    Args:
        realm (str): The magasin realm.
        component (str): The component of the command (f.i superset, daskhub, drill,...).
        service_name (str): The Kubernetes service name to forward.
        ports (str): The ports to forward. Follows the format "local_port:remote_port".
        verbose (bool, optional): Whether to include verbose output. Defaults to False.

    Returns:
        List: The generated command array.
    """
    namespace = get_namespace(component, realm)
    port_forward_command_arr = [
        "kubectl", "port-forward", "--address=0.0.0.0", "--namespace", namespace, service_name, ports]
    if verbose:
        click.echo("port_forward_command_arr: " +
                   " ".join(port_forward_command_arr))
    return port_forward_command_arr

port_forward_command_str(realm, component, service_name, ports, verbose=False)

Returns a string representation of the port forward command.

Parameters:

Name Type Description Default
realm str

The magasin realm.

required
component str

The component of the command (f.i superset, daskhub, drill,...).

required
service_name str

The Kubernetes service name to forward.

required
ports str

The ports to forward. Follows the format "local_port:remote_port".

required
verbose bool

Whether to include verbose output. Defaults to False.

False

Returns:

Name Type Description
str str

The port forward command as a string.

Example:

port_forward_command_str("magasin", "superset", "superset", "8088:8088")

Source code in mag/mag_core/launch.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def port_forward_command_str(realm: str, component: str, service_name: str, ports: str, verbose=False) -> str:
    """
    Returns a string representation of the port forward command.

    Args:
        realm (str): The magasin realm.
        component (str): The component of the command (f.i superset, daskhub, drill,...).
        service_name (str): The Kubernetes service name to forward.
        ports (str): The ports to forward. Follows the format "local_port:remote_port".
        verbose (bool, optional): Whether to include verbose output. Defaults to False.

    Returns:
        str: The port forward command as a string.

    Example:
    ```
    port_forward_command_str("magasin", "superset", "superset", "8088:8088")
    ```

    """
    return " ".join(port_forward_command_arr(realm=realm, component=component, service_name=service_name, ports=ports, verbose=verbose))

ports(default)

Adds the --ports option.

Validates the port format.

Parameters:

Name Type Description Default
default str

The default value for the option.

required

Returns:

Type Description

click.Option: The click option object.

Source code in mag/mag_core/options.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def ports(default: str):
    """
    Adds the --ports option.

    Validates the port format.

    Parameters:
        default (str): The default value for the option.

    Returns:
        click.Option: The click option object.

    """
    return click.option('-p', '--ports', 
                        default=default,
                        show_default=True, 
                        help='Redirection ports. Format <localhost_port>:<pod_port>. Example: 8080:8080', 
                        callback=validate_port_callback)

split_ports(ports)

Split the input string with the format "number:number" into localhost and pod port.

Args: - ports (str): Input string with the format "number:number".

Returns: - tuple: A tuple containing localhost and pod port.

Raises: - ValueError: If the input string is not in the expected format or if ports are not valid.

Source code in mag/mag_core/ports.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def split_ports(ports: str) -> tuple:
    """
    Split the input string with the format "number:number" into localhost and pod port.

    Args:
    - ports (str): Input string with the format "number:number".

    Returns:
    - tuple: A tuple containing localhost and pod port.

    Raises:
    - ValueError: If the input string is not in the expected format or if ports are not valid.
    """
    if not validate_ports(ports):
        raise ValueError("Invalid ports. Port numbers should be between 1 and 65535.")

    try:
        localhost, pod_port = map(int, ports.split(':'))
        return localhost, pod_port
    except ValueError:
        raise ValueError("Invalid ports format. Expected format: 'number:number'")

split_realm(realm)

Split the realm into prefix and suffix based on the last occurrence of "-".

Parameters:

Name Type Description Default
realm str

The input realm string.

required

Returns:

Name Type Description
tuple tuple

A tuple containing prefix and suffix.

Example
  • split_realm("magasin") -> ("magasin", "")
  • split_realm("magasin-post") -> ("magasin", "post")
  • split_realm("magasin-1-post") -> ("magasin-1", "post")
  • split_realm("dev-magasin-1") -> ("dev-magasin", "1")
Reference

For more information about magasin realms, please see the magasin realms documentation.

Source code in mag/mag_core/realm.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def split_realm(realm: str) -> tuple:
    """
    Split the realm into prefix and suffix based on the last occurrence of "-".

    Args:
        realm (str): The input realm string.

    Returns:
        tuple: A tuple containing prefix and suffix.

    Example:
        * split_realm("magasin") -> ("magasin", "")
        * split_realm("magasin-post") -> ("magasin", "post")
        * split_realm("magasin-1-post") -> ("magasin-1", "post")
        * split_realm("dev-magasin-1") -> ("dev-magasin", "1")

    Reference:
        For more information about magasin realms, please see the [magasin realms documentation](https://unicef.github.io/magasin/install/advanced.html#magasin-realms).
    """
    last_dash_index = realm.rfind("-")

    if last_dash_index == -1:
        prefix = realm
        suffix = ""
    else:
        prefix = realm[:last_dash_index]
        suffix = realm[last_dash_index + 1:]

    return prefix, suffix

terminate_process(process)

Terminate the given process if it is running.

Parameters: - process: A subprocess.Popen object representing the process to be terminated.

Usage: terminate_process(process)

Notes: - If the process is not running or is already terminated, this function returns without taking any action. - It checks if the process is running (poll() is None) and terminates it using terminate(). It then waits for the process to finish using wait().

Source code in mag/mag_core/launch.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def terminate_process(process):
    """
    Terminate the given process if it is running.

    Parameters:
    - process: A subprocess.Popen object representing the process to be terminated.

    Usage:
    terminate_process(process)

     Notes:
     - If the process is not running or is already terminated, this function returns without taking any action.
     - It checks if the process is running (poll() is None) and terminates it using terminate().
       It then waits for the process to finish using wait().
     """

    if not process:
        return
    if process.poll() is None:
        process.terminate()
        process.wait()

validate_pod_name(name)

Validates if a Kubernetes pod name is valid according to Kubernetes naming conventions.

Parameters: - name (str): The pod name to validate.

Returns: - bool: True if the pod name is valid, False otherwise.

Source code in mag/mag_core/validators.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def validate_pod_name(name: str) -> bool:
    """
    Validates if a Kubernetes pod name is valid according to Kubernetes naming conventions.

    Parameters:
    - name (str): The pod name to validate.

    Returns:
    - bool: True if the pod name is valid, False otherwise.
    """
    if not name:
        return False

    # Pod name must be no more than 253 characters in length
    if len(name) > 253:
        return False

    # Pod name must consist of lower case alphanumeric characters, '-' or '.', and
    # must start and end with an alphanumeric character
    pattern = re.compile(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$')
    return bool(pattern.match(name))

validate_port_callback(ctx, param, value)

Click callback to validate the port format It validates the ports using mag_core.validators.validate_ports

Raises:

Type Description
BadParameter

If port format is not valid

Source code in mag/mag_core/options.py
38
39
40
41
42
43
44
45
46
47
def validate_port_callback(ctx, param, value):
    """Click callback to validate the port format
    It validates the ports using `mag_core.validators.validate_ports`

    Raises:
        click.BadParameter: If port format is not valid
    """
    if not validate_ports(value):
        raise click.BadParameter("The format of the ports is number:number. Where number is between 1 and 65535. Example: 80:80")
    return value

validate_ports(ports)

Validate that the ports in the input string are numbers between 1 and 65535.

Args: - ports (str): Input string with the format "number:number".

Returns: - bool: True if the ports are valid, False otherwise.

Source code in mag/mag_core/validators.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def validate_ports(ports: str) -> bool:
    """
    Validate that the ports in the input string are numbers between 1 and 65535.

    Args:
    - ports (str): Input string with the format "number:number".

    Returns:
    - bool: True if the ports are valid, False otherwise.
    """
    try:
        localhost, pod_port = map(int, ports.split(':'))
        return 1 <= localhost <= 65535 and 1 <= pod_port <= 65535
    except ValueError:
        return False

validate_realm(realm)

Verify if the realm contains only letters, numbers, "-", and "_".

Args: - realm (str): The input realm string.

Returns: - bool: True if the realm contains only valid characters, False otherwise.

Source code in mag/mag_core/validators.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def validate_realm(realm: str) -> bool:
    """
    Verify if the realm contains only letters, numbers, "-", and "_".

    Args:
    - realm (str): The input realm string.

    Returns:
    - bool: True if the realm contains only valid characters, False otherwise.
    """
    # Define a regular expression pattern for valid characters
    pattern = re.compile(r'^[a-z0-9-]+$')

    # Use re.match to check if the entire string matches the pattern
    return bool(pattern.match(realm))

validate_realm_callback(ctx, param, value)

Validates if the realm has a correct value

See: https://click.palletsprojects.com/en/8.1.x/options/#callbacks-for-validation

Raises:

Type Description
BadParameter

If name is not valid

Source code in mag/mag_core/options.py
25
26
27
28
29
30
31
32
33
34
35
def validate_realm_callback(ctx, param, value):
    """Validates if the realm has a correct value

    See: https://click.palletsprojects.com/en/8.1.x/options/#callbacks-for-validation

    Raises:
        click.BadParameter: If name is not valid
    """
    if not validate_realm(value):
        raise click.BadParameter("Realm can only contain letters, numbers and '-'")
    return value

launch

This module provides functions for launching and managing services in a Kubernetes cluster.

It includes functions for generating port forwarding commands, checking if a TCP port is open, forwarding ports, launching user interfaces, and executing commands in Kubernetes pods.

forward_port(realm, component, service_name, ports, verbose=False)

Forward ports for the specified realm, component, and service name.

Parameters:

Name Type Description Default
realm str

A string representing the realm.

required
component str

A string representing the component.

required
service_name str

(str) A string representing the service name. The service name can be obtained using kubectl get services --namespace magasin-superset).

required
ports str

A string representing the ports to be forwarded (example: "8000:8000").

required
verbose bool

A boolean indicating whether to enable verbose mode (default is False).

False

Returns: None

Usage:

forward_port(realm, component, service_name, ports, verbose)

Example:

# Given this
kubectl get service -n magasin-superset
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
superset                  ClusterIP   10.100.96.47     <none>        8088/TCP   7d22h
You can forward this service
forward_port("magasin", "superset", "superset", "8088:8088")

Notes:

  • Assumes the port_forward_command function is defined elsewhere in the code.
  • Uses subprocess.Popen to launch the port forwarding command in a subprocess.
  • Registers the terminate_process function using atexit.register, ensuring that the port forwarding process is terminated when the script exits.
Source code in mag/mag_core/launch.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def forward_port(realm: str, component: str, service_name: str, ports: str, verbose=False) -> None:
    """
    Forward ports for the specified realm, component, and service name.

    Args:
        realm (str): A string representing the realm.
        component (str): A string representing the component.
        service_name: (str) A string representing the service name. The service name can be obtained using kubectl get services --namespace magasin-superset).
        ports (str): A string representing the ports to be forwarded (example: "8000:8000").
        verbose (bool): A boolean indicating whether to enable verbose mode (default is False).

    Returns:
    None

    Usage:

    forward_port(realm, component, service_name, ports, verbose)

    Example:
    ```
    # Given this
    kubectl get service -n magasin-superset
    NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
    superset                  ClusterIP   10.100.96.47     <none>        8088/TCP   7d22h
    ```
    You can forward this service
    ```
    forward_port("magasin", "superset", "superset", "8088:8088")
    ```

    Notes:

    - Assumes the port_forward_command function is defined elsewhere in the code.
    - Uses subprocess.Popen to launch the port forwarding command in a subprocess.
    - Registers the terminate_process function using atexit.register, ensuring that the port forwarding process
      is terminated when the script exits.
    """
    port_forward_command = port_forward_command_arr(
        realm, component, service_name, ports, verbose)
    click.echo("forward_port command: " + " ".join(port_forward_command))
    process = subprocess.Popen(port_forward_command, shell=False)

    local, _ = split_ports(ports)
    click.echo("Waiting for port to be open...")
    if not is_port_open(host='localhost', port=local):
        click.echo("Port could not be opened.")
        exit(-1)
    click.echo("Port ready.")

    atexit.register(terminate_process, process)

is_port_open(host, port, timeout=15)

Check if a TCP port is open and responding.

Parameters:

Name Type Description Default
host str

A string representing the host to check. Example: localhost

required
port int

An integer representing the port to check. Example: 8080

required
timeout int

An integer representing the timeout in seconds. Default is 15 seconds.

15

Returns:

Name Type Description
bool

Indicates whether the port is open and responding.

Source code in mag/mag_core/launch.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def is_port_open(host, port, timeout=15):
    """
    Check if a TCP port is open and responding.

    Args:
        host (str): A string representing the host to check. Example: localhost
        port (int): An integer representing the port to check. Example: 8080
        timeout (int): An integer representing the timeout in seconds. Default is 15 seconds.

    Returns:
        bool: Indicates whether the port is open and responding.

    """
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            with socket.create_connection((host, port), timeout=1) as _:
                return True
        except (socket.timeout, ConnectionRefusedError):
            time.sleep(1)  # Wait for 1 second before retrying
        except OSError:
            return False
    return False

launch_command(realm, component, pod_name, command='/bin/bash')

Launches a command in a Kubernetes pod.

Parameters:

Name Type Description Default
realm str

The magasin realm (e.g., magasin).

required
component str

The name of the magasin component.

required
pod_name str

The name of the pod.

required
command str

The command to be executed in the pod. Defaults to '/bin/bash'.

'/bin/bash'

Returns:

Type Description

None

Example

launch_command('magasin', 'component_name', 'pod-1', 'ls -l') Running: kubectl exec pod-1 --namespace magasin -ti -- ls -l

Note

This function uses the kubectl command-line tool to execute a command in a Kubernetes pod. Make sure you have kubectl installed and configured properly before using this function.

Source code in mag/mag_core/launch.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def launch_command(realm: str, component: str, pod_name: str, command: str = '/bin/bash'):
    """
    Launches a command in a Kubernetes pod.

    Args:
        realm (str): The magasin realm (e.g., magasin).
        component (str): The name of the magasin component.
        pod_name (str): The name of the pod.
        command (str, optional): The command to be executed in the pod. Defaults to '/bin/bash'.

    Returns:
        None

    Raises:
        None

    Example:
        >>> launch_command('magasin', 'component_name', 'pod-1', 'ls -l')
        Running: kubectl exec pod-1 --namespace magasin -ti -- ls -l
        <output of the command>

    Note:
        This function uses the `kubectl` command-line tool to execute a command in a Kubernetes pod.
        Make sure you have `kubectl` installed and configured properly before using this function.
    """
    namespace = get_namespace(component_name=component, realm=realm)
    user_root = ''

    command = f"kubectl exec {pod_name} --namespace {namespace} -ti -- {command}"
    click.echo(f"Running: {command}")
    subprocess.run(command, shell=True)

launch_ui(realm, component, service_name, ports, protocol='http', verbose=False)

Launches the user interface for a given realm, component, and service.

Parameters:

Name Type Description Default
realm str

The realm of the magasin instance.

required
component str

The magasin component (f.i superset, daskhub, drill, ...)

required
service_name str

The name of the kubernetes service to forward.

required
ports str

The ports to forward, using the format "local_port:remote_port".

required
protocol str

The protocol to use (default is "http").

'http'
verbose bool

Whether to display verbose output (default is False).

False

Returns:

Name Type Description
None None

Nothing

Source code in mag/mag_core/launch.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def launch_ui(realm: str, component: str, service_name: str, ports: str, protocol: str = "http", verbose=False) -> None:
    """
    Launches the user interface for a given realm, component, and service.

    Args:
        realm (str): The realm of the magasin instance.
        component (str): The magasin component (f.i superset, daskhub, drill, ...)
        service_name (str): The name of the kubernetes service to forward.
        ports (str): The ports to forward, using the format "local_port:remote_port".
        protocol (str, optional): The protocol to use (default is "http").
        verbose (bool, optional): Whether to display verbose output (default is False).

    Returns:
        None: Nothing
    """    
    forward_port(realm=realm, component=component,
                 service_name=service_name, ports=ports, verbose=verbose)

    localhost_port, _ = split_ports(ports)
    url = f"{protocol}://localhost:{localhost_port}"
    click.echo(f"Open browser at: {url}")
    click.launch(url)
    click.echo("launch ui")

    try:
        # Wait for user to press Ctrl+C
        signal.pause()
    except KeyboardInterrupt:
        # Handle Ctrl+C: terminate the server and clean up
        process.terminate()
        os.waitpid(process.pid, 0)
        click.echo("\nServer terminated. Exiting.")

port_forward_command_arr(realm, component, service_name, ports, verbose=False)

Generate a command array for port forwarding.

This function generates a command array for port forwarding using kubectl command.

Parameters:

Name Type Description Default
realm str

The magasin realm.

required
component str

The component of the command (f.i superset, daskhub, drill,...).

required
service_name str

The Kubernetes service name to forward.

required
ports str

The ports to forward. Follows the format "local_port:remote_port".

required
verbose bool

Whether to include verbose output. Defaults to False.

False

Returns:

Name Type Description
List List

The generated command array.

Source code in mag/mag_core/launch.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def port_forward_command_arr(realm: str, component: str, service_name: str, ports: str, verbose=False) -> List:
    """Generate a command array for port forwarding.

    This function generates a command array for port forwarding using `kubectl` command.

    Args:
        realm (str): The magasin realm.
        component (str): The component of the command (f.i superset, daskhub, drill,...).
        service_name (str): The Kubernetes service name to forward.
        ports (str): The ports to forward. Follows the format "local_port:remote_port".
        verbose (bool, optional): Whether to include verbose output. Defaults to False.

    Returns:
        List: The generated command array.
    """
    namespace = get_namespace(component, realm)
    port_forward_command_arr = [
        "kubectl", "port-forward", "--address=0.0.0.0", "--namespace", namespace, service_name, ports]
    if verbose:
        click.echo("port_forward_command_arr: " +
                   " ".join(port_forward_command_arr))
    return port_forward_command_arr

port_forward_command_str(realm, component, service_name, ports, verbose=False)

Returns a string representation of the port forward command.

Parameters:

Name Type Description Default
realm str

The magasin realm.

required
component str

The component of the command (f.i superset, daskhub, drill,...).

required
service_name str

The Kubernetes service name to forward.

required
ports str

The ports to forward. Follows the format "local_port:remote_port".

required
verbose bool

Whether to include verbose output. Defaults to False.

False

Returns:

Name Type Description
str str

The port forward command as a string.

Example:

port_forward_command_str("magasin", "superset", "superset", "8088:8088")

Source code in mag/mag_core/launch.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def port_forward_command_str(realm: str, component: str, service_name: str, ports: str, verbose=False) -> str:
    """
    Returns a string representation of the port forward command.

    Args:
        realm (str): The magasin realm.
        component (str): The component of the command (f.i superset, daskhub, drill,...).
        service_name (str): The Kubernetes service name to forward.
        ports (str): The ports to forward. Follows the format "local_port:remote_port".
        verbose (bool, optional): Whether to include verbose output. Defaults to False.

    Returns:
        str: The port forward command as a string.

    Example:
    ```
    port_forward_command_str("magasin", "superset", "superset", "8088:8088")
    ```

    """
    return " ".join(port_forward_command_arr(realm=realm, component=component, service_name=service_name, ports=ports, verbose=verbose))

terminate_process(process)

Terminate the given process if it is running.

Parameters: - process: A subprocess.Popen object representing the process to be terminated.

Usage: terminate_process(process)

Notes: - If the process is not running or is already terminated, this function returns without taking any action. - It checks if the process is running (poll() is None) and terminates it using terminate(). It then waits for the process to finish using wait().

Source code in mag/mag_core/launch.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def terminate_process(process):
    """
    Terminate the given process if it is running.

    Parameters:
    - process: A subprocess.Popen object representing the process to be terminated.

    Usage:
    terminate_process(process)

     Notes:
     - If the process is not running or is already terminated, this function returns without taking any action.
     - It checks if the process is running (poll() is None) and terminates it using terminate().
       It then waits for the process to finish using wait().
     """

    if not process:
        return
    if process.poll() is None:
        process.terminate()
        process.wait()

options

Reusable command line options

These are options that can be included in a command and that can be reused in multiple commands

Example:

Add the --realm option. The realm is validated against the allowed format returns errors when is not.

```python from mag.mag_core import options

@cli.group('drill', cls=ClickAliasedGroup, aliases=['dr']) @options.realm def drill(realm): printf(realm)

realm = click.option('-r', '--realm', default='magasin', show_default=True, help='magasin realm', callback=validate_realm_callback) module-attribute

Adds the --realm option.

ports(default)

Adds the --ports option.

Validates the port format.

Parameters:

Name Type Description Default
default str

The default value for the option.

required

Returns:

Type Description

click.Option: The click option object.

Source code in mag/mag_core/options.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def ports(default: str):
    """
    Adds the --ports option.

    Validates the port format.

    Parameters:
        default (str): The default value for the option.

    Returns:
        click.Option: The click option object.

    """
    return click.option('-p', '--ports', 
                        default=default,
                        show_default=True, 
                        help='Redirection ports. Format <localhost_port>:<pod_port>. Example: 8080:8080', 
                        callback=validate_port_callback)

validate_port_callback(ctx, param, value)

Click callback to validate the port format It validates the ports using mag_core.validators.validate_ports

Raises:

Type Description
BadParameter

If port format is not valid

Source code in mag/mag_core/options.py
38
39
40
41
42
43
44
45
46
47
def validate_port_callback(ctx, param, value):
    """Click callback to validate the port format
    It validates the ports using `mag_core.validators.validate_ports`

    Raises:
        click.BadParameter: If port format is not valid
    """
    if not validate_ports(value):
        raise click.BadParameter("The format of the ports is number:number. Where number is between 1 and 65535. Example: 80:80")
    return value

validate_realm_callback(ctx, param, value)

Validates if the realm has a correct value

See: https://click.palletsprojects.com/en/8.1.x/options/#callbacks-for-validation

Raises:

Type Description
BadParameter

If name is not valid

Source code in mag/mag_core/options.py
25
26
27
28
29
30
31
32
33
34
35
def validate_realm_callback(ctx, param, value):
    """Validates if the realm has a correct value

    See: https://click.palletsprojects.com/en/8.1.x/options/#callbacks-for-validation

    Raises:
        click.BadParameter: If name is not valid
    """
    if not validate_realm(value):
        raise click.BadParameter("Realm can only contain letters, numbers and '-'")
    return value

std_aliases

Standard aliases for mag commands.

Sometimes it is difficult to remember if it is add, create or new the command. Aliases provide an

Aliases are also useful to help advanced users to work more efficiently. For example, instead of typing the "mag minio add bucket mybucket" command, you can just type "mag m a b mybucket".

This package defines some standard aliases which can be used consistently across the whole mag command line interface

Example:

from click_aliases import ClickAliasedGroup from mag.mag import cli cli.group('add', cls=ClickAliasedGroup, aliases=std_aliases.add) def add(): pass

This enables all these commands to be the same ```sh mag add mag a mag create mag c mag new mag n

add = ['a', 'create', 'c', 'new', 'n'] module-attribute

add command. Add a new item, create something.

shell = ['sh', 'bash', 'console', 'cmd'] module-attribute

shell command. Command line interfaces

validators

This module contains functions for validating different inputs in a magasin application.

validate_pod_name(name)

Validates if a Kubernetes pod name is valid according to Kubernetes naming conventions.

Parameters: - name (str): The pod name to validate.

Returns: - bool: True if the pod name is valid, False otherwise.

Source code in mag/mag_core/validators.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def validate_pod_name(name: str) -> bool:
    """
    Validates if a Kubernetes pod name is valid according to Kubernetes naming conventions.

    Parameters:
    - name (str): The pod name to validate.

    Returns:
    - bool: True if the pod name is valid, False otherwise.
    """
    if not name:
        return False

    # Pod name must be no more than 253 characters in length
    if len(name) > 253:
        return False

    # Pod name must consist of lower case alphanumeric characters, '-' or '.', and
    # must start and end with an alphanumeric character
    pattern = re.compile(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$')
    return bool(pattern.match(name))

validate_ports(ports)

Validate that the ports in the input string are numbers between 1 and 65535.

Args: - ports (str): Input string with the format "number:number".

Returns: - bool: True if the ports are valid, False otherwise.

Source code in mag/mag_core/validators.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def validate_ports(ports: str) -> bool:
    """
    Validate that the ports in the input string are numbers between 1 and 65535.

    Args:
    - ports (str): Input string with the format "number:number".

    Returns:
    - bool: True if the ports are valid, False otherwise.
    """
    try:
        localhost, pod_port = map(int, ports.split(':'))
        return 1 <= localhost <= 65535 and 1 <= pod_port <= 65535
    except ValueError:
        return False

validate_realm(realm)

Verify if the realm contains only letters, numbers, "-", and "_".

Args: - realm (str): The input realm string.

Returns: - bool: True if the realm contains only valid characters, False otherwise.

Source code in mag/mag_core/validators.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def validate_realm(realm: str) -> bool:
    """
    Verify if the realm contains only letters, numbers, "-", and "_".

    Args:
    - realm (str): The input realm string.

    Returns:
    - bool: True if the realm contains only valid characters, False otherwise.
    """
    # Define a regular expression pattern for valid characters
    pattern = re.compile(r'^[a-z0-9-]+$')

    # Use re.match to check if the entire string matches the pattern
    return bool(pattern.match(realm))