diff --git a/.github/workflows/docker-git-init.yml b/.github/workflows/docker-git-init.yml
new file mode 100644
index 00000000..27c43677
--- /dev/null
+++ b/.github/workflows/docker-git-init.yml
@@ -0,0 +1,18 @@
+name: Docker Git Init CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Build the Docker image
+ run: docker build -t theia-cloud-git-init:$(date +%s) -f dockerfiles/git-init/Dockerfile .
diff --git a/.github/workflows/docker-landing-page.yml b/.github/workflows/docker-landing-page.yml
index e8cf3ed3..84d944c6 100644
--- a/.github/workflows/docker-landing-page.yml
+++ b/.github/workflows/docker-landing-page.yml
@@ -1,10 +1,10 @@
-name: Docker landing Page CI
+name: Docker Landing Page CI
on:
push:
branches: [ main ]
pull_request:
- branches: [ main, osweek23 ]
+ branches: [ main ]
jobs:
diff --git a/.github/workflows/docker-operator.yml b/.github/workflows/docker-operator.yml
index 134fa667..5e87e91e 100644
--- a/.github/workflows/docker-operator.yml
+++ b/.github/workflows/docker-operator.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
- branches: [ main, osweek23 ]
+ branches: [ main ]
jobs:
diff --git a/.github/workflows/docker-try-now-page.yml b/.github/workflows/docker-try-now-page.yml
index 41816bf6..566f4070 100644
--- a/.github/workflows/docker-try-now-page.yml
+++ b/.github/workflows/docker-try-now-page.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
- branches: [ main, osweek23 ]
+ branches: [ main ]
jobs:
diff --git a/.github/workflows/docker-wondershaper.yml b/.github/workflows/docker-wondershaper.yml
index 8c4ca64f..3d0be9ed 100644
--- a/.github/workflows/docker-wondershaper.yml
+++ b/.github/workflows/docker-wondershaper.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
- branches: [ main, osweek23 ]
+ branches: [ main ]
jobs:
diff --git a/.github/workflows/service-ci.yml b/.github/workflows/service-ci.yml
index 6874f4f7..4867f362 100644
--- a/.github/workflows/service-ci.yml
+++ b/.github/workflows/service-ci.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
- branches: [ main, osweek23 ]
+ branches: [ main ]
jobs:
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index cad1a218..91ec209a 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,5 +1,6 @@
{
"recommendations": [
- "hashicorp.terraform"
+ "hashicorp.terraform",
+ "ms-python.python"
]
-}
\ No newline at end of file
+}
diff --git a/doc/docs/Building-Internal.md b/doc/docs/Building-Internal.md
index b0384e57..857922bc 100644
--- a/doc/docs/Building-Internal.md
+++ b/doc/docs/Building-Internal.md
@@ -61,3 +61,10 @@ Build and push the operator with:
docker build --no-cache -t theiacloud/theia-cloud-operator:latest -f dockerfiles/operator/Dockerfile .
docker push theiacloud/theia-cloud-operator:latest
```
+
+Build and push the git-init container:
+
+```bash
+docker build -t theiacloud/theia-cloud-git-init:latest -f dockerfiles/git-init/Dockerfile .
+docker push theiacloud/theia-cloud-git-init:latest
+```
diff --git a/doc/docs/openapi.json b/doc/docs/openapi.json
index da4f4057..09088c09 100644
--- a/doc/docs/openapi.json
+++ b/doc/docs/openapi.json
@@ -2,7 +2,7 @@
"openapi" : "3.0.3",
"info" : {
"title" : "Theia.cloud API",
- "version" : "0.8.0"
+ "version" : "0.8.1"
},
"paths" : {
"/service" : {
@@ -144,6 +144,7 @@
"description" : "Not Allowed"
}
},
+ "deprecated" : true,
"security" : [ {
"SecurityScheme" : [ ]
} ]
@@ -422,6 +423,40 @@
}
}
},
+ "GitInit" : {
+ "description" : "Holds information used to initialize a Workspace with a clone of a Git repository.",
+ "required" : [ "repository", "checkout" ],
+ "type" : "object",
+ "properties" : {
+ "repository" : {
+ "description" : "The Git repository URL.",
+ "type" : "string"
+ },
+ "checkout" : {
+ "description" : "The branch, commit-id, or tag name to checkout.",
+ "type" : "string",
+ "example" : "main, bd402d6, tags/1.0.0"
+ },
+ "authInformation" : {
+ "description" : "Key for the required auth information, if the repository is not public.",
+ "type" : "string"
+ }
+ }
+ },
+ "InitOperation" : {
+ "type" : "object",
+ "properties" : {
+ "id" : {
+ "type" : "string"
+ },
+ "arguments" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ }
+ }
+ },
"LaunchRequest" : {
"description" : "A request to launch a new session.",
"required" : [ "appId", "user" ],
@@ -462,6 +497,13 @@
}, {
"description" : "Environment variables"
} ]
+ },
+ "gitInit" : {
+ "allOf" : [ {
+ "$ref" : "#/components/schemas/GitInit"
+ }, {
+ "description" : "Git Init information"
+ } ]
}
}
},
@@ -589,6 +631,12 @@
"items" : {
"type" : "string"
}
+ },
+ "initOperations" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/components/schemas/InitOperation"
+ }
}
}
},
@@ -624,6 +672,13 @@
}, {
"description" : "Environment variables"
} ]
+ },
+ "gitInit" : {
+ "allOf" : [ {
+ "$ref" : "#/components/schemas/GitInit"
+ }, {
+ "description" : "Git Init information"
+ } ]
}
}
},
@@ -737,6 +792,9 @@
"description" : "Authentication",
"flows" : {
"implicit" : {
+ "authorizationUrl" : "http://localhost:34159/realms/quarkus/protocol/openid-connect/auth",
+ "tokenUrl" : "http://localhost:34159/realms/quarkus/protocol/openid-connect/token/introspect",
+ "refreshUrl" : "http://localhost:34159/realms/quarkus/protocol/openid-connect/token"
}
}
}
diff --git a/dockerfiles/git-init/Dockerfile b/dockerfiles/git-init/Dockerfile
new file mode 100644
index 00000000..fae57066
--- /dev/null
+++ b/dockerfiles/git-init/Dockerfile
@@ -0,0 +1,19 @@
+FROM debian:11-slim
+
+RUN apt update && \
+ apt install python3 git -y && \
+ apt clean && \
+ mkdir /user && \
+ chmod 777 /user && \
+ chmod 777 /etc/passwd
+
+WORKDIR /tmp
+COPY python/git-init/entrypoint.sh .
+COPY python/git-init/ssh-keyscan.sh .
+COPY python/git-init/git-init.py .
+COPY python/git-init/git-askpw.py .
+
+ENV GIT_ASKPASS=/tmp/git-askpw.py
+
+ENTRYPOINT [ "/tmp/entrypoint.sh" ]
+CMD ["-h"]
\ No newline at end of file
diff --git a/java/common/org.eclipse.theia.cloud.common/.settings/org.eclipse.jdt.ui.prefs b/java/common/org.eclipse.theia.cloud.common/.settings/org.eclipse.jdt.ui.prefs
index 606155f8..766a660b 100644
--- a/java/common/org.eclipse.theia.cloud.common/.settings/org.eclipse.jdt.ui.prefs
+++ b/java/common/org.eclipse.theia.cloud.common/.settings/org.eclipse.jdt.ui.prefs
@@ -3,7 +3,7 @@ editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=org.eclipse.jdt.ui.default.sun_profile
formatter_settings_version=21
org.eclipse.jdt.ui.javadoc=false
-org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n *//********************************************************************************\n * Copyright (C) 2022 EclipseSource and others.\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License v. 2.0 which is available at\n * http\://www.eclipse.org/legal/epl-2.0.\n *\n * This Source Code may also be made available under the following Secondary\n * Licenses when the conditions for such availability set forth in the Eclipse\n * Public License v. 2.0 are satisfied\: GNU General Public License, version 2\n * with the GNU Classpath Exception which is available at\n * https\://www.gnu.org/software/classpath/license.html.\n *\n * SPDX-License-Identifier\: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0\n ********************************************************************************/\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n *//********************************************************************************\n * Copyright (C) 2023 EclipseSource and others.\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License v. 2.0 which is available at\n * http\://www.eclipse.org/legal/epl-2.0.\n *\n * This Source Code may also be made available under the following Secondary\n * Licenses when the conditions for such availability set forth in the Eclipse\n * Public License v. 2.0 are satisfied\: GNU General Public License, version 2\n * with the GNU Classpath Exception which is available at\n * https\://www.gnu.org/software/classpath/license.html.\n *\n * SPDX-License-Identifier\: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0\n ********************************************************************************/\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
sp_cleanup.add_all=false
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
diff --git a/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/Session.java b/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/Session.java
index 7537817f..2049b64b 100644
--- a/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/Session.java
+++ b/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/Session.java
@@ -25,14 +25,14 @@
import io.fabric8.kubernetes.model.annotation.Singular;
import io.fabric8.kubernetes.model.annotation.Version;
-@Version("v6beta")
+@Version("v7beta")
@Group("theia.cloud")
@Singular("session")
@Plural("sessions")
public class Session extends CustomResource implements Namespaced {
private static final long serialVersionUID = 4518092300237069237L;
- public static final String API = "theia.cloud/v6beta";
+ public static final String API = "theia.cloud/v7beta";
public static final String KIND = "Session";
public static final String CRD_NAME = "sessions.theia.cloud";
diff --git a/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/SessionSpec.java b/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/SessionSpec.java
index 7e5cadf7..439ba524 100644
--- a/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/SessionSpec.java
+++ b/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/k8s/resource/SessionSpec.java
@@ -54,13 +54,16 @@ public class SessionSpec implements UserScopedSpec {
@JsonProperty("envVars")
private Map envVars;
-
+
@JsonProperty("envVarsFromConfigMaps")
private List envVarsFromConfigMaps;
@JsonProperty("envVarsFromSecrets")
private List envVarsFromSecrets;
+ @JsonProperty("initOperations")
+ private List initOperations;
+
public SessionSpec() {
}
@@ -68,33 +71,35 @@ public SessionSpec(String name, String appDefinition, String user) {
this(name, appDefinition, user, null);
}
+ public SessionSpec(String name, String appDefinition, String user, Map envVars,
+ List envVarsFromConfigMaps, List envVarsFromSecrets) {
+ this(name, appDefinition, user, null, Map.of(), List.of(), List.of(), List.of());
+ }
+
public SessionSpec(String name, String appDefinition, String user, String workspace) {
- this(name, appDefinition, user, workspace, Map.of(), List.of(), List.of());
+ this(name, appDefinition, user, workspace, Map.of(), List.of(), List.of(), List.of());
}
- public SessionSpec(
- String name, String appDefinition, String user, String workspace, Map envVars
- ) {
- this(name, appDefinition, user, workspace, envVars, List.of(), List.of());
+
+ public SessionSpec(String name, String appDefinition, String user, String workspace, Map envVars,
+ List envVarsFromConfigMaps, List envVarsFromSecrets) {
+ this(name, appDefinition, user, workspace, envVars, envVarsFromConfigMaps, envVarsFromSecrets, List.of());
}
- public SessionSpec(
- String name, String appDefinition, String user, String workspace,
- Map envVars, List envVarsFromConfigMaps
- ) {
- this(name, appDefinition, user, workspace, envVars, envVarsFromConfigMaps, List.of());
+ public SessionSpec(String name, String appDefinition, String user, String workspace,
+ List initOperations) {
+ this(name, appDefinition, user, workspace, Map.of(), List.of(), List.of(), initOperations);
}
- public SessionSpec(
- String name, String appDefinition, String user, String workspace,
- Map envVars, List envVarsFromConfigMaps, List envVarsFromSecrets
- ) {
- this.name = name;
- this.appDefinition = appDefinition;
- this.user = user;
- this.workspace = workspace;
- this.envVars = envVars;
- this.envVarsFromConfigMaps = envVarsFromConfigMaps;
- this.envVarsFromSecrets = envVarsFromSecrets;
+ public SessionSpec(String name, String appDefinition, String user, String workspace, Map envVars,
+ List envVarsFromConfigMaps, List envVarsFromSecrets, List initOperations) {
+ this.name = name;
+ this.appDefinition = appDefinition;
+ this.user = user;
+ this.workspace = workspace;
+ this.envVars = envVars;
+ this.envVarsFromConfigMaps = envVarsFromConfigMaps;
+ this.envVarsFromSecrets = envVarsFromSecrets;
+ this.initOperations = initOperations;
}
public String getName() {
@@ -163,14 +168,19 @@ public String getWorkspace() {
}
public Map getEnvVars() {
- return envVars;
+ return envVars;
}
public List getEnvVarsFromConfigMaps() {
- return envVarsFromConfigMaps;
+ return envVarsFromConfigMaps;
}
+
public List getEnvVarsFromSecrets() {
- return envVarsFromSecrets;
+ return envVarsFromSecrets;
+ }
+
+ public List getInitOperations() {
+ return initOperations;
}
@JsonIgnore
@@ -183,9 +193,17 @@ public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((appDefinition == null) ? 0 : appDefinition.hashCode());
+ result = prime * result + ((envVars == null) ? 0 : envVars.hashCode());
+ result = prime * result + ((envVarsFromConfigMaps == null) ? 0 : envVarsFromConfigMaps.hashCode());
+ result = prime * result + ((envVarsFromSecrets == null) ? 0 : envVarsFromSecrets.hashCode());
+ result = prime * result + ((error == null) ? 0 : error.hashCode());
+ result = prime * result + ((initOperations == null) ? 0 : initOperations.hashCode());
+ result = prime * result + (int) (lastActivity ^ (lastActivity >>> 32));
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((sessionSecret == null) ? 0 : sessionSecret.hashCode());
+ result = prime * result + ((url == null) ? 0 : url.hashCode());
result = prime * result + ((user == null) ? 0 : user.hashCode());
result = prime * result + ((workspace == null) ? 0 : workspace.hashCode());
- result = prime * result + ((sessionSecret == null) ? 0 : sessionSecret.hashCode());
return result;
}
@@ -203,6 +221,48 @@ public boolean equals(Object obj) {
return false;
} else if (!appDefinition.equals(other.appDefinition))
return false;
+ if (envVars == null) {
+ if (other.envVars != null)
+ return false;
+ } else if (!envVars.equals(other.envVars))
+ return false;
+ if (envVarsFromConfigMaps == null) {
+ if (other.envVarsFromConfigMaps != null)
+ return false;
+ } else if (!envVarsFromConfigMaps.equals(other.envVarsFromConfigMaps))
+ return false;
+ if (envVarsFromSecrets == null) {
+ if (other.envVarsFromSecrets != null)
+ return false;
+ } else if (!envVarsFromSecrets.equals(other.envVarsFromSecrets))
+ return false;
+ if (error == null) {
+ if (other.error != null)
+ return false;
+ } else if (!error.equals(other.error))
+ return false;
+ if (initOperations == null) {
+ if (other.initOperations != null)
+ return false;
+ } else if (!initOperations.equals(other.initOperations))
+ return false;
+ if (lastActivity != other.lastActivity)
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (sessionSecret == null) {
+ if (other.sessionSecret != null)
+ return false;
+ } else if (!sessionSecret.equals(other.sessionSecret))
+ return false;
+ if (url == null) {
+ if (other.url != null)
+ return false;
+ } else if (!url.equals(other.url))
+ return false;
if (user == null) {
if (other.user != null)
return false;
@@ -213,37 +273,81 @@ public boolean equals(Object obj) {
return false;
} else if (!workspace.equals(other.workspace))
return false;
- if (sessionSecret == null) {
- if (other.sessionSecret != null)
- return false;
- } else if (!sessionSecret.equals(other.sessionSecret))
- return false;
- if (envVars == null) {
- if (other.envVars != null)
- return false;
- } else if (!envVars.equals(other.envVars))
- return false;
- if (envVarsFromConfigMaps == null) {
- if (other.envVarsFromConfigMaps != null)
- return false;
- } else if (!envVarsFromConfigMaps.equals(other.envVarsFromConfigMaps))
- return false;
- if (envVarsFromSecrets == null) {
- if (other.envVarsFromSecrets != null)
- return false;
- } else if (!envVarsFromSecrets.equals(other.envVarsFromSecrets))
- return false;
return true;
}
@Override
public String toString() {
return "SessionSpec [name=" + name + ", appDefinition=" + appDefinition + ", user=" + user + ", url=" + url
- + ", error=" + error + ", workspace=" + workspace + ", lastActivity=" + lastActivity + "]";
+ + ", error=" + error + ", workspace=" + workspace + ", lastActivity=" + lastActivity + ", envVars="
+ + envVars + ", envVarsFromConfigMaps=" + envVarsFromConfigMaps + ", envVarsFromSecrets="
+ + envVarsFromSecrets + ", initOperations=" + initOperations + "]";
}
public static boolean isEphemeral(String workspace) {
return workspace == null || workspace.isBlank();
}
+ public static class InitOperation {
+
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("arguments")
+ private List arguments;
+
+ public InitOperation() {
+ }
+
+ public InitOperation(String id, List arguments) {
+ this.id = id;
+ this.arguments = arguments;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((arguments == null) ? 0 : arguments.hashCode());
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ InitOperation other = (InitOperation) obj;
+ if (arguments == null) {
+ if (other.arguments != null)
+ return false;
+ } else if (!arguments.equals(other.arguments))
+ return false;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "InitOperation [id=" + id + ", arguments=" + arguments + "]";
+ }
+
+ }
+
}
diff --git a/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/util/TheiaCloudError.java b/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/util/TheiaCloudError.java
index 91ada640..23c3e8a2 100644
--- a/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/util/TheiaCloudError.java
+++ b/java/common/org.eclipse.theia.cloud.common/src/main/java/org/eclipse/theia/cloud/common/util/TheiaCloudError.java
@@ -23,6 +23,8 @@ public class TheiaCloudError {
// client errors: 47x
public static final TheiaCloudError INVALID_APP_ID = new TheiaCloudError(470, "Invalid application id.");
public static final TheiaCloudError INVALID_WORKSPACE_NAME = new TheiaCloudError(471, "Invalid workspace name.");
+ public static final TheiaCloudError INVALID_GIT_INIT_CONFIGURATION = new TheiaCloudError(472,
+ "Invalid git init configuration");
public static final TheiaCloudError INVALID_APP_DEFINITION_NAME = new TheiaCloudError(473,
"Invalid app definition name.");
public static final TheiaCloudError INVALID_SESSION_NAME = new TheiaCloudError(474, "Invalid session name.");
diff --git a/java/operator/org.eclipse.theia.cloud.operator/.settings/org.eclipse.jdt.ui.prefs b/java/operator/org.eclipse.theia.cloud.operator/.settings/org.eclipse.jdt.ui.prefs
index 606155f8..766a660b 100644
--- a/java/operator/org.eclipse.theia.cloud.operator/.settings/org.eclipse.jdt.ui.prefs
+++ b/java/operator/org.eclipse.theia.cloud.operator/.settings/org.eclipse.jdt.ui.prefs
@@ -3,7 +3,7 @@ editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=org.eclipse.jdt.ui.default.sun_profile
formatter_settings_version=21
org.eclipse.jdt.ui.javadoc=false
-org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n *//********************************************************************************\n * Copyright (C) 2022 EclipseSource and others.\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License v. 2.0 which is available at\n * http\://www.eclipse.org/legal/epl-2.0.\n *\n * This Source Code may also be made available under the following Secondary\n * Licenses when the conditions for such availability set forth in the Eclipse\n * Public License v. 2.0 are satisfied\: GNU General Public License, version 2\n * with the GNU Classpath Exception which is available at\n * https\://www.gnu.org/software/classpath/license.html.\n *\n * SPDX-License-Identifier\: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0\n ********************************************************************************/\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n *//********************************************************************************\n * Copyright (C) 2023 EclipseSource and others.\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License v. 2.0 which is available at\n * http\://www.eclipse.org/legal/epl-2.0.\n *\n * This Source Code may also be made available under the following Secondary\n * Licenses when the conditions for such availability set forth in the Eclipse\n * Public License v. 2.0 are satisfied\: GNU General Public License, version 2\n * with the GNU Classpath Exception which is available at\n * https\://www.gnu.org/software/classpath/license.html.\n *\n * SPDX-License-Identifier\: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0\n ********************************************************************************/\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
sp_cleanup.add_all=false
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/di/AbstractTheiaCloudOperatorModule.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/di/AbstractTheiaCloudOperatorModule.java
index 779f2988..96a06973 100644
--- a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/di/AbstractTheiaCloudOperatorModule.java
+++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/di/AbstractTheiaCloudOperatorModule.java
@@ -30,6 +30,7 @@
import org.eclipse.theia.cloud.operator.handler.BandwidthLimiter;
import org.eclipse.theia.cloud.operator.handler.DeploymentTemplateReplacements;
import org.eclipse.theia.cloud.operator.handler.IngressPathProvider;
+import org.eclipse.theia.cloud.operator.handler.InitOperationHandler;
import org.eclipse.theia.cloud.operator.handler.PersistentVolumeCreator;
import org.eclipse.theia.cloud.operator.handler.PersistentVolumeTemplateReplacements;
import org.eclipse.theia.cloud.operator.handler.SessionHandler;
@@ -39,6 +40,7 @@
import org.eclipse.theia.cloud.operator.handler.impl.DefaultDeploymentTemplateReplacements;
import org.eclipse.theia.cloud.operator.handler.impl.DefaultPersistentVolumeCreator;
import org.eclipse.theia.cloud.operator.handler.impl.DefaultPersistentVolumeTemplateReplacements;
+import org.eclipse.theia.cloud.operator.handler.impl.GitInitOperationHandler;
import org.eclipse.theia.cloud.operator.handler.impl.IngressPathProviderImpl;
import org.eclipse.theia.cloud.operator.monitor.MonitorActivityTracker;
import org.eclipse.theia.cloud.operator.monitor.MonitorActivityTrackerImpl;
@@ -71,6 +73,7 @@ protected void configure() {
bind(MonitorMessagingService.class).to(bindMonitorMessagingService()).in(Singleton.class);
configure(MultiBinding.create(TimeoutStrategy.class), this::configureTimeoutStrategies);
+ configure(MultiBinding.create(InitOperationHandler.class), this::configureInitOperationHandlers);
}
protected void configure(final MultiBinding binding, final Consumer> configurator) {
@@ -120,6 +123,10 @@ protected void configureTimeoutStrategies(final MultiBinding bi
binding.add(TimeoutStrategy.FixedTime.class);
}
+ protected void configureInitOperationHandlers(final MultiBinding binding) {
+ binding.add(GitInitOperationHandler.class);
+ }
+
@Provides
@Singleton
protected NamespacedKubernetesClient provideKubernetesClient() {
diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/InitOperationHandler.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/InitOperationHandler.java
new file mode 100644
index 00000000..79d94691
--- /dev/null
+++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/InitOperationHandler.java
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (C) 2023 EclipseSource and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+package org.eclipse.theia.cloud.operator.handler;
+
+import java.util.List;
+
+import org.eclipse.theia.cloud.common.k8s.client.TheiaCloudClient;
+import org.eclipse.theia.cloud.common.k8s.resource.AppDefinition;
+import org.eclipse.theia.cloud.common.k8s.resource.Session;
+
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+
+public interface InitOperationHandler {
+
+ static final String THEIA_CLOUD_INIT_LABEL = "theiaCloudInit";
+ static final String THEIA_CLOUD_USER_LABEL = "theiaCloudUser";
+
+ String operationId();
+
+ void handleInitOperation(String correlationId, TheiaCloudClient client, Deployment deployment,
+ AppDefinition appDefinition, Session session, List args);
+
+}
diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/AddedHandlerUtil.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/AddedHandlerUtil.java
index be274ad5..3b46ac52 100644
--- a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/AddedHandlerUtil.java
+++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/AddedHandlerUtil.java
@@ -26,6 +26,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -47,10 +48,17 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.eclipse.theia.cloud.common.k8s.client.TheiaCloudClient;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinition;
+import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionSpec;
import org.eclipse.theia.cloud.common.k8s.resource.Session;
+import org.eclipse.theia.cloud.common.k8s.resource.SessionSpec.InitOperation;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpecResourceList;
+import org.eclipse.theia.cloud.common.k8s.resource.Workspace;
import org.eclipse.theia.cloud.common.util.LogMessageUtil;
+import org.eclipse.theia.cloud.common.util.WorkspaceUtil;
+import org.eclipse.theia.cloud.operator.handler.InitOperationHandler;
+import org.eclipse.theia.cloud.operator.handler.util.TheiaCloudPersistentVolumeUtil;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapEnvSource;
@@ -58,9 +66,12 @@
import io.fabric8.kubernetes.api.model.EnvFromSource;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.LocalObjectReference;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import io.fabric8.kubernetes.api.model.SecretEnvSource;
+import io.fabric8.kubernetes.api.model.Volume;
+import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.NamespacedKubernetesClient;
@@ -70,6 +81,8 @@ public final class AddedHandlerUtil {
private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
+ public static final String USER_DATA = "user-data";
+
public static final String TEMPLATE_SERVICE_YAML = "/templateService.yaml";
public static final String TEMPLATE_SERVICE_WITHOUT_AOUTH2_PROXY_YAML = "/templateServiceWithoutOAuthProxy.yaml";
public static final String TEMPLATE_CONFIGMAP_EMAILS_YAML = "/templateConfigmapEmails.yaml";
@@ -329,4 +342,61 @@ private static Optional findContainerIdxInDeployment(Deployment deploym
return Optional.empty();
}
+ public static void handleInitOperations(String correlationId, TheiaCloudClient client, Deployment deployment,
+ AppDefinition appDefinition, Session session, Set initOperationHandlers) {
+ List initOperations = session.getSpec().getInitOperations();
+ if (initOperations == null) {
+ return;
+ }
+ for (InitOperation initOperation : initOperations) {
+ Optional handler = initOperationHandlers.stream()
+ .filter(h -> h.operationId().equalsIgnoreCase(initOperation.getId())).findAny();
+ if (handler.isEmpty()) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId, MessageFormat
+ .format("No Init Handler found for operation with id {0}.", initOperation.getId())));
+ continue;
+ }
+ handler.get().handleInitOperation(correlationId, client, deployment, appDefinition, session,
+ initOperation.getArguments());
+ LOGGER.info(formatLogMessage(correlationId,
+ MessageFormat.format("Added init container with id {0} to deployment.", initOperation.getId())));
+ }
+ }
+
+ public static Volume createUserDataVolume(String pvcName) {
+ Volume volume = new Volume();
+ volume.setName(USER_DATA);
+ PersistentVolumeClaimVolumeSource persistentVolumeClaim = new PersistentVolumeClaimVolumeSource();
+ volume.setPersistentVolumeClaim(persistentVolumeClaim);
+ persistentVolumeClaim.setClaimName(pvcName);
+ return volume;
+ }
+
+ public static VolumeMount createUserDataVolumeMount(AppDefinitionSpec appDefinition) {
+ VolumeMount volumeMount = new VolumeMount();
+ volumeMount.setName(AddedHandlerUtil.USER_DATA);
+ volumeMount.setMountPath(TheiaCloudPersistentVolumeUtil.getMountPath(appDefinition));
+ return volumeMount;
+ }
+
+ public static Optional getStorageName(TheiaCloudClient client, Session session, String correlationId) {
+ if (session.getSpec().isEphemeral()) {
+ return Optional.empty();
+ }
+ Optional workspace = client.workspaces().get(session.getSpec().getWorkspace());
+ if (!workspace.isPresent()) {
+ LOGGER.info(formatLogMessage(correlationId, "No workspace with name " + session.getSpec().getWorkspace()
+ + " found for session " + session.getSpec().getName(), correlationId));
+ return Optional.empty();
+
+ }
+ String storageName = WorkspaceUtil.getStorageName(workspace.get());
+ if (!client.persistentVolumeClaims().has(storageName)) {
+ LOGGER.info(formatLogMessage(correlationId,
+ "No storage found for started session, will use ephemeral storage instead", correlationId));
+ return Optional.empty();
+ }
+ return Optional.of(storageName);
+ }
+
}
diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/GitInitOperationHandler.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/GitInitOperationHandler.java
new file mode 100644
index 00000000..29b968a4
--- /dev/null
+++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/GitInitOperationHandler.java
@@ -0,0 +1,282 @@
+/********************************************************************************
+ * Copyright (C) 2023 EclipseSource and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+package org.eclipse.theia.cloud.operator.handler.impl;
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.eclipse.theia.cloud.common.k8s.client.TheiaCloudClient;
+import org.eclipse.theia.cloud.common.k8s.resource.AppDefinition;
+import org.eclipse.theia.cloud.common.k8s.resource.Session;
+import org.eclipse.theia.cloud.common.util.LogMessageUtil;
+import org.eclipse.theia.cloud.operator.handler.InitOperationHandler;
+import org.eclipse.theia.cloud.operator.handler.util.TheiaCloudPersistentVolumeUtil;
+
+import io.fabric8.kubernetes.api.model.Container;
+import io.fabric8.kubernetes.api.model.EnvVar;
+import io.fabric8.kubernetes.api.model.EnvVarSource;
+import io.fabric8.kubernetes.api.model.KeyToPath;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretKeySelector;
+import io.fabric8.kubernetes.api.model.SecretVolumeSource;
+import io.fabric8.kubernetes.api.model.SecurityContext;
+import io.fabric8.kubernetes.api.model.Volume;
+import io.fabric8.kubernetes.api.model.VolumeMount;
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+
+public class GitInitOperationHandler implements InitOperationHandler {
+
+ protected static final String ETC_THEIA_CLOUD_SSH = "/etc/theia-cloud-ssh";
+ protected static final String ID_THEIACLOUD = "id_theiacloud";
+ protected static final String SSH_PRIVATEKEY = "ssh-privatekey";
+ protected static final String SSH_KEY = "ssh-key";
+ protected static final String PASSWORD = "password";
+ protected static final String USERNAME = "username";
+ protected static final String GIT_PROMPT1 = "GIT_PROMPT1";
+ protected static final String GIT_PROMPT2 = "GIT_PROMPT2";
+ protected static final String KUBERNETES_IO_SSH_AUTH = "kubernetes.io/ssh-auth";
+ protected static final String KUBERNETES_IO_BASIC_AUTH = "kubernetes.io/basic-auth";
+ protected static final String HTTPS = "https://";
+ protected static final String HTTP = "http://";
+ protected static final String IMAGE_ENV_KEY = "GIT_INIT_OPERATION_IMAGE";
+ protected static final String DEFAULT_IMAGE = "theiacloud/theia-cloud-git-init:latest";
+ protected static final String ID = "git";
+ protected static final String INIT_CONTAINER_NAME = "git-init";
+
+ private static final Logger LOGGER = LogManager.getLogger(GitInitOperationHandler.class);
+
+ @Override
+ public String operationId() {
+ return ID;
+ }
+
+ @Override
+ public void handleInitOperation(String correlationId, TheiaCloudClient client, Deployment deployment,
+ AppDefinition appDefinition, Session session, List args) {
+
+ if (args.size() < 2 || args.size() > 3) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId, MessageFormat.format(
+ "Git init expects 2-3 arguments (repository path, branch, (secret-name)). Passed arguments are: {0}",
+ args.stream().collect(Collectors.joining(",")))));
+ return;
+ }
+
+ Optional storageName = AddedHandlerUtil.getStorageName(client, session, correlationId);
+ if (storageName.isEmpty()) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId,
+ "Git init is only supported for non-ephemeral workspaces"));
+ return;
+ }
+
+ List initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();
+ List volumes = deployment.getSpec().getTemplate().getSpec().getVolumes();
+
+ Container gitInitContainer = new Container();
+ initContainers.add(gitInitContainer);
+
+ gitInitContainer.setName(INIT_CONTAINER_NAME);
+ gitInitContainer.setImage(getImage());
+ String repository = args.get(0);
+ String branch = args.get(1);
+ List containerArgs = Arrays.asList(repository,
+ TheiaCloudPersistentVolumeUtil.getMountPath(appDefinition.getSpec()), branch);
+ gitInitContainer.setArgs(containerArgs);
+ LOGGER.info(LogMessageUtil.formatLogMessage(correlationId, MessageFormat.format("Git init arguments are: {0}",
+ containerArgs.stream().collect(Collectors.joining(",")))));
+
+ SecurityContext securityContext = new SecurityContext();
+ gitInitContainer.setSecurityContext(securityContext);
+
+ securityContext.setRunAsUser(Long.valueOf(appDefinition.getSpec().getUid()));
+ securityContext.setRunAsGroup(Long.valueOf(appDefinition.getSpec().getUid()));
+
+ VolumeMount volumeMount = AddedHandlerUtil.createUserDataVolumeMount(appDefinition.getSpec());
+ gitInitContainer.getVolumeMounts().add(volumeMount);
+
+ Optional secretName = args.size() == 3 ? Optional.of(args.get(2)) : Optional.empty();
+ Optional secret;
+ if (secretName.isPresent()) {
+ Secret k8sSecret = client.kubernetes().secrets().withName(secretName.get()).get();
+ if (k8sSecret == null) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId,
+ MessageFormat.format("No secret with name {0} found.", secretName)));
+ return;
+ }
+
+ String theiaCloudInit = k8sSecret.getMetadata().getLabels().get(THEIA_CLOUD_INIT_LABEL);
+ if (theiaCloudInit == null || !ID.equals(theiaCloudInit)) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId, MessageFormat
+ .format("Secret with name {0} is not configured to be used with Git init.", secretName)));
+ return;
+ }
+
+ String theiaCloudUser = k8sSecret.getMetadata().getAnnotations().get(THEIA_CLOUD_USER_LABEL);
+ if (theiaCloudUser == null || !session.getSpec().getUser().equals(theiaCloudUser)) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId,
+ MessageFormat.format("Secret with name {0} is not configured to be used by user {1}.",
+ secretName, session.getSpec().getUser())));
+ return;
+ }
+ secret = Optional.of(k8sSecret);
+ } else {
+ secret = Optional.empty();
+ }
+
+ if (isHTTP(repository)) {
+ if (!injectHTTPRepoCredentials(correlationId, secret, secretName, repository, gitInitContainer)) {
+ // problem during injection, return early
+ return;
+ }
+ } else {
+ if (!injectSSHRepoCredentials(correlationId, secret, secretName, repository, gitInitContainer, volumes)) {
+ // problem during injection, return early
+ return;
+ }
+ }
+
+ // init container is added to the deployment at this point
+ // any additional init code (e.g. injecting SSH Keys into the running IDE itself
+ // may follow below or may be added by extending this handler)
+
+ }
+
+ protected boolean injectHTTPRepoCredentials(String correlationId, Optional secret,
+ Optional secretName, String repository, Container gitInitContainer) {
+ /* get username/password from secret */
+ boolean injectUsername = false;
+ boolean injectPassword = false;
+
+ if (secret.isPresent() && secretName.isPresent()) {
+ if (!KUBERNETES_IO_BASIC_AUTH.equals(secret.get().getType())) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId, MessageFormat.format(
+ "Secret with name {0} is not of type {1}.", secretName.get(), KUBERNETES_IO_BASIC_AUTH)));
+ return false;
+ }
+
+ String[] split = repository.toLowerCase().split(Pattern.quote("://"), 2);
+ if (split.length != 2) {
+ LOGGER.error(LogMessageUtil.formatLogMessage(correlationId, MessageFormat
+ .format("Failed to check whether repository {0} contains any user information. ", repository)));
+ return false;
+ }
+ String repositoryWithoutProtocol = split[1];
+ if (repositoryWithoutProtocol.contains("@")) {
+ if (repositoryWithoutProtocol.split(Pattern.quote("@"))[0].contains(":")) {
+ /*
+ * username and password part of URL. keep injectUsername and injectPassword as
+ * false
+ */
+ } else {
+ /* username part of url */
+ injectPassword = true;
+ }
+ } else {
+ injectUsername = true;
+ injectPassword = true;
+ }
+ }
+
+ LOGGER.info(LogMessageUtil.formatLogMessage(correlationId,
+ MessageFormat.format("Inject username: {0}; Inject password: {1}", injectUsername, injectPassword)));
+
+ if (secretName.isPresent()) {
+ String nextEnv = GIT_PROMPT1;
+ if (injectUsername) {
+ EnvVar envVar = new EnvVar();
+ gitInitContainer.getEnv().add(envVar);
+ envVar.setName(nextEnv);
+ nextEnv = GIT_PROMPT2;
+
+ EnvVarSource envVarSource = new EnvVarSource();
+ envVar.setValueFrom(envVarSource);
+ envVarSource.setSecretKeyRef(new SecretKeySelector(USERNAME, secretName.get(), false));
+ }
+ if (injectPassword) {
+ EnvVar envVar = new EnvVar();
+ gitInitContainer.getEnv().add(envVar);
+ envVar.setName(nextEnv);
+
+ EnvVarSource envVarSource = new EnvVarSource();
+ envVar.setValueFrom(envVarSource);
+ envVarSource.setSecretKeyRef(new SecretKeySelector(PASSWORD, secretName.get(), false));
+ }
+ }
+
+ return true;
+ }
+
+ protected boolean injectSSHRepoCredentials(String correlationId, Optional secret,
+ Optional secretName, String repository, Container gitInitContainer, List volumes) {
+
+ if (secret.isPresent() && secretName.isPresent()) {
+ if (!KUBERNETES_IO_SSH_AUTH.equals(secret.get().getType())) {
+ LOGGER.warn(LogMessageUtil.formatLogMessage(correlationId, MessageFormat
+ .format("Secret with name {0} is not of type {1}.", secretName.get(), KUBERNETES_IO_SSH_AUTH)));
+ return false;
+ }
+
+ /* inject password */
+ EnvVar envVar = new EnvVar();
+ gitInitContainer.getEnv().add(envVar);
+ envVar.setName(GIT_PROMPT1);
+
+ EnvVarSource envVarSource = new EnvVarSource();
+ envVar.setValueFrom(envVarSource);
+ envVarSource.setSecretKeyRef(new SecretKeySelector(PASSWORD, secretName.get(), false));
+
+ /* inject ssh key */
+ Volume volume = new Volume();
+ volumes.add(volume);
+ volume.setName(SSH_KEY);
+ SecretVolumeSource secretVolumeSource = new SecretVolumeSource();
+ volume.setSecret(secretVolumeSource);
+ secretVolumeSource.setSecretName(secretName.get());
+ KeyToPath keyToPath = new KeyToPath();
+ secretVolumeSource.getItems().add(keyToPath);
+ keyToPath.setKey(SSH_PRIVATEKEY);
+ keyToPath.setPath(ID_THEIACLOUD);
+
+ VolumeMount volumeMount = new VolumeMount();
+ gitInitContainer.getVolumeMounts().add(volumeMount);
+ volumeMount.setName(SSH_KEY);
+ volumeMount.setMountPath(ETC_THEIA_CLOUD_SSH);
+ volumeMount.setReadOnly(true);
+ }
+
+ return true;
+ }
+
+ protected static boolean isHTTP(String repository) {
+ String lowerCasedRepo = repository.toLowerCase(Locale.US);
+ return (lowerCasedRepo.startsWith(HTTP) || lowerCasedRepo.startsWith(HTTPS));
+ }
+
+ protected String getImage() {
+ String image = System.getenv(IMAGE_ENV_KEY);
+ if (image == null || image.isBlank()) {
+ return DEFAULT_IMAGE;
+ }
+ return image;
+ }
+
+}
diff --git a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/LazySessionHandler.java b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/LazySessionHandler.java
index e4d534c2..af619dbd 100644
--- a/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/LazySessionHandler.java
+++ b/java/operator/org.eclipse.theia.cloud.operator/src/main/java/org/eclipse/theia/cloud/operator/handler/impl/LazySessionHandler.java
@@ -25,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -36,13 +37,12 @@
import org.eclipse.theia.cloud.common.k8s.resource.Session;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpec;
import org.eclipse.theia.cloud.common.k8s.resource.SessionStatus;
-import org.eclipse.theia.cloud.common.k8s.resource.Workspace;
import org.eclipse.theia.cloud.common.util.TheiaCloudError;
-import org.eclipse.theia.cloud.common.util.WorkspaceUtil;
import org.eclipse.theia.cloud.operator.TheiaCloudArguments;
import org.eclipse.theia.cloud.operator.handler.BandwidthLimiter;
import org.eclipse.theia.cloud.operator.handler.DeploymentTemplateReplacements;
import org.eclipse.theia.cloud.operator.handler.IngressPathProvider;
+import org.eclipse.theia.cloud.operator.handler.InitOperationHandler;
import org.eclipse.theia.cloud.operator.handler.SessionHandler;
import org.eclipse.theia.cloud.operator.handler.util.K8sUtil;
import org.eclipse.theia.cloud.operator.handler.util.TheiaCloudConfigMapUtil;
@@ -56,7 +56,6 @@
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
-import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.Volume;
@@ -74,7 +73,6 @@
public class LazySessionHandler implements SessionHandler {
private static final Logger LOGGER = LogManager.getLogger(LazySessionHandler.class);
- protected static final String USER_DATA = "user-data";
@Inject
protected IngressPathProvider ingressPathProvider;
@@ -84,7 +82,8 @@ public class LazySessionHandler implements SessionHandler {
protected BandwidthLimiter bandwidthLimiter;
@Inject
protected DeploymentTemplateReplacements deploymentReplacements;
-
+ @Inject
+ protected Set initOperationHandlers;
@Inject
protected TheiaCloudClient client;
@@ -240,7 +239,7 @@ protected boolean doSessionAdded(Session session, String correlationId) {
return true;
}
- Optional storageName = getStorageName(session, correlationId);
+ Optional storageName = AddedHandlerUtil.getStorageName(client, session, correlationId);
createAndApplyDeployment(correlationId, sessionResourceName, sessionResourceUID, session, appDefinition,
storageName, arguments.isUseKeycloak());
@@ -334,26 +333,6 @@ protected Optional getIngress(AppDefinition appDefinition, String corre
return ingress;
}
- protected Optional getStorageName(Session session, String correlationId) {
- if (session.getSpec().isEphemeral()) {
- return Optional.empty();
- }
- Optional workspace = client.workspaces().get(session.getSpec().getWorkspace());
- if (!workspace.isPresent()) {
- LOGGER.info(formatLogMessage(correlationId, "No workspace with name " + session.getSpec().getWorkspace()
- + " found for session " + session.getSpec().getName(), correlationId));
- return Optional.empty();
-
- }
- String storageName = WorkspaceUtil.getStorageName(workspace.get());
- if (!client.persistentVolumeClaims().has(storageName)) {
- LOGGER.info(formatLogMessage(correlationId,
- "No storage found for started session, will use ephemeral storage instead", correlationId));
- return Optional.empty();
- }
- return Optional.of(storageName);
- }
-
protected Optional createAndApplyService(String correlationId, String sessionResourceName,
String sessionResourceUID, Session session, AppDefinitionSpec appDefinitionSpec, boolean useOAuth2Proxy) {
Map replacements = TheiaCloudServiceUtil.getServiceReplacements(client.namespace(), session,
@@ -440,25 +419,19 @@ protected void createAndApplyDeployment(String correlationId, String sessionReso
&& !appDefinition.getSpec().getPullSecret().isEmpty()) {
AddedHandlerUtil.addImagePullSecret(deployment, appDefinition.getSpec().getPullSecret());
}
+
+ AddedHandlerUtil.handleInitOperations(correlationId, client, deployment, appDefinition, session,
+ initOperationHandlers);
});
}
protected void addVolumeClaim(Deployment deployment, String pvcName, AppDefinitionSpec appDefinition) {
PodSpec podSpec = deployment.getSpec().getTemplate().getSpec();
-
- Volume volume = new Volume();
+ Volume volume = AddedHandlerUtil.createUserDataVolume(pvcName);
podSpec.getVolumes().add(volume);
- volume.setName(USER_DATA);
- PersistentVolumeClaimVolumeSource persistentVolumeClaim = new PersistentVolumeClaimVolumeSource();
- volume.setPersistentVolumeClaim(persistentVolumeClaim);
- persistentVolumeClaim.setClaimName(pvcName);
-
- Container theiaContainer = TheiaCloudPersistentVolumeUtil.getTheiaContainer(podSpec, appDefinition);
-
- VolumeMount volumeMount = new VolumeMount();
- theiaContainer.getVolumeMounts().add(volumeMount);
- volumeMount.setName(USER_DATA);
- volumeMount.setMountPath(TheiaCloudPersistentVolumeUtil.getMountPath(appDefinition));
+ Container container = TheiaCloudPersistentVolumeUtil.getTheiaContainer(podSpec, appDefinition);
+ VolumeMount volumeMount = AddedHandlerUtil.createUserDataVolumeMount(appDefinition);
+ container.getVolumeMounts().add(volumeMount);
}
protected synchronized String updateIngress(Optional ingress, Optional serviceToUse,
diff --git a/java/service/org.eclipse.theia.cloud.service/.settings/org.eclipse.jdt.ui.prefs b/java/service/org.eclipse.theia.cloud.service/.settings/org.eclipse.jdt.ui.prefs
index 606155f8..766a660b 100644
--- a/java/service/org.eclipse.theia.cloud.service/.settings/org.eclipse.jdt.ui.prefs
+++ b/java/service/org.eclipse.theia.cloud.service/.settings/org.eclipse.jdt.ui.prefs
@@ -3,7 +3,7 @@ editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=org.eclipse.jdt.ui.default.sun_profile
formatter_settings_version=21
org.eclipse.jdt.ui.javadoc=false
-org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n *//********************************************************************************\n * Copyright (C) 2022 EclipseSource and others.\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License v. 2.0 which is available at\n * http\://www.eclipse.org/legal/epl-2.0.\n *\n * This Source Code may also be made available under the following Secondary\n * Licenses when the conditions for such availability set forth in the Eclipse\n * Public License v. 2.0 are satisfied\: GNU General Public License, version 2\n * with the GNU Classpath Exception which is available at\n * https\://www.gnu.org/software/classpath/license.html.\n *\n * SPDX-License-Identifier\: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0\n ********************************************************************************/\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n *//**\n * @param ${param} the ${bare_field_name} to set\n *//**\n * ${tags}\n *//**\n * \n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * \n *//**\n * ${tags}\n *//**\n * @author ${user}\n *\n * ${tags}\n *//**\n * ${tags}\n * ${see_to_target}\n *//********************************************************************************\n * Copyright (C) 2023 EclipseSource and others.\n *\n * This program and the accompanying materials are made available under the\n * terms of the Eclipse Public License v. 2.0 which is available at\n * http\://www.eclipse.org/legal/epl-2.0.\n *\n * This Source Code may also be made available under the following Secondary\n * Licenses when the conditions for such availability set forth in the Eclipse\n * Public License v. 2.0 are satisfied\: GNU General Public License, version 2\n * with the GNU Classpath Exception which is available at\n * https\://www.gnu.org/software/classpath/license.html.\n *\n * SPDX-License-Identifier\: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0\n ********************************************************************************/\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n\n// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\n${body_statement}${body_statement}\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
sp_cleanup.add_all=false
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/GitInit.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/GitInit.java
new file mode 100644
index 00000000..8d182fb4
--- /dev/null
+++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/GitInit.java
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * Copyright (C) 2023 EclipseSource and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+package org.eclipse.theia.cloud.service;
+
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+@Schema(name = "GitInit", description = "Holds information used to initialize a Workspace with a clone of a Git repository.")
+public class GitInit {
+
+ public static final String ID = "git";
+
+ @Schema(description = "The Git repository URL.", required = true)
+ public String repository;
+
+ @Schema(description = "The branch, commit-id, or tag name to checkout.", example = "main, bd402d6, tags/1.0.0", required = true)
+ public String checkout;
+
+ @Schema(description = "Key for the required auth information, if the repository is not public.", required = false)
+ public String authInformation;
+
+ @Override
+ public String toString() {
+ return "GitInit [repository=" + repository + ", checkout=" + checkout + ", authInformation=" + authInformation
+ + "]";
+ }
+
+}
diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java
index 34887890..86cea005 100644
--- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java
+++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/K8sUtil.java
@@ -18,7 +18,9 @@
import static org.eclipse.theia.cloud.common.util.WorkspaceUtil.getSessionName;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -29,6 +31,7 @@
import org.eclipse.theia.cloud.common.k8s.client.TheiaCloudClient;
import org.eclipse.theia.cloud.common.k8s.resource.Session;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpec;
+import org.eclipse.theia.cloud.common.k8s.resource.SessionSpec.InitOperation;
import org.eclipse.theia.cloud.common.k8s.resource.Workspace;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceSpec;
import org.eclipse.theia.cloud.common.util.CustomResourceUtil;
@@ -79,20 +82,20 @@ public String launchEphemeralSession(String correlationId, String appDefinition,
}
public String launchWorkspaceSession(String correlationId, UserWorkspace workspace, int timeout,
- EnvironmentVars env) {
- SessionSpec sessionSpec = new SessionSpec(getSessionName(workspace.name), workspace.appDefinition,
- workspace.user, workspace.name);
- sessionSpec = sessionSpecWithEnv(sessionSpec, env);
+ Optional env, Optional gitInit) {
+ SessionSpec sessionSpec = new SessionSpec(//
+ getSessionName(workspace.name), //
+ workspace.appDefinition, //
+ workspace.user, //
+ workspace.name, //
+ env.isPresent() ? env.get().fromMap : Map.of(), //
+ env.isPresent() ? env.get().fromConfigMaps : List.of(), //
+ env.isPresent() ? env.get().fromSecrets : List.of(), //
+ getInitOperations(gitInit));
return launchSession(correlationId, sessionSpec, timeout);
}
- private String launchSession(String correlationId, SessionSpec sessionSpec, int timeout) {
- SessionSpec spec = CLIENT.sessions().launch(correlationId, sessionSpec, timeout).getSpec();
- TheiaCloudWebException.throwIfErroneous(spec);
- return spec.getUrl();
- }
-
private SessionSpec sessionSpecWithEnv(SessionSpec spec, EnvironmentVars env) {
if (env == null)
return spec;
@@ -101,6 +104,26 @@ private SessionSpec sessionSpecWithEnv(SessionSpec spec, EnvironmentVars env) {
env.fromMap, env.fromConfigMaps, env.fromSecrets);
}
+ private List getInitOperations(Optional gitInit) {
+ List result = new ArrayList<>();
+ if (gitInit.isPresent()) {
+ List args = new ArrayList<>();
+ args.add(gitInit.get().repository);
+ args.add(gitInit.get().checkout);
+ if (gitInit.get().authInformation != null && !gitInit.get().authInformation.isBlank()) {
+ args.add(gitInit.get().authInformation);
+ }
+ result.add(new InitOperation(GitInit.ID, args));
+ }
+ return result;
+ }
+
+ private String launchSession(String correlationId, SessionSpec sessionSpec, int timeout) {
+ SessionSpec spec = CLIENT.sessions().launch(correlationId, sessionSpec, timeout).getSpec();
+ TheiaCloudWebException.throwIfErroneous(spec);
+ return spec.getUrl();
+ }
+
public boolean reportSessionActivity(String correlationId, String sessionName) {
return CLIENT.sessions().reportActivity(correlationId, sessionName);
}
diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/LaunchRequest.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/LaunchRequest.java
index b2f9dbc0..efb6dcb2 100644
--- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/LaunchRequest.java
+++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/LaunchRequest.java
@@ -39,6 +39,9 @@ public class LaunchRequest extends UserScopedServiceRequest {
@Schema(description = "Environment variables", required = false)
public EnvironmentVars env = new EnvironmentVars();
+ @Schema(description = "Git Init information", required = false)
+ public GitInit gitInit;
+
public LaunchRequest() {
super(KIND);
}
@@ -61,8 +64,8 @@ public boolean isCreateWorkspace() {
@Override
public String toString() {
return "LaunchRequest [user=" + user + ", appDefinition=" + appDefinition + ", workspaceName=" + workspaceName
- + ", label=" + label + ", ephemeral=" + ephemeral + ", appId=" + appId + ", kind=" + kind + ", timeout="
- + timeout + ", " + env + "]";
+ + ", label=" + label + ", ephemeral=" + ephemeral + ", timeout=" + timeout + ", env=" + env
+ + ", gitInit=" + gitInit + "]";
}
}
diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java
index 1208755b..865888fc 100644
--- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java
+++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/RootResource.java
@@ -67,6 +67,18 @@ public String launch(LaunchRequest request) {
throw new TheiaCloudWebException(TheiaCloudError.INVALID_APP_DEFINITION_NAME);
}
+ if (request.gitInit != null) {
+ if (request.isEphemeral()) {
+ /*
+ * Git init is currently only possible with non ephemeral sessions because we
+ * are initialising the mounted volume
+ */
+ error(correlationId,
+ "Failed to lauch session. Initialising a git repository with an ephemeral session is not supported at the moment.");
+ throw new TheiaCloudWebException(TheiaCloudError.INVALID_GIT_INIT_CONFIGURATION);
+ }
+ }
+
if (request.isEphemeral()) {
info(correlationId, "Launching ephemeral session " + request);
return k8sUtil.launchEphemeralSession(correlationId, request.appDefinition, user, request.timeout,
@@ -87,7 +99,7 @@ public String launch(LaunchRequest request) {
info(correlationId, "Launching existing workspace session " + request);
return k8sUtil.launchWorkspaceSession(correlationId, new UserWorkspace(workspace.get().getSpec()),
- request.timeout, request.env);
+ request.timeout, Optional.ofNullable(request.env), Optional.ofNullable(request.gitInit));
}
}
@@ -99,7 +111,7 @@ public String launch(LaunchRequest request) {
info(correlationId, "Launch workspace session " + request);
try {
return k8sUtil.launchWorkspaceSession(correlationId, new UserWorkspace(workspace.getSpec()),
- request.timeout, request.env);
+ request.timeout, Optional.ofNullable(request.env), Optional.ofNullable(request.gitInit));
} catch (Exception exception) {
info(correlationId, "Delete workspace due to launch error " + request);
k8sUtil.deleteWorkspace(correlationId, workspace.getSpec().getName());
diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionResource.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionResource.java
index 7edbdce9..515bc120 100644
--- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionResource.java
+++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionResource.java
@@ -74,6 +74,19 @@ public String start(SessionStartRequest request) {
final String user = evaluatedRequest.getUser();
info(correlationId, "Launching session " + request);
+
+ if (request.gitInit != null) {
+ if (request.isEphemeral()) {
+ /*
+ * Git init is currently only possible with non ephemeral sessions because we
+ * are initialising the mounted volume
+ */
+ error(correlationId,
+ "Failed to lauch session. Initialising a git repository with an ephemeral session is not supported at the moment.");
+ throw new TheiaCloudWebException(TheiaCloudError.INVALID_GIT_INIT_CONFIGURATION);
+ }
+ }
+
if (request.isEphemeral()) {
return k8sUtil.launchEphemeralSession(correlationId, request.appDefinition, user, request.timeout,
request.env);
@@ -92,7 +105,7 @@ public String start(SessionStartRequest request) {
}
info(correlationId, "Launch workspace session: " + request);
return k8sUtil.launchWorkspaceSession(correlationId, new UserWorkspace(workspace.get().getSpec()),
- request.timeout, request.env);
+ request.timeout, Optional.ofNullable(request.env), Optional.ofNullable(request.gitInit));
}
@Operation(summary = "Stop session", description = "Stops a session.")
@@ -126,6 +139,7 @@ public boolean stop(SessionStopRequest request) {
@Operation(summary = "Report session activity", description = "Updates the last activity timestamp for a session to monitor activity.")
@PATCH
@PermitAll
+ @Deprecated
public boolean activity(SessionActivityRequest request) {
// TODO activity reporting will be removed from this service
// There will be a dedicated service that will have direct communication with
@@ -180,4 +194,5 @@ protected boolean isOwner(String user, SessionSpec session) {
return session.getUser().equals(user);
}
+
}
diff --git a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionStartRequest.java b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionStartRequest.java
index 1ab2419b..9473ef9c 100644
--- a/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionStartRequest.java
+++ b/java/service/org.eclipse.theia.cloud.service/src/main/java/org/eclipse/theia/cloud/service/session/SessionStartRequest.java
@@ -18,6 +18,7 @@
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.theia.cloud.service.EnvironmentVars;
+import org.eclipse.theia.cloud.service.GitInit;
import org.eclipse.theia.cloud.service.UserScopedServiceRequest;
@Schema(name = "SessionStartRequest", description = "A request to start a session")
@@ -36,6 +37,9 @@ public class SessionStartRequest extends UserScopedServiceRequest {
@Schema(description = "Environment variables", required = false)
public EnvironmentVars env = new EnvironmentVars();
+ @Schema(description = "Git Init information", required = false)
+ public GitInit gitInit;
+
public SessionStartRequest() {
super(KIND);
}
@@ -69,7 +73,7 @@ public boolean isEphemeral() {
@Override
public String toString() {
return "SessionStartRequest [user=" + user + ", appDefinition=" + appDefinition + ", workspaceName="
- + workspaceName + ", timeout=" + timeout + ", env=" + env + "]";
+ + workspaceName + ", timeout=" + timeout + ", env=" + env + ", gitInit=" + gitInit + "]";
}
}
diff --git a/java/service/org.eclipse.theia.cloud.service/src/test/java/org/eclipse/theia/cloud/service/session/SessionResourceTests.java b/java/service/org.eclipse.theia.cloud.service/src/test/java/org/eclipse/theia/cloud/service/session/SessionResourceTests.java
index 09cb8bd8..421cb93e 100644
--- a/java/service/org.eclipse.theia.cloud.service/src/test/java/org/eclipse/theia/cloud/service/session/SessionResourceTests.java
+++ b/java/service/org.eclipse.theia.cloud.service/src/test/java/org/eclipse/theia/cloud/service/session/SessionResourceTests.java
@@ -402,7 +402,7 @@ void start_noKeycloak_throwForbidden() {
// Assert
Mockito.verify(k8sUtil, never()).getWorkspace(anyString(), anyString());
- Mockito.verify(k8sUtil, never()).launchWorkspaceSession(anyString(), any(), anyInt(), any());
+ Mockito.verify(k8sUtil, never()).launchWorkspaceSession(anyString(), any(), anyInt(), any(), any());
assertEquals(Status.FORBIDDEN.getStatusCode(), exception.getResponse().getStatus());
}
@@ -425,7 +425,7 @@ void start_otherUser_throwForbidden() {
// Assert
Mockito.verify(k8sUtil, never()).getWorkspace(anyString(), anyString());
- Mockito.verify(k8sUtil, never()).launchWorkspaceSession(anyString(), any(), anyInt(), any());
+ Mockito.verify(k8sUtil, never()).launchWorkspaceSession(anyString(), any(), anyInt(), any(), any());
assertEquals(Status.FORBIDDEN.getStatusCode(), exception.getResponse().getStatus());
}
diff --git a/node/common/package.json b/node/common/package.json
index e5ffbe4b..4219cb01 100644
--- a/node/common/package.json
+++ b/node/common/package.json
@@ -1,6 +1,6 @@
{
"name": "@eclipse-theiacloud/common",
- "version": "0.8.1-alpha.1",
+ "version": "0.8.1-alpha.2",
"description": "Common functionality for Theia.cloud",
"license": "EPL-2.0",
"keywords": [
diff --git a/node/common/src/client.ts b/node/common/src/client.ts
index b7ee7131..3c46d027 100644
--- a/node/common/src/client.ts
+++ b/node/common/src/client.ts
@@ -2,7 +2,7 @@ import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { v4 as uuidv4 } from 'uuid';
import {
- LaunchRequest as ClientLaunchRequest,
+ GitInit as ClientGitInit, LaunchRequest as ClientLaunchRequest,
PingRequest as ClientPingRequest, RootResourceApi, SessionActivityRequest as ClientSessionActivityRequest, SessionListRequest as ClientSessionListRequest,
SessionPerformance, SessionPerformanceRequest as ClientSessionPerformanceRequest,
SessionResourceApi, SessionSpec, SessionStartRequest as ClientSessionStartRequest, SessionStopRequest as ClientSessionStopRequest,
@@ -49,13 +49,13 @@ export namespace LaunchRequest {
}
export function createWorkspace(serviceUrl: string, appId: string, appDefinition: string, timeout?: number, user: string = createUser(),
- workspaceName?: string, label?: string): LaunchRequest {
- return { serviceUrl, appId, appDefinition, user, label, workspaceName, ephemeral: false, timeout };
+ workspaceName?: string, label?: string, gitInit?: GitInit): LaunchRequest {
+ return { serviceUrl, appId, appDefinition, user, label, workspaceName, ephemeral: false, timeout, gitInit };
}
// eslint-disable-next-line max-len
- export function existingWorkspace(serviceUrl: string, appId: string, workspaceName: string, timeout?: number, appDefinition?: string, user: string = createUser()): LaunchRequest {
- return { serviceUrl, appId, workspaceName, appDefinition, user, timeout };
+ export function existingWorkspace(serviceUrl: string, appId: string, workspaceName: string, timeout?: number, appDefinition?: string, user: string = createUser(), gitInit?: GitInit): LaunchRequest {
+ return { serviceUrl, appId, workspaceName, appDefinition, user, timeout, gitInit };
}
}
@@ -99,6 +99,8 @@ export namespace WorkspaceDeletionRequest {
export const KIND = 'workspaceDeletionRequest';
}
+export type GitInit = ClientGitInit;
+
export namespace TheiaCloud {
function rootApi(serviceUrl: string, accessToken: string | undefined): RootResourceApi {
return new RootResourceApi(new Configuration({ basePath: serviceUrl, accessToken }));
diff --git a/node/common/src/client/api.ts b/node/common/src/client/api.ts
index fba8c198..cd8e58e8 100644
--- a/node/common/src/client/api.ts
+++ b/node/common/src/client/api.ts
@@ -4,7 +4,7 @@
* Theia.cloud API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
- * The version of the OpenAPI document: 0.8.0
+ * The version of the OpenAPI document: 0.8.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -21,7 +21,7 @@ import globalAxios from 'axios';
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
import type { RequestArgs } from './base';
// @ts-ignore
-import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base';
+import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base';
/**
* An object to hold all the ways environment variables can be passed. Not to be used by itself.
@@ -48,6 +48,50 @@ export interface EnvironmentVars {
*/
'fromSecrets'?: Array;
}
+/**
+ * Holds information used to initialize a Workspace with a clone of a Git repository.
+ * @export
+ * @interface GitInit
+ */
+export interface GitInit {
+ /**
+ * The Git repository URL.
+ * @type {string}
+ * @memberof GitInit
+ */
+ 'repository': string;
+ /**
+ * The branch, commit-id, or tag name to checkout.
+ * @type {string}
+ * @memberof GitInit
+ */
+ 'checkout': string;
+ /**
+ * Key for the required auth information, if the repository is not public.
+ * @type {string}
+ * @memberof GitInit
+ */
+ 'authInformation'?: string;
+}
+/**
+ *
+ * @export
+ * @interface InitOperation
+ */
+export interface InitOperation {
+ /**
+ *
+ * @type {string}
+ * @memberof InitOperation
+ */
+ 'id'?: string;
+ /**
+ *
+ * @type {Array}
+ * @memberof InitOperation
+ */
+ 'arguments'?: Array;
+}
/**
* A request to launch a new session.
* @export
@@ -102,6 +146,12 @@ export interface LaunchRequest {
* @memberof LaunchRequest
*/
'env'?: LaunchRequestEnv;
+ /**
+ *
+ * @type {LaunchRequestGitInit}
+ * @memberof LaunchRequest
+ */
+ 'gitInit'?: LaunchRequestGitInit;
}
/**
*
@@ -128,6 +178,31 @@ export interface LaunchRequestEnv {
*/
'fromSecrets'?: Array;
}
+/**
+ *
+ * @export
+ * @interface LaunchRequestGitInit
+ */
+export interface LaunchRequestGitInit {
+ /**
+ * The Git repository URL.
+ * @type {string}
+ * @memberof LaunchRequestGitInit
+ */
+ 'repository': string;
+ /**
+ * The branch, commit-id, or tag name to checkout.
+ * @type {string}
+ * @memberof LaunchRequestGitInit
+ */
+ 'checkout': string;
+ /**
+ * Key for the required auth information, if the repository is not public.
+ * @type {string}
+ * @memberof LaunchRequestGitInit
+ */
+ 'authInformation'?: string;
+}
/**
* Request to ping the availability of the service.
* @export
@@ -301,6 +376,12 @@ export interface SessionSpec {
* @memberof SessionSpec
*/
'envVarsFromSecrets'?: Array;
+ /**
+ *
+ * @type {Array}
+ * @memberof SessionSpec
+ */
+ 'initOperations'?: Array;
}
/**
* A request to start a session
@@ -344,6 +425,12 @@ export interface SessionStartRequest {
* @memberof SessionStartRequest
*/
'env'?: LaunchRequestEnv;
+ /**
+ *
+ * @type {LaunchRequestGitInit}
+ * @memberof SessionStartRequest
+ */
+ 'gitInit'?: LaunchRequestGitInit;
}
/**
* A request to stop a session
@@ -584,7 +671,9 @@ export const RootResourceApiFp = function(configuration?: Configuration) {
*/
async serviceAppIdGet(appId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceAppIdGet(appId, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['RootResourceApi.serviceAppIdGet']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Launches a session and creates a workspace if required. Responds with the URL of the launched session.
@@ -595,7 +684,9 @@ export const RootResourceApiFp = function(configuration?: Configuration) {
*/
async servicePost(launchRequest?: LaunchRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.servicePost(launchRequest, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['RootResourceApi.servicePost']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
}
};
@@ -663,6 +754,7 @@ export class RootResourceApi extends BaseAPI {
}
+
/**
* SessionResourceApi - axios parameter creator
* @export
@@ -754,6 +846,7 @@ export const SessionResourceApiAxiosParamCreator = function (configuration?: Con
* @summary Report session activity
* @param {SessionActivityRequest} [sessionActivityRequest]
* @param {*} [options] Override http request option.
+ * @deprecated
* @throws {RequiredError}
*/
serviceSessionPatch: async (sessionActivityRequest?: SessionActivityRequest, options: AxiosRequestConfig = {}): Promise => {
@@ -887,7 +980,9 @@ export const SessionResourceApiFp = function(configuration?: Configuration) {
*/
async serviceSessionAppIdUserGet(appId: string, user: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceSessionAppIdUserGet(appId, user, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['SessionResourceApi.serviceSessionAppIdUserGet']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Stops a session.
@@ -898,18 +993,23 @@ export const SessionResourceApiFp = function(configuration?: Configuration) {
*/
async serviceSessionDelete(sessionStopRequest?: SessionStopRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceSessionDelete(sessionStopRequest, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['SessionResourceApi.serviceSessionDelete']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Updates the last activity timestamp for a session to monitor activity.
* @summary Report session activity
* @param {SessionActivityRequest} [sessionActivityRequest]
* @param {*} [options] Override http request option.
+ * @deprecated
* @throws {RequiredError}
*/
async serviceSessionPatch(sessionActivityRequest?: SessionActivityRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceSessionPatch(sessionActivityRequest, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['SessionResourceApi.serviceSessionPatch']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Returns the current CPU and memory usage of the session\'s pod.
@@ -921,7 +1021,9 @@ export const SessionResourceApiFp = function(configuration?: Configuration) {
*/
async serviceSessionPerformanceAppIdSessionNameGet(appId: string, sessionName: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceSessionPerformanceAppIdSessionNameGet(appId, sessionName, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['SessionResourceApi.serviceSessionPerformanceAppIdSessionNameGet']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Starts a new session for an existing workspace and responds with the URL of the started session.
@@ -932,7 +1034,9 @@ export const SessionResourceApiFp = function(configuration?: Configuration) {
*/
async serviceSessionPost(sessionStartRequest?: SessionStartRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceSessionPost(sessionStartRequest, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['SessionResourceApi.serviceSessionPost']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
}
};
@@ -970,6 +1074,7 @@ export const SessionResourceApiFactory = function (configuration?: Configuration
* @summary Report session activity
* @param {SessionActivityRequest} [sessionActivityRequest]
* @param {*} [options] Override http request option.
+ * @deprecated
* @throws {RequiredError}
*/
serviceSessionPatch(sessionActivityRequest?: SessionActivityRequest, options?: any): AxiosPromise {
@@ -1036,6 +1141,7 @@ export class SessionResourceApi extends BaseAPI {
* @summary Report session activity
* @param {SessionActivityRequest} [sessionActivityRequest]
* @param {*} [options] Override http request option.
+ * @deprecated
* @throws {RequiredError}
* @memberof SessionResourceApi
*/
@@ -1070,6 +1176,7 @@ export class SessionResourceApi extends BaseAPI {
}
+
/**
* WorkspaceResourceApi - axios parameter creator
* @export
@@ -1214,7 +1321,9 @@ export const WorkspaceResourceApiFp = function(configuration?: Configuration) {
*/
async serviceWorkspaceAppIdUserGet(appId: string, user: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceWorkspaceAppIdUserGet(appId, user, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['WorkspaceResourceApi.serviceWorkspaceAppIdUserGet']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Deletes a workspace.
@@ -1225,7 +1334,9 @@ export const WorkspaceResourceApiFp = function(configuration?: Configuration) {
*/
async serviceWorkspaceDelete(workspaceDeletionRequest?: WorkspaceDeletionRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceWorkspaceDelete(workspaceDeletionRequest, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['WorkspaceResourceApi.serviceWorkspaceDelete']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* Creates a new workspace for a user.
@@ -1236,7 +1347,9 @@ export const WorkspaceResourceApiFp = function(configuration?: Configuration) {
*/
async serviceWorkspacePost(workspaceCreationRequest?: WorkspaceCreationRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.serviceWorkspacePost(workspaceCreationRequest, options);
- return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['WorkspaceResourceApi.serviceWorkspacePost']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
}
};
@@ -1328,3 +1441,4 @@ export class WorkspaceResourceApi extends BaseAPI {
}
+
diff --git a/node/common/src/client/base.ts b/node/common/src/client/base.ts
index 021d1142..826facd7 100644
--- a/node/common/src/client/base.ts
+++ b/node/common/src/client/base.ts
@@ -4,7 +4,7 @@
* Theia.cloud API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
- * The version of the OpenAPI document: 0.8.0
+ * The version of the OpenAPI document: 0.8.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -53,7 +53,7 @@ export class BaseAPI {
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
if (configuration) {
this.configuration = configuration;
- this.basePath = configuration.basePath || this.basePath;
+ this.basePath = configuration.basePath ?? basePath;
}
}
};
@@ -70,3 +70,17 @@ export class RequiredError extends Error {
this.name = "RequiredError"
}
}
+
+interface ServerMap {
+ [key: string]: {
+ url: string,
+ description: string,
+ }[];
+}
+
+/**
+ *
+ * @export
+ */
+export const operationServerMap: ServerMap = {
+}
diff --git a/node/common/src/client/common.ts b/node/common/src/client/common.ts
index ec23c5ce..ce29e5f5 100644
--- a/node/common/src/client/common.ts
+++ b/node/common/src/client/common.ts
@@ -4,7 +4,7 @@
* Theia.cloud API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
- * The version of the OpenAPI document: 0.8.0
+ * The version of the OpenAPI document: 0.8.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -144,7 +144,7 @@ export const toPathString = function (url: URL) {
*/
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
- const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
+ const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url};
return axios.request(axiosRequestArgs);
};
}
diff --git a/node/common/src/client/configuration.ts b/node/common/src/client/configuration.ts
index a93108b4..d26512cb 100644
--- a/node/common/src/client/configuration.ts
+++ b/node/common/src/client/configuration.ts
@@ -4,7 +4,7 @@
* Theia.cloud API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
- * The version of the OpenAPI document: 0.8.0
+ * The version of the OpenAPI document: 0.8.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -19,6 +19,7 @@ export interface ConfigurationParameters {
password?: string;
accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise);
basePath?: string;
+ serverIndex?: number;
baseOptions?: any;
formDataCtor?: new () => any;
}
@@ -58,6 +59,13 @@ export class Configuration {
* @memberof Configuration
*/
basePath?: string;
+ /**
+ * override server index
+ *
+ * @type {number}
+ * @memberof Configuration
+ */
+ serverIndex?: number;
/**
* base options for axios calls
*
@@ -80,6 +88,7 @@ export class Configuration {
this.password = param.password;
this.accessToken = param.accessToken;
this.basePath = param.basePath;
+ this.serverIndex = param.serverIndex;
this.baseOptions = param.baseOptions;
this.formDataCtor = param.formDataCtor;
}
diff --git a/node/common/src/client/index.ts b/node/common/src/client/index.ts
index 94045ca3..12feaefd 100644
--- a/node/common/src/client/index.ts
+++ b/node/common/src/client/index.ts
@@ -4,7 +4,7 @@
* Theia.cloud API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
- * The version of the OpenAPI document: 0.8.0
+ * The version of the OpenAPI document: 0.8.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
diff --git a/node/package-lock.json b/node/package-lock.json
index eb68db3e..993ae830 100644
--- a/node/package-lock.json
+++ b/node/package-lock.json
@@ -28,7 +28,7 @@
},
"common": {
"name": "@eclipse-theiacloud/common",
- "version": "0.8.1-alpha.1",
+ "version": "0.8.1-alpha.2",
"license": "EPL-2.0",
"dependencies": {
"@types/uuid": "^8.3.4",
@@ -38,7 +38,7 @@
},
"monitor": {
"name": "theiacloud-monitor",
- "version": "0.8.0",
+ "version": "0.8.1",
"license": "EPL-2.0",
"dependencies": {
"express": "^4.18.1",
@@ -68,7 +68,8 @@
}
},
"monitor-theia": {
- "version": "0.8.0",
+ "name": "@eclipse-theiacloud/monitor-theia",
+ "version": "0.8.1-alpha.2",
"dependencies": {
"@theia/core": "^1.34.0",
"@theia/plugin-ext": "^1.34.0"
@@ -2230,6 +2231,10 @@
"resolved": "common",
"link": true
},
+ "node_modules/@eclipse-theiacloud/monitor-theia": {
+ "resolved": "monitor-theia",
+ "link": true
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -6056,8 +6061,6 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
- "optional": true,
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -6072,9 +6075,7 @@
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "optional": true,
- "peer": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
@@ -13990,10 +13991,6 @@
"node": ">=10"
}
},
- "node_modules/monitor-theia": {
- "resolved": "monitor-theia",
- "link": true
- },
"node_modules/mount-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mount-point/-/mount-point-3.0.0.tgz",
@@ -20897,7 +20894,7 @@
"testing-page": {
"version": "0.1.0",
"dependencies": {
- "@eclipse-theiacloud/common": "0.8.1-alpha.1",
+ "@eclipse-theiacloud/common": "0.8.1-alpha.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
@@ -20935,7 +20932,7 @@
"try-now-page": {
"version": "0.1.0",
"dependencies": {
- "@eclipse-theiacloud/common": "0.8.1-alpha.1",
+ "@eclipse-theiacloud/common": "0.8.1-alpha.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
@@ -22371,6 +22368,15 @@
"uuid": "^8.3.2"
}
},
+ "@eclipse-theiacloud/monitor-theia": {
+ "version": "file:monitor-theia",
+ "requires": {
+ "@theia/core": "^1.34.0",
+ "@theia/plugin-ext": "^1.34.0",
+ "rimraf": "^3.0.2",
+ "typescript": "^4.6.3"
+ }
+ },
"@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -25405,13 +25411,14 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "requires": {},
+ "requires": {
+ "ajv": "^8.0.0"
+ },
"dependencies": {
"ajv": {
- "version": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
- "optional": true,
- "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -25422,9 +25429,7 @@
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "optional": true,
- "peer": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
}
}
},
@@ -31307,15 +31312,6 @@
}
}
},
- "monitor-theia": {
- "version": "file:monitor-theia",
- "requires": {
- "@theia/core": "^1.34.0",
- "@theia/plugin-ext": "^1.34.0",
- "rimraf": "^3.0.2",
- "typescript": "^4.6.3"
- }
- },
"mount-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mount-point/-/mount-point-3.0.0.tgz",
@@ -34570,6 +34566,7 @@
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
+ "postcss": "^8.4.12",
"postcss-js": "^4.0.0",
"postcss-load-config": "^3.1.4",
"postcss-nested": "5.0.6",
@@ -34716,7 +34713,7 @@
"testing-page": {
"version": "file:testing-page",
"requires": {
- "@eclipse-theiacloud/common": "0.8.1-alpha.1",
+ "@eclipse-theiacloud/common": "0.8.1-alpha.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
@@ -34969,7 +34966,7 @@
"try-now-page": {
"version": "file:try-now-page",
"requires": {
- "@eclipse-theiacloud/common": "0.8.1-alpha.1",
+ "@eclipse-theiacloud/common": "0.8.1-alpha.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
diff --git a/node/testing-page/package.json b/node/testing-page/package.json
index d1c3b36c..e2dc6838 100644
--- a/node/testing-page/package.json
+++ b/node/testing-page/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@eclipse-theiacloud/common": "0.8.1-alpha.1",
+ "@eclipse-theiacloud/common": "0.8.1-alpha.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
diff --git a/node/try-now-page/package.json b/node/try-now-page/package.json
index c21d17ff..c113443b 100644
--- a/node/try-now-page/package.json
+++ b/node/try-now-page/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": ".",
"dependencies": {
- "@eclipse-theiacloud/common": "0.8.1-alpha.1",
+ "@eclipse-theiacloud/common": "0.8.1-alpha.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
diff --git a/python/git-init/README.md b/python/git-init/README.md
new file mode 100644
index 00000000..58dec310
--- /dev/null
+++ b/python/git-init/README.md
@@ -0,0 +1,175 @@
+# Git Init Container
+
+## Scenarios
+
+- HTTP(S)
+ - No Auth
+ - Ask for password only
+ - Ask for username and password
+- SSH
+ - No Auth
+ - Ask for password
+
+## Testing
+
+### Build init container
+
+```bash
+docker build -t theiacloud/theia-cloud-git-init:local -f dockerfiles/git-init/Dockerfile .
+```
+
+### Generate Test SSH Key Pair
+
+```bash
+# don't save in ~/.ssh/... but e.g. in ~/tmp/ssh/id_theiacloud
+ssh-keygen -t ed25519 -C "Test TC Git Init SSH Keypair"
+
+# check if key is added already
+ssh-add -L
+
+# add the key if necessary
+ssh-add ~/tmp/ssh/id_theiacloud
+```
+
+### Test Checkout with container
+
+Please also play with wrong password or public SSH Keys that are not (yet) added to the repository to get the known error cases.
+
+```bash
+# Adjust URLs and Password/PATs below
+# keep spaces in front to avoid command being added to bash history
+ export HTTP_PUBLIC=https://github.com/eclipsesource/theia-cloud.git
+ export HTTP_PRIVATE=https://gitlab.eclipse.org/username/my.repository.git
+ export HTTP_PRIVATE_WITH_USERNAME=https://username@gitlab.eclipse.org/username/my.repository.git
+ export HTTP_PRIVATE_WITH_USERNAME_AND_PASSWORD=https://username:pat@gitlab.eclipse.org/username/my.repository.git
+ export HTTP_USERNAME=username
+ export HTTP_PASSWORD=pat
+ export SSH_PASSWORD=sshpw
+ export SSH_REPO="git@gitlab.eclipse.org:username/my.repository.git"
+ export BRANCH=maintenance_1_1_x
+
+# HTTPS Public
+docker run --rm theiacloud/theia-cloud-git-init:local "$HTTP_PUBLIC" "/tmp/my-repo" "$BRANCH"
+
+# For HTTPS auth with PATs as password a lot of providers accept any username
+# HTTPS Private
+docker run --env GIT_PROMPT1=$HTTP_USERNAME --env GIT_PROMPT2=$HTTP_PASSWORD --rm theiacloud/theia-cloud-git-init:local "$HTTP_PRIVATE" "/tmp/my-repo" "$BRANCH"
+
+# HTTPS Private with Username
+docker run --env GIT_PROMPT1=$HTTP_PASSWORD --rm theiacloud/theia-cloud-git-init:local "$HTTP_PRIVATE_WITH_USERNAME" "/tmp/my-repo" "$BRANCH"
+
+# HTTPS Private with Username and Password
+docker run --rm theiacloud/theia-cloud-git-init:local "$HTTP_PRIVATE_WITH_USERNAME_AND_PASSWORD" "/tmp/my-repo" "$BRANCH"
+
+# SSH (the expected keyname is id_theiacloud ! With a different naming pattern this command will fail. Rename/Create a copy of you keyname if necessary)
+docker run --env GIT_PROMPT1=$SSH_PASSWORD -v ~/tmp/ssh/:/etc/theia-cloud-ssh --rm theiacloud/theia-cloud-git-init:local "$SSH_REPO" "/tmp/my-repo" "$BRANCH"
+```
+
+### Create Kubernetes Resources
+
+#### Workspace
+
+If testing on Minikube also mount a directory with expected user permissions: `minikube mount --uid 101 --gid 101 ~/tmp/minikube:/tmp/hostpath-provisioner/theia-cloud`
+
+You might have to adjust your firewall (temporarily).
+
+With below Sessions, the Workspace will be mounted inside the `persisted` subdirectory in the workspace.
+
+```yaml
+apiVersion: theia.cloud/v3beta
+kind: Workspace
+metadata:
+ name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io
+ namespace: theiacloud
+spec:
+ name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io
+ user: foo@theia-cloud.io
+```
+
+#### Secret for HTTP(S) auth
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: foo-theiacloud-io-basic-auth
+ namespace: theiacloud
+ labels:
+ theiaCloudInit: git
+ annotations:
+ theiaCloudUser: foo@theia-cloud.io
+type: kubernetes.io/basic-auth
+stringData:
+ username: username
+ password: pat
+```
+
+#### Example Session for HTTP(S) auth
+
+```yaml
+apiVersion: theia.cloud/v7beta
+kind: Session
+metadata:
+ name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io-session
+ namespace: theiacloud
+spec:
+ appDefinition: theia-cloud-demo
+ envVars: {}
+ envVarsFromConfigMaps: []
+ envVarsFromSecrets: []
+ name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io-session
+ user: foo@theia-cloud.io
+ workspace: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io
+ sessionSecret: 3e68605f-0c6d-4ae5-9816-738f15d34fc9
+ initOperations:
+ - id: git
+ arguments:
+ - https://gitlab.eclipse.org/username/my.repository.git
+ - maintenance_1_1_x
+ - foo-theiacloud-io-basic-auth
+```
+
+#### Secrets for SSH auth
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: foo-theiacloud-io-ssh-auth
+ namespace: theiacloud
+ labels:
+ theiaCloudInit: git
+ annotations:
+ theiaCloudUser: foo@theia-cloud.io
+type: kubernetes.io/ssh-auth
+stringData:
+ ssh-privatekey: |
+ -----BEGIN OPENSSH PRIVATE KEY-----
+ b3B...
+ password: sshpw
+```
+
+#### Example Session for SSH auth
+
+```yaml
+apiVersion: theia.cloud/v7beta
+kind: Session
+metadata:
+ name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io-session
+ namespace: theiacloud
+spec:
+ appDefinition: theia-cloud-demo
+ envVars: {}
+ envVarsFromConfigMaps: []
+ envVarsFromSecrets: []
+ name: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io-session
+ user: foo@theia-cloud.io
+ workspace: ws-asdfghjkl-theia-cloud-demo-foo-theia-cloud-io
+ sessionSecret: 3e68605f-0c6d-4ae5-9816-738f15d34fc9
+ initOperations:
+ - id: git
+ arguments:
+ - git@gitlab.eclipse.org:username/my.repository.git
+ - maintenance_1_1_x
+ - foo-theiacloud-io-ssh-auth
+```
diff --git a/python/git-init/entrypoint.sh b/python/git-init/entrypoint.sh
new file mode 100755
index 00000000..ce856a20
--- /dev/null
+++ b/python/git-init/entrypoint.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+USERID=$(id -u)
+
+if id "$USERID" &>/dev/null; then
+ echo 'Existing user'
+else
+ echo 'Setup user for id'
+ # add an entry to /etc/passwd for the user
+ echo user:x:$USERID:$USERID:user:/user:/bin/bash >> /etc/passwd
+ export HOME=/user
+fi
+
+# start SSH agent
+eval `ssh-agent`
+
+# prepare required directories and import ssh key, if available
+mkdir -p $HOME/.ssh
+touch $HOME/.ssh/known_hosts
+[ -e /etc/theia-cloud-ssh/id_theiacloud ] && { sleep 1; echo $GIT_PROMPT1; } | script -q /dev/null -c 'ssh-add /etc/theia-cloud-ssh/id_theiacloud'
+
+# hand over to clone script
+python3 git-init.py "$@"
\ No newline at end of file
diff --git a/python/git-init/git-askpw.py b/python/git-init/git-askpw.py
new file mode 100755
index 00000000..3dded3fa
--- /dev/null
+++ b/python/git-init/git-askpw.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+import os
+
+path = "/tmp/theia-cloud-askpw"
+
+if os.path.isfile(path):
+ prompt2 = os.environ['GIT_PROMPT2']
+ print(prompt2)
+else:
+ prompt1 = os.environ['GIT_PROMPT1']
+ print(prompt1)
+ os.mknod(path)
diff --git a/python/git-init/git-init.py b/python/git-init/git-init.py
new file mode 100755
index 00000000..d9464fe2
--- /dev/null
+++ b/python/git-init/git-init.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+import argparse
+import subprocess
+import os
+import sys
+
+debugLogging = False
+sshKey = "/etc/theia-cloud-ssh/id_theiacloud"
+NL = "\n"
+
+
+def runProcess(args):
+ process = subprocess.Popen(
+ args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = process.communicate()
+ process.wait()
+ out = stdout.decode('ascii')
+ if len(out) > 0:
+ sys.stdout.write(out + NL)
+ if process.returncode != 0:
+ sys.stderr.write(stderr.decode('ascii') + NL)
+ return process.returncode
+
+def getHostname(repository):
+ # remove protocol, if any
+ split = repository.split("://", 1)
+ if len(split) == 1:
+ repository = split[0]
+ else:
+ repository = split[1]
+ if debugLogging:
+ sys.stdout.write("getHostname 1: " + repository + NL)
+
+ # remove path, if any
+ split = repository.split("/", 1)
+ repository = split[0]
+ if debugLogging:
+ sys.stdout.write("getHostname 2: " + repository + NL)
+
+ # remove user information, if any
+ split = repository.split("@", 1)
+ if len(split) == 1:
+ repository = split[0]
+ else:
+ repository = split[1]
+ if debugLogging:
+ sys.stdout.write("getHostname 3: " + repository + NL)
+
+ # remove trailing information, if any
+ split = repository.split(":", 1)
+ repository = split[0]
+ if debugLogging:
+ sys.stdout.write("getHostname 4: " + repository + NL)
+
+ return repository
+
+parser = argparse.ArgumentParser()
+parser.add_argument("repository", help="The repository URL", type=str)
+parser.add_argument("directory", help="The directory to clone into", type=str)
+parser.add_argument("checkout", help="The branch/commit id/tag to checkout", type=str)
+args = parser.parse_args()
+
+# Check if directory is empty, don't clone if it isn't
+if os.path.isdir(args.directory):
+ if len(os.listdir(args.directory)) > 0:
+ sys.stdout.write("Clone directory is not empty. Continue without cloning." + NL)
+ exit(0)
+
+# Set up git credential helper
+code = runProcess(["git", "config", "--global", "credential.helper", "store"])
+if code != 0:
+ exit(code)
+
+# Check if SSH key is available, if so prepare clone with SSH
+if os.path.isfile(sshKey):
+ # Add know host
+ code = runProcess(["/tmp/ssh-keyscan.sh", getHostname(args.repository)])
+ if code != 0:
+ exit(code)
+
+ if debugLogging:
+ runProcess(["ssh-add", "-l"])
+ runProcess(["cat", os.environ.get('HOME') + "/.ssh/known_hosts"])
+
+# Clone repository
+code = runProcess(["git", "clone", args.repository, args.directory])
+if code != 0:
+ exit(code)
+
+if debugLogging:
+ runProcess(["ls", "-al", args.directory])
+
+# Checkout
+code = runProcess(["git", "-C", args.directory, "checkout", args.checkout])
+if code != 0:
+ exit(code)
+
+if debugLogging:
+ runProcess(["git", "-C", args.directory, "status"])
diff --git a/python/git-init/ssh-keyscan.sh b/python/git-init/ssh-keyscan.sh
new file mode 100755
index 00000000..e254847b
--- /dev/null
+++ b/python/git-init/ssh-keyscan.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+ssh-keyscan -H $@ >> $HOME/.ssh/known_hosts
\ No newline at end of file
diff --git a/terraform/configurations/.gitignore b/terraform/configurations/.gitignore
new file mode 100644
index 00000000..bf0824e5
--- /dev/null
+++ b/terraform/configurations/.gitignore
@@ -0,0 +1 @@
+*.log
\ No newline at end of file
diff --git a/terraform/terraform.md b/terraform/terraform.md
index e628f6c0..9bf20521 100644
--- a/terraform/terraform.md
+++ b/terraform/terraform.md
@@ -61,6 +61,15 @@ terraform apply
Point your browser to the `try_now` output value URL printed to the console at the end.
+If you want to use non ephemeral workspaces with minikube you have to mount a directory with the expected user id. Please check the existing persisted volume in Minikube for the path.\
+You might have to configure the firewall for mounting.
+
+```bash
+# This mounts the ~/tmp/minikube on the machine running minikube into minkube.
+# Check the persisted volume to find the exact /tmp/hostpath-provisioner/theia-cloud/ path
+minikube mount --uid 101 --gid 101 ~/tmp/minikube:/tmp/hostpath-provisioner/theia-cloud
+```
+
#### Destroy Minikube Cluster
First remove the persistent volume from the terraform state: