diff --git a/Makefile b/Makefile
index 3816fc6b3..24c56ac1c 100644
--- a/Makefile
+++ b/Makefile
@@ -120,7 +120,6 @@ rollout: backend frontend
undeploy:
helm uninstall --kube-context k3d-$(CLUSTER_NAME) --namespace $(NAMESPACE) $(RELEASE)
kubectl --context k3d-$(CLUSTER_NAME) delete --all deployments -n $(SESSION_NAMESPACE)
- rm -f .provision-guacamole
create-cluster:
type k3d || { echo "K3D is not installed, install k3d and run 'make create-cluster' again"; exit 1; }
@@ -132,7 +131,6 @@ create-cluster:
delete-cluster:
k3d cluster list $(CLUSTER_NAME) 2>&- && k3d cluster delete $(CLUSTER_NAME)
- rm -f .provision-guacamole
wait:
@echo "-----------------------------------------------------------"
@@ -146,8 +144,7 @@ wait:
kubectl wait --for=condition=Ready pods --timeout=5m --context k3d-$(CLUSTER_NAME) -n $(NAMESPACE) -l id=$(RELEASE)-deployment-guacamole-guacamole
kubectl exec --context k3d-$(CLUSTER_NAME) --namespace $(NAMESPACE) $$(kubectl get pod --namespace $(NAMESPACE) -l id=$(RELEASE)-deployment-guacamole-guacamole --no-headers | cut -f1 -d' ') -- /opt/guacamole/bin/initdb.sh --postgres | \
kubectl exec -ti --context k3d-$(CLUSTER_NAME) --namespace $(NAMESPACE) $$(kubectl get pod --namespace $(NAMESPACE) -l id=$(RELEASE)-deployment-guacamole-postgres --no-headers | cut -f1 -d' ') -- psql -U guacamole guacamole && \
- echo "Guacamole database initialized sucessfully."; \
- touch .provision-guacamole
+ echo "Guacamole database initialized sucessfully.";
# Execute with `make -j3 dev`
dev: dev-oauth-mock dev-frontend dev-backend
diff --git a/backend/t4cclient/extensions/modelsources/git/core.py b/backend/t4cclient/extensions/modelsources/git/core.py
new file mode 100644
index 000000000..ead872a46
--- /dev/null
+++ b/backend/t4cclient/extensions/modelsources/git/core.py
@@ -0,0 +1,37 @@
+# Copyright DB Netz AG and the capella-collab-manager contributors
+# SPDX-License-Identifier: Apache-2.0
+
+import collections.abc as cabc
+import logging
+import subprocess
+
+from fastapi import HTTPException
+
+log = logging.getLogger(__name__)
+
+
+def ls_remote(url: str, env: cabc.Mapping[str, str]) -> list[str]:
+ try:
+ proc = subprocess.run(
+ ["git", "ls-remote", url], capture_output=True, check=True, env=env
+ )
+ except subprocess.CalledProcessError as e:
+ log.debug(
+ {
+ "msg": "Exit code 128 during cloning of the repository " + url,
+ "stdout": e.stdout,
+ "stderr": e.stderr,
+ "exitcode": e.returncode,
+ }
+ )
+ if e.returncode == 128:
+ raise HTTPException(
+ status_code=500,
+ detail={
+ "err_code": "no_git_model_credentials",
+ "reason": "There was an error accessing the model. Please ask your project lead for more information. In most cases, the credentials need to be updated.",
+ },
+ )
+ else:
+ raise e
+ return proc.stdout.decode().strip().splitlines()
diff --git a/backend/t4cclient/extensions/modelsources/git/routes.py b/backend/t4cclient/extensions/modelsources/git/routes.py
index 4e074dc0e..81494d994 100644
--- a/backend/t4cclient/extensions/modelsources/git/routes.py
+++ b/backend/t4cclient/extensions/modelsources/git/routes.py
@@ -2,26 +2,30 @@
# SPDX-License-Identifier: Apache-2.0
import base64
+import logging
+import os
import typing as t
-from fastapi import APIRouter, Depends
+from fastapi import APIRouter, Depends, HTTPException
from requests import Session
+from .core import ls_remote
from t4cclient.core.authentication.database import verify_repository_role
from t4cclient.core.authentication.database.git_models import verify_gitmodel_permission
from t4cclient.core.authentication.jwt_bearer import JWTBearer
from t4cclient.core.database import get_db
from t4cclient.core.oauth.responses import AUTHENTICATION_RESPONSES
from t4cclient.extensions.modelsources import git
+from t4cclient.extensions.modelsources.git.crud import get_primary_model_of_repository
from t4cclient.extensions.modelsources.git.models import (
GetRepositoryGitModel,
PatchRepositoryGitModel,
PostGitModel,
RepositoryGitInnerModel,
- RepositoryGitModel,
)
router = APIRouter()
+log = logging.getLogger(__name__)
@router.get(
@@ -105,3 +109,44 @@ def patch_model(
model=RepositoryGitInnerModel(**db_model.__dict__),
)
return None
+
+
+@router.get(
+ "/primary/revisions", tags=["Repositories"], responses=AUTHENTICATION_RESPONSES
+)
+def get_revisions(
+ project: str, db: Session = Depends(get_db), token=Depends(JWTBearer())
+):
+ remote_refs: dict[str, list[str]] = {"branches": [], "tags": []}
+
+ git_model = get_primary_model_of_repository(db, project)
+ if not git_model:
+ raise HTTPException(
+ status_code=500,
+ detail={
+ "err_code": "no_git_model",
+ "reason": "No git model is assigned to your project. Please ask a project lead to assign a git model.",
+ },
+ )
+
+ url = git_model.path
+ log.debug("Fetch revisions of git-model '%s' with url '%s'", git_model.name, url)
+
+ git_env = os.environ.copy()
+ git_env["GIT_USERNAME"] = git_model.username or ""
+ git_env["GIT_PASSWORD"] = git_model.password or ""
+ for ref in ls_remote(url, git_env):
+ (_, ref) = ref.split("\t")
+ if "^" in ref:
+ continue
+ if ref.startswith("refs/heads/"):
+ remote_refs["branches"].append(ref[len("refs/heads/") :])
+ elif ref.startswith("refs/tags/"):
+ remote_refs["tags"].append(ref[len("refs/tags/") :])
+
+ remote_refs["default"] = git_model.revision
+
+ log.debug("Determined branches: %s", remote_refs["branches"])
+ log.debug("Determined tags: %s", remote_refs["tags"])
+ log.debug("Determined default branch: %s", remote_refs["default"])
+ return remote_refs
diff --git a/backend/t4cclient/routes/repositories/__init__.py b/backend/t4cclient/routes/repositories/__init__.py
index 495c58c9a..dccc15999 100644
--- a/backend/t4cclient/routes/repositories/__init__.py
+++ b/backend/t4cclient/routes/repositories/__init__.py
@@ -1,12 +1,13 @@
# Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0
+import collections.abc as cabc
import importlib
import logging
import typing as t
from importlib import metadata
-from fastapi import APIRouter, Depends
+from fastapi import APIRouter, Depends, HTTPException
from requests import Session
import t4cclient.core.services.repositories as repository_service
@@ -21,6 +22,7 @@
from t4cclient.core.database import get_db, repositories
from t4cclient.core.database import users as database_users
from t4cclient.core.oauth.responses import AUTHENTICATION_RESPONSES
+from t4cclient.extensions.modelsources.git.crud import get_primary_model_of_repository
from t4cclient.extensions.modelsources.t4c import connection
from t4cclient.schemas.repositories import (
GetRepositoryUserResponse,
diff --git a/backend/t4cclient/sessions/operators/k8s.py b/backend/t4cclient/sessions/operators/k8s.py
index e6fb6ea9d..6d835e229 100644
--- a/backend/t4cclient/sessions/operators/k8s.py
+++ b/backend/t4cclient/sessions/operators/k8s.py
@@ -128,6 +128,7 @@ def start_readonly_session(
entrypoint: str,
git_username: str,
git_password: str,
+ git_depth: int,
) -> t.Dict[str, t.Any]:
id = self._generate_id()
@@ -140,6 +141,7 @@ def start_readonly_session(
"GIT_URL": git_url,
"GIT_REVISION": git_revision,
"GIT_ENTRYPOINT": entrypoint,
+ "GIT_DEPTH": git_depth,
"RMT_PASSWORD": password,
},
)
diff --git a/backend/t4cclient/sessions/routes.py b/backend/t4cclient/sessions/routes.py
index b7bd9f057..4446de707 100644
--- a/backend/t4cclient/sessions/routes.py
+++ b/backend/t4cclient/sessions/routes.py
@@ -30,6 +30,7 @@
from t4cclient.sessions.operators import OPERATOR
from t4cclient.sessions.schema import (
AdvancedSessionResponse,
+ DepthType,
GetSessionsResponse,
GetSessionUsageResponse,
GuacamoleAuthentication,
@@ -68,7 +69,7 @@ def get_current_sessions(db: Session = Depends(get_db), token=Depends(JWTBearer(
]
]
)
- )
+ ),
)
@@ -78,7 +79,6 @@ def get_current_sessions(db: Session = Depends(get_db), token=Depends(JWTBearer(
def request_session(
body: PostSessionRequest, db: Session = Depends(get_db), token=Depends(JWTBearer())
):
-
rdp_password = generate_password(length=64)
owner = get_username(token)
@@ -139,13 +139,28 @@ def request_session(
"reason": "The Model has no connected Git Model. Please contact a project manager or admininistrator",
},
)
+
+ revision = body.branch or git_model.revision
+ if body.depth == DepthType.LatestCommit:
+ depth = 1
+ elif body.depth == DepthType.CompleteHistory:
+ depth = 0
+ else:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "err_code": "wrong_depth_format",
+ "reason": f"Depth type {depth} is not allowed.",
+ },
+ )
session = OPERATOR.start_readonly_session(
password=rdp_password,
git_url=git_model.path,
- git_revision=git_model.revision,
+ git_revision=revision,
entrypoint=git_model.entrypoint,
git_username=git_model.username,
git_password=git_model.password,
+ git_depth=depth,
)
guacamole_identifier = guacamole.create_connection(
@@ -159,13 +174,17 @@ def request_session(
guacamole_token, guacamole_username, guacamole_identifier
)
+ body_dict = body.dict()
+ del body_dict["branch"]
+ del body_dict["depth"]
+
database_model = DatabaseSession(
guacamole_username=guacamole_username,
guacamole_password=guacamole_password,
rdp_password=rdp_password,
guacamole_connection_id=guacamole_identifier,
owner_name=owner,
- **body.dict(),
+ **body_dict,
**session,
)
response = database.create_session(db=db, session=database_model).__dict__
diff --git a/backend/t4cclient/sessions/schema.py b/backend/t4cclient/sessions/schema.py
index acf3a3b30..07d1632d4 100644
--- a/backend/t4cclient/sessions/schema.py
+++ b/backend/t4cclient/sessions/schema.py
@@ -23,6 +23,11 @@ class WorkspaceType(enum.Enum):
READONLY = "readonly"
+class DepthType(enum.Enum):
+ LatestCommit = "LatestCommit"
+ CompleteHistory = "CompleteHistory"
+
+
class GetSessionsResponse(BaseModel):
id: str
type: WorkspaceType
@@ -58,6 +63,8 @@ class Config:
class PostSessionRequest(BaseModel):
type: WorkspaceType
+ branch: str
+ depth: DepthType
repository: t.Optional[str]
class Config:
diff --git a/capella-dockerimages b/capella-dockerimages
index be0daeeb8..91a056775 160000
--- a/capella-dockerimages
+++ b/capella-dockerimages
@@ -1 +1 @@
-Subproject commit be0daeeb86cbfe6fc08a3f987bccd1f3492d0fb4
+Subproject commit 91a05677505a273c8d52226dcec556a10aefb914
diff --git a/docs/user/docs/sessions/request.md b/docs/user/docs/sessions/request.md
index 773783a34..be329a1d2 100644
--- a/docs/user/docs/sessions/request.md
+++ b/docs/user/docs/sessions/request.md
@@ -1,15 +1,20 @@
Please follow these steps to request a session:
-1. Navigate to the `Workspaces` tab inside the webapplication.
-2. Please select your preferred worspace type.
+1. Navigate to the `Workspaces` tab inside the web application.
+1. Please select your preferred worspace type.
!!! Question "Which type should I use?"
Please have a look at [Session Types](types.md)
-3. Click the `Request session` button.
-4. You should see the state of your request. It can take up to 2 minutes, until we can assign the required resources. In most of the cases this takes under 30 seconds.
-5. You should now see a success message. Please click on `Connect to Session`.
-6. The session should appear in a new tab and Capella starts automatically.
-7. Please follow the instructions (depends on the workflow of your project)
+1. In case of read-only sessions, you have to select a project. Then, two more fields appear where you can choose the revision (i.e. branch or tag) and either the latest commit or complete commit history of the primary model of this project. Instead of choosing a single branch, it is possible to click the checkbox to mark that all branches should be downloaded.
+
+ !!! note ""
+ Choosing all revisions and only the latest commit clones only the latest commits for all branches.
+
+1. Click the `Request session` button.
+1. You should see the state of your request. It can take up to 2 minutes, until the required resources can be assigned. In most of the cases this takes under 30 seconds.
+1. You should now see a success message. Please click on `Connect to Session`.
+1. The session should appear in a new tab and Capella starts automatically.
+1. Please follow the instructions (depends on the workflow of your project)
1. [Git](flows/git.md)
- 2. [TeamForCapella](flows/t4c.md)
+ 1. [TeamForCapella](flows/t4c.md)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 28ee04373..dcd9b4a6d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -28,7 +28,7 @@
"@angular/cli": "~14.1.1",
"@angular/compiler-cli": "~14.1.1",
"@types/jasmine": "~4.0.3",
- "@types/node": "^18.6.3",
+ "@types/node": "^18.6.5",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.1",
@@ -2805,9 +2805,10 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "18.6.3",
- "dev": true,
- "license": "MIT"
+ "version": "18.6.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
+ "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
+ "dev": true
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -12542,7 +12543,9 @@
"dev": true
},
"@types/node": {
- "version": "18.6.3",
+ "version": "18.6.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
+ "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
"dev": true
},
"@types/parse-json": {
diff --git a/frontend/package.json b/frontend/package.json
index c850baad3..43418fe0f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -30,7 +30,7 @@
"@angular/cli": "~14.1.1",
"@angular/compiler-cli": "~14.1.1",
"@types/jasmine": "~4.0.3",
- "@types/node": "^18.6.3",
+ "@types/node": "^18.6.5",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.1",
diff --git a/frontend/src/app/auth/http-interceptor/auth.interceptor.ts b/frontend/src/app/auth/http-interceptor/auth.interceptor.ts
index f2468f352..fffaa221b 100644
--- a/frontend/src/app/auth/http-interceptor/auth.interceptor.ts
+++ b/frontend/src/app/auth/http-interceptor/auth.interceptor.ts
@@ -1,23 +1,22 @@
// Copyright DB Netz AG and the capella-collab-manager contributors
// SPDX-License-Identifier: Apache-2.0
-import { Injectable } from '@angular/core';
import {
- HttpRequest,
- HttpHandler,
HttpEvent,
+ HttpHandler,
HttpInterceptor,
- HttpHeaders,
+ HttpRequest,
} from '@angular/common/http';
-import { Observable, throwError } from 'rxjs';
-import { LocalStorageService } from '../local-storage/local-storage.service';
-import { catchError, first, map, tap, switchMap } from 'rxjs/operators';
+import { Injectable } from '@angular/core';
+import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
+import { Observable, throwError } from 'rxjs';
+import { catchError, first, map, switchMap } from 'rxjs/operators';
import {
AuthService,
RefreshTokenResponse,
} from 'src/app/services/auth/auth.service';
-import { MatSnackBar } from '@angular/material/snack-bar';
+import { LocalStorageService } from '../local-storage/local-storage.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
diff --git a/frontend/src/app/home/request-session/request-session.component.css b/frontend/src/app/home/request-session/request-session.component.css
index 2e34a691b..6f31894cb 100644
--- a/frontend/src/app/home/request-session/request-session.component.css
+++ b/frontend/src/app/home/request-session/request-session.component.css
@@ -3,15 +3,15 @@
* SPDX-License-Identifier: Apache-2.0
*/
-mat-spinner {
+.big-spinner {
margin-top: 70px;
margin-left: auto;
margin-right: auto;
}
-.center {
- margin-left: auto;
- margin-right: auto;
+.small-spinner {
+ zoom: 0.3;
+ margin-bottom: 40px;
}
mat-card {
@@ -51,6 +51,12 @@ mat-card {
margin-bottom: 20px;
}
+.checkbox {
+ position: relative;
+ right: 20px;
+ bottom: 10px;
+}
+
.center {
text-align: center;
}
diff --git a/frontend/src/app/home/request-session/request-session.component.html b/frontend/src/app/home/request-session/request-session.component.html
index 88eb83961..ed996e9b8 100644
--- a/frontend/src/app/home/request-session/request-session.component.html
+++ b/frontend/src/app/home/request-session/request-session.component.html
@@ -62,6 +62,7 @@
+