Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into owner
Browse files Browse the repository at this point in the history
  • Loading branch information
olevitt committed Jun 10, 2024
2 parents 7dda39d + 9e998f7 commit 61d865f
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public HelmInstaller installChart(
InterruptedException,
TimeoutException,
IllegalArgumentException {
StringBuilder command = new StringBuilder("helm upgrade --install ");
StringBuilder command = new StringBuilder("helm upgrade --install --history-max 0 ");
if (skipTlsVerify) {
command.append("--insecure-skip-tls-verify ");
} else if (caFile != null) {
Expand Down
4 changes: 2 additions & 2 deletions onyxia-api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
FROM eclipse-temurin:21.0.2_13-jre as extract
FROM eclipse-temurin:21.0.3_9-jre as extract
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM eclipse-temurin:21.0.2_13-jre
FROM eclipse-temurin:21.0.3_9-jre
WORKDIR /app
# Install helm
RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | DESIRED_VERSION=v3.14.2 bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,40 @@ public Service getApp(
return null;
}

@PostMapping("/app/rename")
public void renameApp(
@Parameter(hidden = true) Region region,
@Parameter(hidden = true) Project project,
@RequestBody RenameRequestDTO request)
throws Exception {
if (Service.ServiceType.KUBERNETES.equals(region.getServices().getType())) {
User user = userProvider.getUser(region);
helmAppsService.rename(
region,
project,
userProvider.getUser(region),
request.getServiceID(),
request.getFriendlyName());
}
}

@PostMapping("/app/share")
public void shareApp(
@Parameter(hidden = true) Region region,
@Parameter(hidden = true) Project project,
@RequestBody ShareRequestDTO request)
throws Exception {
if (Service.ServiceType.KUBERNETES.equals(region.getServices().getType())) {
User user = userProvider.getUser(region);
helmAppsService.share(
region,
project,
userProvider.getUser(region),
request.getServiceID(),
request.isShare());
}
}

@PostMapping("/app/suspend")
public void suspendApp(
@Parameter(hidden = true) Region region,
Expand Down Expand Up @@ -456,4 +490,46 @@ public void setServiceID(String serviceID) {
this.serviceID = serviceID;
}
}

public static class RenameRequestDTO {
private String serviceID;
private String friendlyName;

public String getServiceID() {
return serviceID;
}

public void setServiceID(String serviceID) {
this.serviceID = serviceID;
}

public String getFriendlyName() {
return friendlyName;
}

public void setFriendlyName(String friendlyName) {
this.friendlyName = friendlyName;
}
}

public static class ShareRequestDTO {
private String serviceID;
private boolean share;

public String getServiceID() {
return serviceID;
}

public void setServiceID(String serviceID) {
this.serviceID = serviceID;
}

public boolean isShare() {
return share;
}

public void setShare(boolean share) {
this.share = share;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ UninstallService destroyService(
Watch getEvents(Region region, Project project, User user, Watcher<Event> watcher)
throws HelmInstallService.MultipleServiceFound, ParseException;

void rename(Region region, Project project, User user, String serviceId, String friendlyName)
throws IOException, InterruptedException, TimeoutException;

void share(Region region, Project project, User user, String serviceId, boolean share)
throws IOException, InterruptedException, TimeoutException;

void resume(
Region region,
Project project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.OwnerReference;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
Expand Down Expand Up @@ -82,6 +83,8 @@ public class HelmAppsService implements AppsService {

final OnyxiaEventPublisher onyxiaEventPublisher;

public static final String ONYXIA_SECRET_PREFIX = "sh.onyxia.release.v1.";

@Autowired
public HelmAppsService(
@Qualifier("helm") ObjectMapper mapperHelm,
Expand Down Expand Up @@ -214,6 +217,15 @@ public String getInitScript(
pkg.getName(),
catalogId);
onyxiaEventPublisher.publishEvent(installServiceEvent);
Map<String, String> metadata = new HashMap<>();
metadata.put("catalog", catalogId);
metadata.put("owner", user.getIdep());
if (requestDTO.getFriendlyName() != null) {
metadata.put("friendlyName", requestDTO.getFriendlyName());
}
metadata.put("share", String.valueOf(requestDTO.isShare()));
kubernetesService.createOnyxiaSecret(
region, namespaceId, requestDTO.getName(), metadata);
return List.of(res.getManifest());
} catch (IllegalArgumentException e) {
throw new AccessDeniedException(e.getMessage());
Expand Down Expand Up @@ -550,6 +562,31 @@ private Service getHelmApp(Region region, User user, HelmLs release) {
} catch (Exception e) {
service.setStartedAt(0);
}
try {
KubernetesClient client = kubernetesClientProvider.getUserClient(region, user);
Secret secret =
client.secrets()
.inNamespace(release.getNamespace())
.withName(ONYXIA_SECRET_PREFIX + release.getName())
.get();
if (secret != null && secret.getStringData() != null) {
Map<String, String> stringData = secret.getStringData();
if (stringData.containsKey("friendlyName")) {
service.setFriendlyName(stringData.get("friendlyName"));
}
if (stringData.containsKey("owner")) {
service.setOwner(stringData.get("owner"));
}
if (stringData.containsKey("catalog")) {
service.setCatalogId(stringData.get("catalog"));
}
if (stringData.containsKey("share")) {
service.setShare(Boolean.parseBoolean(stringData.get("share")));
}
}
} catch (Exception e) {
LOGGER.warn("Exception occurred", e);
}
service.setId(release.getName());
service.setName(release.getName());
service.setSubtitle(release.getChart());
Expand Down Expand Up @@ -586,6 +623,46 @@ private Service getHelmApp(Region region, User user, HelmLs release) {
return service;
}

@Override
public void rename(
Region region, Project project, User user, String serviceId, String friendlyName)
throws IOException, InterruptedException, TimeoutException {
patchOnyxiaSecret(region, project, user, serviceId, Map.of("friendlyName", friendlyName));
}

@Override
public void share(Region region, Project project, User user, String serviceId, boolean share)
throws IOException, InterruptedException, TimeoutException {
patchOnyxiaSecret(region, project, user, serviceId, Map.of("share", String.valueOf(share)));
}

private void patchOnyxiaSecret(
Region region, Project project, User user, String serviceId, Map<String, String> data) {
String namespaceId =
kubernetesService.determineNamespaceAndCreateIfNeeded(region, project, user);
KubernetesClient client = kubernetesClientProvider.getUserClient(region, user);
Secret secret =
client.secrets()
.inNamespace(namespaceId)
.withName(ONYXIA_SECRET_PREFIX + serviceId)
.get();
if (secret != null) {
Map<String, String> secretData = secret.getStringData();
if (secretData == null) {
// Initialize the map if it's null
secretData = new HashMap<>();
}
secretData.putAll(data);
secret.setStringData(secretData);
client.secrets().inNamespace(namespaceId).resource(secret).serverSideApply();
} else {
Map<String, String> metadata = new HashMap<>();
metadata.put("owner", user.getIdep());
metadata.putAll(data);
kubernetesService.createOnyxiaSecret(region, namespaceId, serviceId, metadata);
}
}

@Override
public void suspend(
Region region,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
import fr.insee.onyxia.api.controller.exception.NamespaceNotFoundException;
import fr.insee.onyxia.api.events.InitNamespaceEvent;
import fr.insee.onyxia.api.events.OnyxiaEventPublisher;
import fr.insee.onyxia.api.services.impl.HelmAppsService;
import fr.insee.onyxia.model.User;
import fr.insee.onyxia.model.project.Project;
import fr.insee.onyxia.model.region.Region;
import fr.insee.onyxia.model.service.quota.Quota;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.ResourceQuota;
import io.fabric8.kubernetes.api.model.ResourceQuotaBuilder;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
import io.fabric8.kubernetes.api.model.rbac.SubjectBuilder;
Expand Down Expand Up @@ -219,6 +217,43 @@ private void applyQuotas(
}
}

public void createOnyxiaSecret(
Region region, String namespaceId, String releaseName, Map<String, String> secretData) {

final KubernetesClient kubClient = kubernetesClientProvider.getRootClient(region);
String ownerSecretName =
"sh.helm.release.v1." + releaseName + ".v1"; // Name of the Secret managed by Helm

// Fetch the existing secret managed by Helm
Secret ownerSecret =
kubClient.secrets().inNamespace(namespaceId).withName(ownerSecretName).get();

// Create owner reference
OwnerReference ownerReference =
new OwnerReferenceBuilder()
.withApiVersion(ownerSecret.getApiVersion())
.withKind(ownerSecret.getKind())
.withName(ownerSecret.getMetadata().getName())
.withUid(ownerSecret.getMetadata().getUid())
.withController(true) // Optional: Specifies that this owner is a controller
.build();

// Define the new secret
Secret newSecret =
new SecretBuilder()
.withNewMetadata()
.withName(HelmAppsService.ONYXIA_SECRET_PREFIX + releaseName)
.addNewOwnerReferenceLike(ownerReference)
.endOwnerReference()
.endMetadata()
.addToStringData(secretData)
.withType("onyxia.sh/release.v1")
.build();

// Create the secret in Kubernetes
newSecret = kubClient.secrets().inNamespace(namespaceId).resource(newSecret).create();
}

private String getNameFromOwner(Region region, Owner owner) {
String username = owner.getId();
if (owner.getType() == Owner.OwnerType.USER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ public class CreateServiceDTO {
defaultValue = "false")
boolean dryRun = false;

@Schema(
description =
"When true, all users of the namespace will list this service, false by default",
defaultValue = "false")
boolean share = false;

@Schema(description = "A friendly name for the service")
String friendlyName;

public String getCatalogId() {
return catalogId;
}
Expand Down Expand Up @@ -78,4 +87,20 @@ public boolean isDryRun() {
public void setDryRun(boolean dryRun) {
this.dryRun = dryRun;
}

public boolean isShare() {
return share;
}

public void setShare(boolean share) {
this.share = share;
}

public String getFriendlyName() {
return friendlyName;
}

public void setFriendlyName(String friendlyName) {
this.friendlyName = friendlyName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ public class Service {
@Schema(description = "Is this service suspended ?")
private boolean suspended = false;

@Schema(description = "CatalogId")
private String catalogId;

@Schema(description = "userID who starts the service")
private String owner;

@Schema(description = "friendly name of the service")
private String friendlyName;

@Schema(description = "Is this service shared ?")
private boolean share = false;

@Schema(description = "")
private Map<String, String> labels;

Expand Down Expand Up @@ -285,6 +297,38 @@ public void setTemplates(List<Object> templates) {
this.templates = templates;
}

public String getCatalogId() {
return catalogId;
}

public void setCatalogId(String catalogId) {
this.catalogId = catalogId;
}

public boolean isShare() {
return share;
}

public void setShare(boolean share) {
this.share = share;
}

public String getFriendlyName() {
return friendlyName;
}

public void setFriendlyName(String friendlyName) {
this.friendlyName = friendlyName;
}

public String getOwner() {
return owner;
}

public void setOwner(String owner) {
this.owner = owner;
}

@Schema(description = "")
public static enum ServiceStatus {
DEPLOYING,
Expand Down
Loading

0 comments on commit 61d865f

Please sign in to comment.