Skip to content

Commit

Permalink
Provide conversion webhook for CRDs (#283)
Browse files Browse the repository at this point in the history
Webhook is called, whenever a CR is requested in a specific version.
This allows for a better updating path, as old CRs are still useable.

[conversion] Provide conversion java project:
- A webhook that offers endpoints for the three CRD types
- Is buildable as a runnable jar
- Contains Mappers from all supported versions to a hub

[common] Add Hub and previous version for all CRDs
- The hub is a superset of all values of a CRD across all versions
- Keep a copy of the old supported versions to transform back
- This is useful, as this way the the old operator still works
- Supported from these versions:
    - `AppDefintion.v1beta8`
    - `Session.v1beta6`
    - `Workspace.v1beta3`
- To showcase the functionality update CRDs to new version:
    - Move status like fields to status:
        - `Session.v1beta7`: Move `url`, `lastActivity` and `error` fields
        from the spec to the status.
        - `Workspace.v1beta4`: Move the `error` field from the spec to the status.
        Also add the `error` field to `Workspace.v1beta3` as it was missing

    - Remove `timeout.strategy` from AppDefinition
        - `AppDefinition.v1beta9`: Removed `timeout.strategy` and `timeout.limit`
        is now just `timeout`. This was done, as there is only one Strategy left.

[operator] Adjust operator so it works with the above changes.

[service] Adjust service so it works with the above changes.

[documentation] Add build command for `conversion-webhook`

[.github] Add ci for `conversion-webhook`
- Also fix publishing of new `next-version` (reusable typo)

Contributed on behalf of STMicroelectronics

---------

Co-authored-by: Johannes Faltermeier <[email protected]>
  • Loading branch information
sgraband and jfaltermeier authored Mar 4, 2024
1 parent 5b0e390 commit 7c67236
Show file tree
Hide file tree
Showing 98 changed files with 3,552 additions and 1,842 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/ci-conversion-webhook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: conversion-webhook CI

on:
push:
branches:
- main
paths:
- "java/common/**"
- "java/conversion/**"
- "dockerfiles/conversion-webhook/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-conversion-webhook.yml"
- ".github/workflows/reusable-docker.yml"
pull_request:
branches:
- main
paths:
- "java/common/**"
- "java/conversion/**"
- "dockerfiles/conversion-webhook/**"
release:
types:
- published

jobs:
call-reusable-docker-workflow:
uses: ./.github/workflows/reusable-docker.yml
with:
docker_org: theiacloud
docker_image: theia-cloud-conversion-webhook
docker_file: dockerfiles/conversion-webhook/Dockerfile
secrets:
dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/ci-landing-page.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- "dockerfiles/landing-page/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-landing-page.yml"
- ".github/workflows/reuseable-docker.yml"
- ".github/workflows/reusable-docker.yml"
pull_request:
branches:
- main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-monitor-theia.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
- "node/monitor-theia/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-monitor-theia.yml"
- ".github/workflows/reuseable-npm.yml"
- ".github/workflows/reusable-npm.yml"
pull_request:
branches:
- main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-node-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
- "node/common/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-node-common.yml"
- ".github/workflows/reuseable-npm.yml"
- ".github/workflows/reusable-npm.yml"
pull_request:
branches:
- main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-operator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- "dockerfiles/operator/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-operator.yml"
- ".github/workflows/reuseable-docker.yml"
- ".github/workflows/reusable-docker.yml"
pull_request:
branches:
- main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- "dockerfiles/service/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-service.yml"
- ".github/workflows/reuseable-docker.yml"
- ".github/workflows/reusable-docker.yml"
pull_request:
branches:
- main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-try-now-page.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- "dockerfiles/try-now-page/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-try-now-page.yml"
- ".github/workflows/reuseable-docker.yml"
- ".github/workflows/reusable-docker.yml"
pull_request:
branches:
- main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-wondershaper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
- "dockerfiles/wondershaper/**"
# Publish when a workflow has changed (this is needed to detect version updates)
- ".github/workflows/ci-wondershaper.yml"
- ".github/workflows/reuseable-docker.yml"
- ".github/workflows/reusable-docker.yml"
pull_request:
branches:
- main
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

- [.github/workflows] Improve version detection in workflows (do not build release commits, auto-detect version for demo publishing) [#280](https://github.com/eclipsesource/theia-cloud/pull/280) - contributed on behalf of STMicroelectronics
- [node] Separate `monitor` package from other workspaces to fix bundling the extension [#280](https://github.com/eclipsesource/theia-cloud/pull/280) - contributed on behalf of STMicroelectronics
- [conversion] Provide java conversion webhook for CRD updates [#283](https://github.com/eclipsesource/theia-cloud/pull/283) | [#49](https://github.com/eclipsesource/theia-cloud-helm/pull/49) - contributed on behalf of STMicroelectronics
- [.github/workflows] Add ci for `conversion-webhook` and fix typo to build on version bumps [#283](https://github.com/eclipsesource/theia-cloud/pull/283) | [#49](https://github.com/eclipsesource/theia-cloud-helm/pull/49) - contributed on behalf of STMicroelectronics
- [common] Update CRs, keep previous version and offer Hub (used by conversion-webhook) [#283](https://github.com/eclipsesource/theia-cloud/pull/283) | [#49](https://github.com/eclipsesource/theia-cloud-helm/pull/49) - contributed on behalf of STMicroelectronics
- Move status like fields to status
- `Session.v1beta7`: Move `url`, `lastActivity` and `error` fields from the spec to the status.
- `Workspace.v1beta4`: Move the `error` field from the spec to the status. Also add the `error` field to `Workspace.v1beta3` as it was missing
- Remove `timeout.strategy` from AppDefinition
- `AppDefinition.v1beta9`: Removed `timeout.strategy` and `timeout.limit` is now just `timeout`. This was done, as there is only one Strategy left.

## [0.9.0] - 2024-01-23

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: placeholder-depname
namespace: placeholder-namespace
ownerReferences:
- apiVersion: theia.cloud/v1beta8
- apiVersion: theia.cloud/v1beta9
kind: AppDefinition
name: placeholder
uid: placeholder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: placeholder-depname
namespace: placeholder-namespace
ownerReferences:
- apiVersion: theia.cloud/v1beta8
- apiVersion: theia.cloud/v1beta9
kind: AppDefinition
name: placeholder
uid: placeholder
Expand Down
11 changes: 11 additions & 0 deletions dockerfiles/conversion-webhook/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>conversion-docker</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>
18 changes: 18 additions & 0 deletions dockerfiles/conversion-webhook/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM eclipse-temurin:17-jdk AS builder
RUN apt-get update && apt-get install -y maven
WORKDIR /conversion
COPY java/common ./common
COPY java/conversion ./conversion
RUN cd /conversion/common/maven-conf && \
mvn clean install --no-transfer-progress && \
cd /conversion/common/org.eclipse.theia.cloud.common && \
mvn clean install --no-transfer-progress&& \
cd /conversion/conversion/org.eclipse.theia.cloud.conversion && \
mvn clean package -Dmaven.test.skip=true -Dquarkus.package.type=uber-jar --no-transfer-progress

FROM eclipse-temurin:17-jre-alpine
WORKDIR /conversion
COPY --from=builder /conversion/conversion/org.eclipse.theia.cloud.conversion/target/conversion-webhook-0.10.0-SNAPSHOT-runner.jar .

ENTRYPOINT java -jar ./conversion-webhook-0.10.0-SNAPSHOT-runner.jar
CMD [ "" ]
6 changes: 6 additions & 0 deletions documentation/Building.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ docker build -t theia-cloud-try-now-page -f dockerfiles/try-now-page/Dockerfile
docker build -t theia-cloud-wondershaper -f dockerfiles/wondershaper/Dockerfile .
```

## CRD conversion webhook

```bash
docker build -t theia-cloud-conversion-webhook -f dockerfiles/conversion-webhook/Dockerfile .
```

## Demo applications

### Theia demo
Expand Down
2 changes: 1 addition & 1 deletion helm/theia.cloud/test/cdt.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: theia.cloud/v1beta8
apiVersion: theia.cloud/v1beta9
kind: AppDefinition
metadata:
name: cdt-cloud-demo
Expand Down
4 changes: 4 additions & 0 deletions java/common/maven-conf/.settings/org.eclipse.m2e.core.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
9 changes: 6 additions & 3 deletions java/common/org.eclipse.theia.cloud.common/.classpath
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,18 @@
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="test" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public DefaultSessionResourceClient(NamespacedKubernetesClient client) {
public Session create(String correlationId, SessionSpec spec) {
Session session = new Session();
session.setSpec(spec);
spec.setLastActivity(Instant.now().toEpochMilli());
session.getStatus().setLastActivity(Instant.now().toEpochMilli());
spec.setSessionSecret(UUID.randomUUID().toString());

ObjectMeta metadata = new ObjectMeta();
Expand All @@ -56,40 +56,39 @@ public Session create(String correlationId, SessionSpec spec) {
public Session launch(String correlationId, SessionSpec spec, long timeout, TimeUnit unit) {
// get or create session
Session session = get(spec.getName()).orElseGet(() -> create(correlationId, spec));
SessionSpec sessionSpec = session.getSpec();
SessionStatus sessionStatus = session.getStatus();

// if session is available and has already an url or error, return that session
if (sessionSpec.hasUrl()) {
if (sessionStatus.hasUrl()) {
return session;
}
if (sessionSpec.hasError()) {
if (sessionStatus.hasError()) {
delete(correlationId, spec.getName());
return session;
}

// wait for session url or error to be available
try {
watchUntil((action, changedSession) -> isSessionComplete(correlationId, sessionSpec, changedSession),
timeout, unit);
watchUntil((action, changedSession) -> isSessionComplete(correlationId, session, changedSession), timeout,
unit);
} catch (InterruptedException exception) {
error(correlationId, "Timeout while waiting for URL for " + spec.getName(), exception);
sessionSpec.setError(TheiaCloudError.SESSION_LAUNCH_TIMEOUT);
sessionStatus.setError(TheiaCloudError.SESSION_LAUNCH_TIMEOUT);
}
return session;
}

protected boolean isSessionComplete(String correlationId, SessionSpec sessionSpec,
Session changedSession) {
if (sessionSpec.getName().equals(changedSession.getSpec().getName())) {
if (changedSession.getSpec().hasUrl()) {
protected boolean isSessionComplete(String correlationId, Session session, Session changedSession) {
if (session.getSpec().getName().equals(changedSession.getSpec().getName())) {
if (changedSession.getStatus().hasUrl()) {
info(correlationId, "Received URL for " + changedSession);
sessionSpec.setUrl(changedSession.getSpec().getUrl());
session.getStatus().setUrl(changedSession.getStatus().getUrl());
return true;
}
if (changedSession.getSpec().hasError()) {
if (changedSession.getStatus().hasError()) {
info(correlationId, "Received Error for " + changedSession + ". Deleting session again.");
delete(correlationId, sessionSpec.getName());
sessionSpec.setError(changedSession.getSpec().getError());
delete(correlationId, session.getSpec().getName());
session.getStatus().setError(changedSession.getStatus().getError());
return true;
}
}
Expand All @@ -100,7 +99,7 @@ protected boolean isSessionComplete(String correlationId, SessionSpec sessionSpe
public boolean reportActivity(String correlationId, String name) {
return edit(correlationId, name, session -> {
trace(correlationId, "Updating activity for session {" + name + "}");
session.getSpec().setLastActivity(Instant.now().toEpochMilli());
session.getStatus().setLastActivity(Instant.now().toEpochMilli());
}) != null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,40 +50,40 @@ public Workspace create(String correlationId, WorkspaceSpec spec) {
public Workspace launch(String correlationId, WorkspaceSpec spec, long timeout, TimeUnit unit) {
Workspace workspace = get(spec.getName()).orElseGet(() -> create(correlationId, spec));
WorkspaceSpec workspaceSpec = workspace.getSpec();
WorkspaceStatus workspaceStatus = workspace.getStatus();

if (workspaceSpec.hasStorage()) {
return workspace;
}

if (workspaceSpec.hasError()) {
if (workspaceStatus.hasError()) {
delete(correlationId, spec.getName());
return workspace;
}

try {
watchUntil(
(action, changedWorkspace) -> isWorkspaceComplete(correlationId, workspaceSpec, changedWorkspace),
watchUntil((action, changedWorkspace) -> isWorkspaceComplete(correlationId, workspace, changedWorkspace),
timeout, unit);
} catch (InterruptedException exception) {
error(correlationId, "Timeout while waiting for workspace storage " + workspaceSpec.getName()
+ ". Deleting workspace again.", exception);
workspaceSpec.setError(TheiaCloudError.WORKSPACE_LAUNCH_TIMEOUT);
workspaceStatus.setError(TheiaCloudError.WORKSPACE_LAUNCH_TIMEOUT);
}
return workspace;
}

protected boolean isWorkspaceComplete(String correlationId, WorkspaceSpec createdWorkspace,
protected boolean isWorkspaceComplete(String correlationId, Workspace createdWorkspace,
Workspace changedWorkspace) {
if (createdWorkspace.getName().equals(changedWorkspace.getSpec().getName())) {
if (createdWorkspace.getSpec().getName().equals(changedWorkspace.getSpec().getName())) {
if (changedWorkspace.getSpec().hasStorage()) {
info(correlationId, "Received URL for " + createdWorkspace);
createdWorkspace.setStorage(changedWorkspace.getSpec().getStorage());
createdWorkspace.getSpec().setStorage(changedWorkspace.getSpec().getStorage());
return true;
}
if (changedWorkspace.getSpec().hasError()) {
if (changedWorkspace.getStatus().hasError()) {
info(correlationId, "Received Error for " + changedWorkspace + ". Deleting workspace again.");
delete(correlationId, createdWorkspace.getName());
createdWorkspace.setError(changedWorkspace.getSpec().getError());
delete(correlationId, createdWorkspace.getSpec().getName());
createdWorkspace.getStatus().setError(changedWorkspace.getStatus().getError());
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
import io.fabric8.kubernetes.model.annotation.Singular;
import io.fabric8.kubernetes.model.annotation.Version;

@Version("v1beta8")
@Version("v1beta9")
@Group("theia.cloud")
@Kind("AppDefinition")
@Singular("appdefinition")
@Plural("appdefinitions")
public class AppDefinition extends CustomResource<AppDefinitionSpec, AppDefinitionStatus> implements Namespaced {

private static final long serialVersionUID = 8749670583218521755L;
public static final String API = "theia.cloud/v1beta8";
public static final String API = "theia.cloud/v1beta9";
public static final String KIND = "AppDefinition";
public static final String CRD_NAME = "appdefinitions.theia.cloud";

Expand All @@ -49,11 +49,11 @@ public AppDefinition() {
}

public AppDefinition(AppDefinitionHub fromHub) {
this.setMetadata(fromHub.getMetadata());
this.spec = new AppDefinitionSpec(fromHub.getSpec());
if (fromHub.getStatus() != null) {
this.status = new AppDefinitionStatus(fromHub.getStatus());
if (fromHub.getMetadata().isPresent()) {
this.setMetadata(fromHub.getMetadata().get());
}
this.spec = new AppDefinitionSpec(fromHub);
this.status = new AppDefinitionStatus(fromHub);
}

}
Loading

0 comments on commit 7c67236

Please sign in to comment.