This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 95b508db6dc53b56adc139aa058cd9866170da36
Author: Lahiru Jayathilake <[email protected]>
AuthorDate: Tue Feb 25 15:34:42 2025 -0500

    CyberFaCES docker file
    
    * initial docker config files for cyberfaces
    
    * docker dependencies
---
 .../deployments/cyberfaces/agent/Dockerfile        |  25 ++
 .../jupyter/deployments/cyberfaces/agent/README.md |  14 +
 .../cyberfaces/agent/build-container.sh            |   6 +
 .../deployments/cyberfaces/agent/docker-agent.sh   |   4 +
 .../jupyter/deployments/cyberfaces/agent/kernel.py | 145 ++++++++
 .../deployments/cyberfaces/jupyterlab/Dockerfile   |  14 +
 .../cyberfaces/jupyterlab/build-jupyter-image.sh   |   1 +
 .../cyberfaces/jupyterlab/labconfig/__init__.py    |   0
 .../jupyterlab/labconfig/airavata_magics.py        | 404 +++++++++++++++++++++
 .../cyberfaces/jupyterlab/labconfig/bootstrap.sh   |   2 +
 .../cyberfaces/jupyterlab/labconfig/device_auth.py |  58 +++
 .../jupyterlab/labconfig/jupyter_lab_config.py     |  25 ++
 .../jupyterlab/run-jupyter-lab-container.sh        |   1 +
 13 files changed, 699 insertions(+)

diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/Dockerfile
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/Dockerfile
new file mode 100644
index 0000000000..3521c00a86
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.8.13-slim
+
+USER root
+
+RUN pip install pandas
+RUN pip install geopandas
+RUN pip install pygeos
+RUN pip install matplotlib
+RUN pip install "numpy<2"
+RUN apt update
+RUN apt install -y gdal-bin
+RUN pip install -U libpysal
+RUN pip install -U esda
+RUN pip3 install contextily
+
+RUN pip install flask jupyter jupyter-client wget jupyter_contrib_nbextensions 
\
+    utm matplotlib_scalebar earthpy patool cdsapi gdown geojson pyarrow pooch 
zarr cython dask fsspec gcsfs \
+    intake intake-esm cmaps cartopy regionmask cdo-api-py progressbar 
googledrivedownloader cartopy s3fs pywbt cfgrib \
+    tensorflow seaborn earthengine-api folium scikit-learn netCDF4 xarray 
statsmodels pmdarima \
+    pygridmet hydrofunctions pynhd missingno geemap numba==0.56.3 scipy \
+    rasterio ipyleaflet bsddb3 scikit-learn
+RUN mkdir -p /opt/jupyter
+RUN python -m venv /opt/jupyter/venv
+ADD airavata-agent-linux /opt/airavata-agent
+ADD kernel.py /opt/jupyter/kernel.py
\ No newline at end of file
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/README.md
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/README.md
new file mode 100644
index 0000000000..bf95186cc5
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/README.md
@@ -0,0 +1,14 @@
+## Setting up Dev Environment
+
+### Agent
+Build the Agent
+```shell
+./build-container.sh
+```
+
+Execute the following command to run an agent locally.
+```shell
+docker run --rm -v $(pwd):/workspace -w /workspace 
cybershuttle/remote-agent-cyberfaces bash -c "./docker-agent.sh"
+```
+
+
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/build-container.sh
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/build-container.sh
new file mode 100755
index 0000000000..d821caca3a
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/build-container.sh
@@ -0,0 +1,6 @@
+cd ../../../../
+env GOOS=linux GOARCH=amd64 go build
+cp airavata-agent jupyter/deployments/cyberfaces/agent/airavata-agent-linux
+cd jupyter/deployments/cyberfaces/agent
+docker build --platform linux/x86_64 -t cybershuttle/remote-agent-cyberfaces .
+docker push cybershuttle/remote-agent-cyberfaces
\ No newline at end of file
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/docker-agent.sh
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/docker-agent.sh
new file mode 100755
index 0000000000..9978af18b6
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/docker-agent.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+/opt/airavata-agent api.gateway.cybershuttle.org:19900 agent2
+
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/kernel.py
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/kernel.py
new file mode 100644
index 0000000000..c9bd281479
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/kernel.py
@@ -0,0 +1,145 @@
+import time
+from jupyter_client import KernelManager
+from flask import Flask, request, jsonify
+import os
+import json
+import re
+
+
+app = Flask(__name__)
+
+km = None
+kc = None
+
+kernel_running = False
+
+ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
+
[email protected]('/start', methods=['GET'])
+def start_kernel():
+
+    global km
+    global kc
+    global kernel_running
+
+    if kernel_running:
+        return "Kernel already running"
+    # Create a new kernel manager
+    km = KernelManager(kernel_name='python3')
+    km.start_kernel()
+
+    # Create a client to interact with the kernel
+    kc = km.client()
+    kc.start_channels()
+
+    # Ensure the client is connected before executing code
+    kc.wait_for_ready()
+    kernel_running = True
+    return "Kernel started"
+
+def strip_ansi_codes(text):
+    return ansi_escape.sub('', text)
+
[email protected]('/execute', methods=['POST'])
+def execute():
+
+    global km
+    global kc
+
+    code = request.json.get('code', '')
+    if not code:
+        return jsonify({'error': 'No code provided'}), 400
+ 
+    kc.execute(code)
+
+    outputs = []
+    execution_noticed = False
+
+    while True:
+        try:
+            msg = kc.get_iopub_msg(timeout=5)
+
+            content = msg.get("content", {})
+            msg_type = msg.get("msg_type", "")
+
+            # When a message with the text stream comes and it's the result of 
our execution
+            if msg_type == "execute_input":
+                execution_noticed = True
+
+            # Handle stdout streams
+            if msg_type == "stream" and content.get("name") == "stdout":
+                outputs.append({
+                    "output_type": "stream",
+                    "name": "stdout",
+                    "text": content.get("text", "")
+                })
+
+            # Handle stderr streams
+            if msg_type == "stream" and content.get("name") == "stderr":
+                outputs.append({
+                    "output_type": "stream",
+                    "name": "stderr",
+                    "text": content.get("text", "")
+                })
+
+            # Handle display data (e.g. plots)
+            if msg_type == "display_data":
+                outputs.append({
+                    "output_type": "display_data",
+                    "data": content.get("data", {}),
+                    "metadata": content.get("metadata", {})
+                })
+
+            # Handle execution results (e.g. return values)
+            if msg_type == "execute_result":
+                outputs.append({
+                    "output_type": "execute_result",
+                    "data": content.get("data", {}),
+                    "metadata": content.get("metadata", {}),
+                    "execution_count": content.get("execution_count", None)
+                })
+
+            # Handle errors
+            if msg_type == "error":
+                # Strip ANSI codes from traceback
+                clean_traceback = [strip_ansi_codes(line) for line in 
content.get("traceback", [])]
+                outputs.append({
+                    "output_type": "error",
+                    "ename": content.get("ename", ""),
+                    "evalue": content.get("evalue", ""),
+                    "traceback": clean_traceback
+                })
+
+            # Check for end of execution
+            if msg_type == "status" and content.get("execution_state") == 
"idle" and execution_noticed:
+                break
+
+        except KeyboardInterrupt:
+            return jsonify({'error': "Execution interrupted by user"}), 500
+        except Exception as e:
+            print(f"Error while getting Jupyter message: {str(e)}")
+
+    response = {
+        "outputs": outputs
+    }
+
+    return jsonify(response), 200
+
[email protected]('/stop', methods=['GET'])
+def stop():
+
+    global km
+    global kc
+    global kernel_running
+
+    if not kernel_running:
+        return "Kernel is not running to shut down"
+    
+    kc.stop_channels()
+    km.shutdown_kernel()
+    kernel_running = False
+    return 'Kernel shutting down...'
+
+
+if __name__ == '__main__':
+    app.run(port=15000)
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/Dockerfile
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/Dockerfile
new file mode 100644
index 0000000000..9dc84c72c7
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/Dockerfile
@@ -0,0 +1,14 @@
+FROM cybershuttle/base-cyberfaces
+
+USER root
+
+COPY labconfig/jupyter_lab_config.py /jupyter_lab_config.py
+COPY labconfig/airavata_magics.py /airavata_magics.py
+COPY labconfig/__init__.py /__init__.py
+COPY labconfig/device_auth.py /device_auth.py
+COPY labconfig/bootstrap.sh /bootstrap.sh
+RUN chmod +x /bootstrap.sh
+
+USER ${NB_UID}
+
+CMD ["start-notebook.sh","--NotebookApp.iopub_data_rate_limit=1e10"]
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/build-jupyter-image.sh
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/build-jupyter-image.sh
new file mode 100755
index 0000000000..234c95876a
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/build-jupyter-image.sh
@@ -0,0 +1 @@
+docker build --platform linux/x86_64 -t cybershuttle/cyberfaces-jupyter-lab .
\ No newline at end of file
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/__init__.py
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/airavata_magics.py
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/airavata_magics.py
new file mode 100644
index 0000000000..b6fd96db2a
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/airavata_magics.py
@@ -0,0 +1,404 @@
+import base64
+import binascii
+import json
+import time
+from pathlib import Path
+from typing import NamedTuple
+import jwt
+import os
+import requests
+from IPython.core.magic import register_cell_magic, register_line_magic
+from IPython.display import HTML, Image, display
+from device_auth import DeviceFlowAuthenticator
+
+
+AgentInfo = NamedTuple('AgentInfo', [
+    ('agentId', str),
+    ('experimentId', str),
+    ('processId', str),
+    ('cluster', str),
+    ('queue', str),
+    ('cpus', int),
+    ('memory', int),
+    ('walltime', int),
+    ('gateway_id', str),
+    ('group', str),
+])
+api_base_url = "https://api.gateway.cybershuttle.org";
+file_server_url = "http://3.142.234.94:8050";
+current_agent : AgentInfo | None = None
+MSG_NOT_INITIALIZED = r"Remote agent not initialized. Please run %init_remote 
cluster=<cluster> cpu=<cpu> memory=<memory mb> queue=<queue> walltime=<walltime 
minutes> group=<group>"
+
+
+def get_access_token() -> str | None:
+    token_from_env = os.getenv('CS_ACCESS_TOKEN')
+    if token_from_env:
+        return token_from_env
+    EXPLICIT_TOKEN_FILE = Path("~").expanduser() / "csagent" / "token" / 
"keys.json"
+    if EXPLICIT_TOKEN_FILE.exists():
+        with open(EXPLICIT_TOKEN_FILE, "r") as f:
+            return json.load(f).get("access_token")
+
+
+def get_agent_status() -> dict | None:
+    if not current_agent:
+        return print(MSG_NOT_INITIALIZED)
+    url = f"{api_base_url}/api/v1/agent/{current_agent.agentId}"
+    response = requests.get(url)
+    if response.status_code == 202:
+        return response.json()
+    return print(f"Got [{response.status_code}] Response: {response.text}")
+
+
+def get_process_id(experiment_id: str, headers) -> str:
+    """
+    Get process id by experiment id
+
+    """
+    url = f"{api_base_url}/api/v1/exp/{experiment_id}/process"
+    process_id = ""
+    while not process_id:
+        response = requests.get(url, headers=headers)
+        if response.status_code == 200:
+            process_id = response.json().get("processId")
+        else:
+            time.sleep(5)
+    return process_id
+
+
+def submit_agent_job(experiment_name, cluster, queue, cpus, memory, walltime, 
access_token, group, gateway_id='default'):
+    global current_agent
+
+    # URL to which the POST request will be sent
+    url = api_base_url + '/api/v1/exp/launch'
+
+    # Data to be sent in the POST request
+    data = {
+        'experimentName': experiment_name,
+        'remoteCluster': cluster,
+        'cpuCount': cpus,
+        'nodeCount': 1,
+        'memory': memory,
+        'wallTime': walltime,
+        'queue': queue,
+        'group': group,
+    }
+
+    # Convert the data to JSON format
+    json_data = json.dumps(data)
+
+    decode = jwt.decode(access_token, options={"verify_signature": False})
+    user_id = decode['preferred_username']
+    claimsMap = {
+        "userName": user_id,
+        "gatewayID": gateway_id
+    }
+
+    # Headers
+    headers = {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + access_token,
+        'X-Claims': json.dumps(claimsMap)
+    }
+
+    # Send the POST request
+    response = requests.post(url, headers=headers, data=json_data)
+
+    # Check if the request was successful
+    if response.status_code == 200:
+        # Parse the JSON response
+        obj = response.json()
+        current_agent = AgentInfo(
+            agentId=obj['agentId'],
+            experimentId=obj['experimentId'],
+            processId=get_process_id(obj['experimentId'], headers=headers),
+            cluster=cluster,
+            queue=queue,
+            cpus=cpus,
+            memory=memory,
+            walltime=walltime,
+            gateway_id=gateway_id,
+            group=group,
+        )
+        print('Agent Initialized:', current_agent)
+    else:
+        print('Failed to send POST request. Status code:', 
response.status_code)
+        print('Response:', response.text)
+
+
+def terminate_agent(access_token, gateway_id='default'):
+    global current_agent
+    if not current_agent:
+        return print(MSG_NOT_INITIALIZED)
+
+    expId = current_agent.experimentId
+    url = api_base_url + '/api/v1/exp/terminate/' + expId
+
+    decode = jwt.decode(access_token, options={"verify_signature": False})
+    user_id = decode['preferred_username']
+    claimsMap = {
+        "userName": user_id,
+        "gatewayID": gateway_id
+    }
+
+    # Headers
+    headers = {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + access_token,
+        'X-Claims': json.dumps(claimsMap)
+    }
+
+    # Send the POST request
+    response = requests.get(url, headers=headers)
+
+    if response.status_code == 200:
+        # Parse the JSON response
+        response_data = response.json()
+        print('Agent terminated:', response_data)
+        current_agent = None
+    else:
+        print('Failed to send termination request. Status code:', 
response.status_code)
+        print('Response:', response.text)
+
+
+@register_cell_magic
+def run_remote(line, cell):
+    global current_agent
+    if not current_agent:
+        return print(MSG_NOT_INITIALIZED)
+
+    url = api_base_url + '/api/v1/agent/executejupyterrequest'
+
+    data = {
+        "sessionId": "session1",
+        "keepAlive": True,
+        "code": cell,
+        "agentId": current_agent.agentId
+    }
+
+    json_data = json.dumps(data)
+    response = requests.post(url, headers={'Content-Type': 
'application/json'}, data=json_data)
+    execution_resp = response.json()
+    execution_id = execution_resp.get("executionId")
+    error = execution_resp.get("error")
+    if error:
+        print("Cell execution failed. Error: " + error)
+    if execution_id:
+        while True:
+            url = api_base_url + "/api/v1/agent/executejupyterresponse/" + 
execution_id
+            response = requests.get(url, headers={'Accept': 
'application/json'})
+            json_response = response.json()
+            if json_response.get('available'):
+                result_str = json_response.get('responseString')
+                try:
+                    result = json.loads(result_str)
+                except json.JSONDecodeError as e:
+                    print(f"Failed to decode JSON response: {e}")
+                    break
+
+                if 'outputs' in result:
+                    for output in result['outputs']:
+                        output_type = output.get('output_type')
+                        if output_type == 'display_data':
+                            data_obj = output.get('data', {})
+                            if 'image/png' in data_obj:
+                                image_data = data_obj['image/png']
+                                try:
+                                    image_bytes = base64.b64decode(image_data)
+                                    display(Image(data=image_bytes, 
format='png'))
+                                except binascii.Error as e:
+                                    print(f"Failed to decode image data: {e}")
+                            # Ignoring any texts in the display data
+                            # if 'text/plain' in data_obj:
+                            #     print(data_obj['text/plain'])
+
+                        elif output_type == 'stream':
+                            stream_name = output.get('name', 'stdout')
+                            stream_text = output.get('text', '')
+                            if stream_name == 'stderr':
+                                error_html = f"""
+                                <div style="
+                                    color: #a71d5d;
+                                    background-color: #fdd;
+                                    border: 1px solid #a71d5d;
+                                    padding: 5px;
+                                    border-radius: 5px;
+                                    font-family: Consolas, 'Courier New', 
monospace;
+                                    white-space: pre-wrap;
+                                ">
+                                    {stream_text}
+                                </div>
+                                """
+                                display(HTML(error_html))
+                            else:
+                                print(stream_text)
+
+                        elif output_type == 'error':
+                            ename = output.get('ename', 'Error')
+                            evalue = output.get('evalue', '')
+                            traceback = output.get('traceback', [])
+
+                            error_html = f"""
+                            <div style="
+                                color: #a71d5d;
+                                background-color: #fdd;
+                                border: 1px solid #a71d5d;
+                                padding: 5px;
+                                border-radius: 5px;
+                                font-family: Consolas, 'Courier New', 
monospace;
+                            ">
+                                <pre><strong>{ename}: {evalue}</strong>
+                            """
+                            for line in traceback:
+                                error_html += f"{line}\n"
+                            error_html += "</pre></div>"
+                            display(HTML(error_html))
+
+                        elif output_type == 'execute_result':
+                            data_obj = output.get('data', {})
+                            if 'text/plain' in data_obj:
+                                print(data_obj['text/plain'])
+                else:
+                    if 'result' in result:
+                        print(result['result'])
+                    elif 'error' in result:
+                        print(result['error']['ename'])
+                        print(result['error']['evalue'])
+                        print(result['error']['traceback'])
+                    elif 'display' in result:
+                        data_obj = result['display'].get('data', {})
+                        if 'image/png' in data_obj:
+                            image_data = data_obj['image/png']
+                            try:
+                                image_bytes = base64.b64decode(image_data)
+                                display(Image(data=image_bytes, format='png'))
+                            except binascii.Error as e:
+                                print(f"Failed to decode image data: {e}")
+                break
+            time.sleep(1)
+
+
+@register_line_magic
+def cs_login(line):
+    try:
+        authenticator = DeviceFlowAuthenticator()
+        authenticator.login()
+    except ValueError as e:
+        print(f"Configuration error: {e}")
+
+
+@register_line_magic
+def init_remote(line):
+    if current_agent:
+        status = get_agent_status()
+        if status:
+            if status['agentUp']:
+                print("An agent is already running. Please terminate it first 
by running %terminate_remote")
+                return
+            else:
+                print("An agent was scheduled. Please terminate it first by 
running %terminate_remote")
+                return
+
+    access_token = get_access_token()
+    pairs = line.split()
+
+    # Initialize variable to store the cluster value
+    cluster_value = None
+    memory_value = None
+    cpu_value = None
+    queue_value = None
+    walltime_value = None
+    group_value = ""
+
+    # Iterate through the pairs to find the cluster value
+    for pair in pairs:
+        if pair.startswith("cluster="):
+            cluster_value = pair.split("=")[1]
+        if pair.startswith("cpu="):
+            cpu_value = pair.split("=")[1]
+        if pair.startswith("memory="):
+            memory_value = pair.split("=")[1]
+        if pair.startswith("queue="):
+            queue_value = pair.split("=")[1]
+        if pair.startswith("walltime="):
+            walltime_value = pair.split("=")[1]
+        if pair.startswith("group="):
+            group_value = pair.split("=")[1]
+
+    submit_agent_job('CS_Agent', cluster_value, queue_value, cpu_value, 
memory_value, walltime_value, access_token, group_value)
+
+
+@register_line_magic
+def status_remote(line):
+    status = get_agent_status()
+    if status:
+        if status['agentUp']:
+            print("Agent", status['agentId'], 'is running')
+        else:
+            print("Agent", status['agentId'], 'is still preparing. Please 
wait')
+
+
+@register_line_magic
+def terminate_remote(line):
+    global current_agent
+    access_token = get_access_token()
+    if current_agent:
+        terminate_agent(access_token)
+
+
+@register_line_magic
+def push_remote(line):
+    if not current_agent:
+        return print(MSG_NOT_INITIALIZED)
+    pairs = line.split()
+    remot_path = None
+    local_path = None
+    for pair in pairs:
+        if pair.startswith("source="):
+            local_path = pair.split("=")[1]
+        if pair.startswith("target="):
+            remot_path = pair.split("=")[1]
+    # validate paths
+    if not remot_path or not local_path:
+        return print("Please provide paths for both source and target")
+    # upload file
+    print(f"Pushing local:{local_path} to remote:{remot_path}")
+    url = 
f"{file_server_url}/upload/live/{current_agent.processId}/{remot_path}"
+    with open(local_path, "rb") as file:
+        files = {"file": file}
+        response = requests.post(url, files=files)
+    print(f"[{response.status_code}] Uploaded local:{local_path} to 
remote:{remot_path}")
+
+
+@register_line_magic
+def pull_remote(line):
+    if not current_agent:
+        return print(MSG_NOT_INITIALIZED)
+    pairs = line.split()
+    remot_path = None
+    local_path = None
+    for pair in pairs:
+        if pair.startswith("source="):
+            remot_path = pair.split("=")[1]
+        if pair.startswith("target="):
+            local_path = pair.split("=")[1]
+    # validate paths
+    if not remot_path or not local_path:
+        return print("Please provide paths for both source and target")
+    # download file
+    print(f"Pulling remote:{remot_path} to local:{local_path}")
+    url = 
f"{file_server_url}/download/live/{current_agent.processId}/{remot_path}"
+    response = requests.get(url)
+    with open(local_path, "wb") as file:
+        file.write(response.content)
+    print(f"[{response.status_code}] Downloaded remote:{remot_path} to 
local:{local_path}")
+
+
+def load_ipython_extension(ipython):
+    ipython.register_magic_function(cs_login)
+    ipython.register_magic_function(init_remote)
+    ipython.register_magic_function(status_remote)
+    ipython.register_magic_function(terminate_remote)
+    ipython.register_magic_function(run_remote)
+    ipython.register_magic_function(push_remote)
+    ipython.register_magic_function(pull_remote)
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/bootstrap.sh
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/bootstrap.sh
new file mode 100644
index 0000000000..754a6e8f52
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/bootstrap.sh
@@ -0,0 +1,2 @@
+cd /home/jupyter-notebook-examples && git pull && cd -
+jupyter lab --config=/jupyter_lab_config.py --ip=0.0.0.0 --port=8888 
--no-browser --allow-root --NotebookApp.token=''
\ No newline at end of file
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/device_auth.py
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/device_auth.py
new file mode 100644
index 0000000000..269085ca69
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/device_auth.py
@@ -0,0 +1,58 @@
+import requests
+import time
+import os
+# Load environment variables from .env file
+
+class DeviceFlowAuthenticator:
+    def __init__(self):
+        self.client_id = "cybershuttle-agent"
+        self.realm = "default"
+        self.auth_server_url = "https://auth.cybershuttle.org";
+
+        if not self.client_id or not self.realm or not self.auth_server_url:
+            raise ValueError("Missing required environment variables for 
client ID, realm, or auth server URL")
+
+        self.device_code = None
+        self.interval = None
+
+    def login(self):
+        # Step 1: Request device and user code
+        auth_device_url = 
f"{self.auth_server_url}/realms/{self.realm}/protocol/openid-connect/auth/device"
+        response = requests.post(auth_device_url, data={"client_id": 
self.client_id, "scope": "openid"})
+
+        if response.status_code != 200:
+            print(f"Error in device authorization request: 
{response.status_code} - {response.text}")
+            return
+
+        data = response.json()
+        self.device_code = data.get("device_code")
+        self.interval = data.get("interval", 5)
+
+        print(f"User code: {data.get('user_code')}")
+        print(f"Please authenticate by visiting: 
{data.get('verification_uri_complete')}")
+
+        # Step 2: Poll for the token
+        self.poll_for_token()
+
+    def poll_for_token(self):
+        token_url = 
f"{self.auth_server_url}/realms/{self.realm}/protocol/openid-connect/token"
+        while True:
+            response = requests.post(token_url, data={
+                "client_id": self.client_id,
+                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
+                "device_code": self.device_code
+            })
+
+            if response.status_code == 200:
+                data = response.json()
+                access_token = data.get("access_token")
+                print(f"Received access token")
+                os.environ['CS_ACCESS_TOKEN'] = access_token
+                break
+            elif response.status_code == 400 and response.json().get("error") 
== "authorization_pending":
+                print("Authorization pending, retrying...")
+            else:
+                print(f"Error in token request: {response.status_code} - 
{response.text}")
+                break
+
+            time.sleep(self.interval)
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/jupyter_lab_config.py
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/jupyter_lab_config.py
new file mode 100644
index 0000000000..a160cd77cb
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/jupyter_lab_config.py
@@ -0,0 +1,25 @@
+import sys
+sys.path.append('/')
+
+c = get_config()
+
+c.InteractiveShellApp.exec_lines = [
+    "import sys"
+    "sys.path.append('/')"
+    "import airavata_magics",
+    "airavata_magics.load_ipython_extension(get_ipython())"
+]
+
+# Set the IP address Jupyter Lab will listen on
+c.ServerApp.ip = '0.0.0.0'
+
+# Set the port Jupyter Lab will listen on
+c.ServerApp.port = 8888
+
+# Don't open the browser by default
+c.ServerApp.open_browser = False
+
+c.FileContentsManager.use_atomic_writing = False
+
+# Allow root access
+c.ServerApp.allow_root = True
\ No newline at end of file
diff --git 
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/run-jupyter-lab-container.sh
 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/run-jupyter-lab-container.sh
new file mode 100755
index 0000000000..81c92419c2
--- /dev/null
+++ 
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/run-jupyter-lab-container.sh
@@ -0,0 +1 @@
+docker run -p 18888:8888 -it cybershuttle/cyberfaces-jupyter-lab
\ No newline at end of file

Reply via email to