diff --git a/app/app.py b/app/app.py index b41705f..a538459 100644 --- a/app/app.py +++ b/app/app.py @@ -16,7 +16,11 @@ import json from models import init_app, db, DataSource, UserDataSourcePermission, User from forms import DataSourceForm -from azure_active_directory import create_aad_group, add_users_to_aad_group +from azure_active_directory import ( + create_aad_group, + add_users_to_aad_group, + create_team_from_group, +) from cluster_manager import launch_vscode_for_user, sanitize_username from requests.exceptions import RequestException from gevent.pywsgi import WSGIServer @@ -36,7 +40,7 @@ app.config["SECRET_KEY"] = secrets["session_secret"] app.config["SESSION_TYPE"] = "filesystem" -app.config['LOGGING_LEVEL'] = 10 +app.config["LOGGING_LEVEL"] = 10 # Initialize database init_app(app) @@ -197,6 +201,25 @@ def create_data_source(): flash( "Data source and associated AAD group created successfully!", "success" ) + + team_creation_response = create_team_from_group( + group_id=aad_group_id, + access_token=session.get("token").get("access_token"), + ) + if team_creation_response: + team_info = team_creation_response.json() + team_id = team_info.get("id") + if team_id: + data_source.team_id = team_id + db.session.commit() + flash( + "Team created and associated with data source successfully!", + "success", + ) + else: + flash("Failed to extract team ID from the response.", "error") + else: # If there was an error + flash("Failed to create the team.", "error") else: flash("Failed to create AAD group.", "error") @@ -438,13 +461,11 @@ def start_vscode(id): # Redirect to a waiting page or directly embed the VS Code interface if it's ready # The implementation of this part can vary based on how you handle the VS Code UI embedding # return render_template("vscode.html") - vscode_url = url_for('vscode_proxy') + vscode_url = url_for("vscode_proxy") return redirect(vscode_url) -@app.route( - "/vscode_proxy", methods=["GET", "POST", "PUT", "DELETE", "PATCH"] -) +@app.route("/vscode_proxy", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) def vscode_proxy(): """ This route acts as a proxy for the VS Code server, forwarding requests and responses. @@ -464,9 +485,7 @@ def vscode_proxy(): app.logger.info(f"Service name: {service_name}") # Construct the URL of the VS Code server for this user. - vscode_url = ( - f"http://vscode-service-dbe0354c6b5f4bdc8a356af8d4ec68ed.dataaccessmanager.svc.cluster.local/" - ) + vscode_url = f"http://vscode-service-dbe0354c6b5f4bdc8a356af8d4ec68ed.dataaccessmanager.svc.cluster.local/" app.logger.info(f"VSCode URL: {vscode_url}") # Check if it's a WebSocket request diff --git a/app/azure_active_directory.py b/app/azure_active_directory.py index cdef77d..5a78155 100644 --- a/app/azure_active_directory.py +++ b/app/azure_active_directory.py @@ -12,13 +12,14 @@ def create_aad_group(group_name, description, user_id, access_token, dry_run=Fal "Content-Type": "application/json", } - # The payload for the request + # The payload for the request, setting 'visibility' to 'Private' to make a private group group_data = { "displayName": group_name, "description": description, "mailEnabled": False, "mailNickname": group_name.replace(" ", "").lower(), - "securityEnabled": True, + "securityEnabled": False, # Unified groups are not security groups + "visibility": "Private", # Setting the group as a private group } # If dry_run is enabled, we skip the actual creation process @@ -64,6 +65,40 @@ def create_aad_group(group_name, description, user_id, access_token, dry_run=Fal return None +def create_team_from_group(group_id, access_token): + url = f"https://graph.microsoft.com/v1.0/groups/{group_id}/team" + + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + } + + team_data = { + "memberSettings": {"allowCreateUpdateChannels": True}, + "messagingSettings": { + "allowUserEditMessages": True, + "allowUserDeleteMessages": True, + }, + "funSettings": {"allowGiphy": True, "giphyContentRating": "Moderate"}, + } + + try: + response = requests.put( + url, headers=headers, json=team_data + ) # Using PUT as per Graph API documentation for creating team from group + response.raise_for_status() + # If the request was successful, get the JSON response + return response.json() + except requests.exceptions.HTTPError as err: + print(f"An HTTP error occurred: {err}") + flash("An error occurred while creating the team.", "error") + return None + except Exception as e: + print(f"An unexpected error occurred: {e}") + flash("An unexpected error occurred while creating the team.", "error") + return None + + def add_user_as_group_admin(group_id, user_id, access_token): # Microsoft Graph API endpoint to add a member to the group url = f"https://graph.microsoft.com/v1.0/groups/{group_id}/members/$ref" diff --git a/app/cluster_manager.py b/app/cluster_manager.py index e74a60f..08e0cb9 100644 --- a/app/cluster_manager.py +++ b/app/cluster_manager.py @@ -53,6 +53,7 @@ def create_service_account(user_id): else: print(f"Failed to check service account {name}: {e}", flush=True) + def deploy_vscode_server(user_id): namespace = "dataaccessmanager" sanitized_user_id = sanitize_username(user_id) @@ -77,12 +78,15 @@ def deploy_vscode_server(user_id): ], ) metadata = client.V1ObjectMeta(name=name, labels={"app": name}) - pod = client.V1Pod(api_version="v1", kind="Pod", metadata=metadata, spec=pod_spec) + pod = client.V1Pod( + api_version="v1", kind="Pod", metadata=metadata, spec=pod_spec + ) api_instance.create_namespaced_pod(namespace, pod) print(f"Deployed pod {name}.", flush=True) else: print(f"Failed to check pod {name}: {e}", flush=True) + def create_service_for_vscode(user_id): namespace = "dataaccessmanager" sanitized_user_id = sanitize_username(user_id) @@ -111,6 +115,7 @@ def create_service_for_vscode(user_id): else: print(f"Failed to check service {name}: {e}", flush=True) + def wait_for_pod_ready(namespace, pod_name): # Create a watch object for Pod events w = watch.Watch() @@ -126,6 +131,7 @@ def wait_for_pod_ready(namespace, pod_name): return False # Default case, though your logic might differ based on how you want to handle timeouts + def wait_for_service_ready(service_url, timeout=300): """ Wait for the service to become ready by sending requests to the service URL. @@ -138,16 +144,17 @@ def wait_for_service_ready(service_url, timeout=300): try: response = requests.get(service_url, timeout=5) if response.status_code == 200: - print("VSCODE Service is ready",flush=True) + print("VSCODE Service is ready", flush=True) # Service is ready return True except requests.RequestException as e: print(f"Request failed: {e}") # Wait for a while before retrying time.sleep(5) - print("VSCODE Service is NOT ready",flush=True) + print("VSCODE Service is NOT ready", flush=True) return False # Timeout reached + def launch_vscode_for_user(user_id): # Step 1: Create a service account for the user create_service_account(user_id) @@ -164,7 +171,9 @@ def launch_vscode_for_user(user_id): namespace = "dataaccessmanager" # if wait_for_pod_ready(namespace, pod_name): - if wait_for_service_ready(service_url="http://vscode-service-dbe0354c6b5f4bdc8a356af8d4ec68ed.dataaccessmanager.svc.cluster.local/"): + if wait_for_service_ready( + service_url="http://vscode-service-dbe0354c6b5f4bdc8a356af8d4ec68ed.dataaccessmanager.svc.cluster.local/" + ): print("VS Code server is ready for use.") else: print("There was a problem starting the VS Code server.") diff --git a/app/models.py b/app/models.py index 533acfd..d5f2434 100644 --- a/app/models.py +++ b/app/models.py @@ -30,6 +30,7 @@ class DataSource(db.Model): ) created_by = db.Column(db.String(255), db.ForeignKey("users.id")) aad_group_id = db.Column(db.String(255), unique=True) + teams_id = db.Column(db.String(255), unique=True) # Relationships permissions = db.relationship(