You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/07/27 22:22:47 UTC
[camel-karavan] 02/03: devservices #817
This is an automated email from the ASF dual-hosted git repository.
marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 44a0674263bcf7ffb282e783cfac6a5572cc35cc
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 27 16:39:12 2023 -0400
devservices #817
---
.../apache/camel/karavan/api/DevModeResource.java | 16 +-
.../camel/karavan/api/InfrastructureResource.java | 48 ++++-
.../apache/camel/karavan/docker/DockerService.java | 205 ++++++------------
.../camel/karavan/docker/DockerServiceUtils.java | 229 +++++++++++++++++++++
.../camel/karavan/docker/model/DevService.java | 103 +++++++++
.../karavan/docker/model/HealthCheckConfig.java | 55 +++++
.../apache/camel/karavan/service/CodeService.java | 5 +-
.../camel/karavan/service/ProjectService.java | 11 +-
.../camel/karavan/service/ScheduledService.java | 11 +-
.../src/main/webui/src/api/KaravanApi.tsx | 8 +-
.../src/main/webui/src/api/ProjectModels.ts | 2 +-
.../src/main/webui/src/api/ProjectService.ts | 66 +++---
.../src/main/webui/src/api/ServiceModels.ts | 13 +-
.../webui/src/containers/ContainerTableRow.tsx | 12 +-
.../main/webui/src/containers/ContainersPage.tsx | 32 +--
.../webui/src/project/pipeline/ProjectStatus.tsx | 2 +-
.../src/main/webui/src/services/ServicesPage.tsx | 33 ++-
.../main/webui/src/services/ServicesTableRow.tsx | 91 ++++++--
.../karavan/infinispan/InfinispanService.java | 6 -
.../karavan/infinispan/model/ContainerStatus.java | 4 +
20 files changed, 680 insertions(+), 272 deletions(-)
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
index d6d647c9..8466fe42 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
@@ -35,6 +35,8 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Objects;
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
+
@Path("/api/devmode")
public class DevModeResource {
@@ -74,7 +76,8 @@ public class DevModeResource {
if (ConfigService.inKubernetes()) {
kubernetesService.runDevModeContainer(project, jBangOptions);
} else {
- dockerService.runDevmodeContainer(project, jBangOptions);
+ dockerService.createDevmodeContainer(project.getProjectId(), jBangOptions);
+ dockerService.runContainer(project.getProjectId());
}
return Response.ok(containerName).build();
}
@@ -105,7 +108,7 @@ public class DevModeResource {
@Consumes(MediaType.APPLICATION_JSON)
@Path("/{projectId}/{deletePVC}")
public Response deleteDevMode(@PathParam("projectId") String projectId, @PathParam("deletePVC") boolean deletePVC) {
- infinispanService.setContainerStatusTransit(projectId, environment, projectId);
+ setContainerStatusTransit(projectId, ContainerStatus.ContainerType.devmode.name());
if (ConfigService.inKubernetes()) {
kubernetesService.deleteDevModePod(projectId, deletePVC);
} else {
@@ -114,6 +117,15 @@ public class DevModeResource {
return Response.accepted().build();
}
+ private void setContainerStatusTransit(String name, String type){
+ ContainerStatus status = infinispanService.getContainerStatus(name, environment, name);
+ if (status == null) {
+ status = ContainerStatus.createByType(name, environment, ContainerStatus.ContainerType.valueOf(type));
+ }
+ status.setInTransit(true);
+ eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status));
+ }
+
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/container/{projectId}")
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
index 8291c7eb..74ffd6a8 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
@@ -21,12 +21,14 @@ import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.eventbus.EventBus;
import io.vertx.mutiny.core.eventbus.Message;
import org.apache.camel.karavan.docker.DockerService;
+import org.apache.camel.karavan.docker.model.DevService;
import org.apache.camel.karavan.infinispan.InfinispanService;
import org.apache.camel.karavan.infinispan.model.ContainerStatus;
import org.apache.camel.karavan.infinispan.model.DeploymentStatus;
import org.apache.camel.karavan.infinispan.model.Project;
import org.apache.camel.karavan.infinispan.model.ServiceStatus;
import org.apache.camel.karavan.kubernetes.KubernetesService;
+import org.apache.camel.karavan.service.ProjectService;
import org.apache.camel.karavan.shared.ConfigService;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
@@ -40,6 +42,8 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
+
@Path("/api/infrastructure")
public class InfrastructureResource {
@@ -55,6 +59,9 @@ public class InfrastructureResource {
@Inject
DockerService dockerService;
+ @Inject
+ ProjectService projectService;
+
@ConfigProperty(name = "karavan.environment")
String environment;
@@ -166,23 +173,47 @@ public class InfrastructureResource {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
- @Path("/container/{env}/{name}")
- public Response startContainer(@PathParam("env") String env, @PathParam("name") String name, JsonObject command) throws Exception {
+ @Path("/container/{env}/{type}/{name}")
+ public Response startContainer(@PathParam("env") String env, @PathParam("type") String type, @PathParam("name") String name, JsonObject command) throws Exception {
if (infinispanService.isReady()) {
- infinispanService.setContainerStatusTransit(name, env, name);
+ // set container statuses
+ setContainerStatusTransit(name, type);
+ // exec docker commands
if (command.containsKey("command")) {
- if (command.getString("command").equalsIgnoreCase("start")) {
- dockerService.startContainer(name);
+ if (command.getString("command").equalsIgnoreCase("run")) {
+ if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
+ String code = projectService.getDevServiceCode();
+ DevService devService = dockerService.getDevService(code, name);
+ if (devService != null) {
+ dockerService.createDevserviceContainer(devService);
+ dockerService.runContainer(devService.getContainer_name());
+ }
+ } else if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
+ dockerService.createDevmodeContainer(name, "");
+ dockerService.runContainer(name);
+ }
return Response.ok().build();
} else if (command.getString("command").equalsIgnoreCase("stop")) {
dockerService.stopContainer(name);
return Response.ok().build();
+ } else if (command.getString("command").equalsIgnoreCase("pause")) {
+ dockerService.pauseContainer(name);
+ return Response.ok().build();
}
}
}
return Response.notModified().build();
}
+ private void setContainerStatusTransit(String name, String type){
+ ContainerStatus status = infinispanService.getContainerStatus(name, environment, name);
+ if (status == null) {
+ status = ContainerStatus.createByType(name, environment, ContainerStatus.ContainerType.valueOf(type));
+ }
+ status.setInTransit(true);
+ eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status));
+ }
+
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/container/{env}")
@@ -205,10 +236,11 @@ public class InfrastructureResource {
@DELETE
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
- @Path("/container/{env}/{name}")
- public Response deleteContainer(@PathParam("env") String env, @PathParam("name") String name) {
+ @Path("/container/{env}/{type}/{name}")
+ public Response deleteContainer(@PathParam("env") String env, @PathParam("type") String type, @PathParam("name") String name) {
if (infinispanService.isReady()) {
- infinispanService.setContainerStatusTransit(name, env, name);
+ // set container statuses
+ setContainerStatusTransit(name, type);
try {
if (ConfigService.inKubernetes()) {
kubernetesService.deletePod(name, kubernetesService.getNamespace());
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
index af1d67f6..03e8b378 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
@@ -18,6 +18,7 @@ package org.apache.camel.karavan.docker;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.CreateNetworkResponse;
import com.github.dockerjava.api.command.HealthState;
@@ -30,11 +31,14 @@ import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import io.smallrye.mutiny.tuples.Tuple2;
import io.vertx.core.eventbus.EventBus;
-import org.apache.camel.karavan.infinispan.InfinispanService;
+import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.docker.model.DevService;
import org.apache.camel.karavan.infinispan.model.ContainerStatus;
import org.apache.camel.karavan.infinispan.model.Project;
+import org.apache.camel.karavan.service.CodeService;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
+import org.yaml.snakeyaml.Yaml;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@@ -49,7 +53,7 @@ import static org.apache.camel.karavan.shared.Constants.*;
import static org.apache.camel.karavan.shared.EventType.*;
@ApplicationScoped
-public class DockerService {
+public class DockerService extends DockerServiceUtils {
private static final Logger LOGGER = Logger.getLogger(DockerService.class.getName());
@@ -57,10 +61,7 @@ public class DockerService {
protected static final String KARAVAN_CONTAINER_NAME = "karavan-headless";
protected static final String NETWORK_NAME = "karavan";
- private static final DecimalFormat formatCpu = new DecimalFormat("0.00");
- private static final DecimalFormat formatMiB = new DecimalFormat("0.0");
- private static final DecimalFormat formatGiB = new DecimalFormat("0.00");
- private static final Map<String, Tuple2<Long, Long>> previousStats = new ConcurrentHashMap<>();
+
private static final List<String> infinispanHealthCheckCMD = List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status");
@ConfigProperty(name = "karavan.environment")
@@ -100,25 +101,37 @@ public class DockerService {
}
}
- public void runDevmodeContainer(Project project, String jBangOptions) throws InterruptedException {
- String projectId = project.getProjectId();
+ public void createDevmodeContainer(String projectId, String jBangOptions) throws InterruptedException {
LOGGER.infof("DevMode starting for %s with JBANG_OPTIONS=%s", projectId, jBangOptions);
HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:8080/q/dev/health"))
.withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
- List<String> env = jBangOptions !=null && !jBangOptions.trim().isEmpty()
+ List<String> env = jBangOptions != null && !jBangOptions.trim().isEmpty()
? List.of(ENV_VAR_JBANG_OPTIONS + "=" + jBangOptions)
: List.of();
createContainer(projectId, devmodeImage,
- env, null, false, false, healthCheck,
+ env, null, false, List.of(), healthCheck,
Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId));
- startContainer(projectId);
LOGGER.infof("DevMode started for %s", projectId);
}
+ public void createDevserviceContainer(DevService devService) throws InterruptedException {
+ LOGGER.infof("DevService starting for ", devService.getContainer_name());
+
+ HealthCheck healthCheck = getHealthCheck(devService.getHealthcheck());
+ List<String> env = devService.getEnvironment() != null ? devService.getEnvironmentList() : List.of();
+ String ports = String.join(",", devService.getPorts());
+
+ createContainer(devService.getContainer_name(), devService.getImage(),
+ env, ports, false, devService.getExpose(), healthCheck,
+ Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devservice.name()));
+
+ LOGGER.infof("DevService started for %s", devService.getContainer_name());
+ }
+
public void startInfinispan() {
try {
LOGGER.info("Infinispan is starting...");
@@ -126,12 +139,14 @@ public class DockerService {
HealthCheck healthCheck = new HealthCheck().withTest(infinispanHealthCheckCMD)
.withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
+ List<String> exposedPorts = List.of(infinispanPort.split(":")[0]);
+
createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword),
- infinispanPort, false, true, healthCheck,
+ infinispanPort, false, exposedPorts, healthCheck,
Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name()));
- startContainer(INFINISPAN_CONTAINER_NAME);
+ runContainer(INFINISPAN_CONTAINER_NAME);
LOGGER.info("Infinispan is started");
} catch (Exception e) {
LOGGER.error(e.getMessage());
@@ -148,10 +163,10 @@ public class DockerService {
"INFINISPAN_USERNAME=" + infinispanUsername,
"INFINISPAN_PASSWORD=" + infinispanPassword
),
- null, false, false, new HealthCheck(),
+ null, false, List.of(), new HealthCheck(),
Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name()));
- startContainer(KARAVAN_CONTAINER_NAME);
+ runContainer(KARAVAN_CONTAINER_NAME);
LOGGER.info("Karavan headless is started");
} catch (Exception e) {
LOGGER.error(e.getMessage());
@@ -171,7 +186,8 @@ public class DockerService {
List<ContainerStatus> result = new ArrayList<>();
getDockerClient().listContainersCmd().withShowAll(true).exec().forEach(container -> {
ContainerStatus containerStatus = getContainerStatus(container);
- updateStatistics(containerStatus, container);
+ Statistics stats = getContainerStats(container.getId());
+ updateStatistics(containerStatus, container, stats);
result.add(containerStatus);
});
return result;
@@ -186,16 +202,6 @@ public class DockerService {
return ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, commands, container.getState(), created);
}
- private void updateStatistics(ContainerStatus containerStatus, Container container) {
- Statistics stats = getContainerStats(container.getId());
- if (stats != null && stats.getMemoryStats() != null) {
- String memoryUsage = formatMemory(stats.getMemoryStats().getUsage());
- String memoryLimit = formatMemory(stats.getMemoryStats().getLimit());
- containerStatus.setMemoryInfo(memoryUsage + " / " + memoryLimit);
- containerStatus.setCpuInfo(formatCpu(containerStatus.getContainerName(), stats));
- }
- }
-
public void startListeners() {
getDockerClient().eventsCmd().exec(dockerEventListener);
}
@@ -254,22 +260,23 @@ public class DockerService {
}
public Container createContainer(String name, String image, List<String> env, String ports, boolean inRange,
- boolean exposedPort, HealthCheck healthCheck, Map<String, String> labels) throws InterruptedException {
+ List<String> exposed, HealthCheck healthCheck, Map<String, String> labels) throws InterruptedException {
List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
if (containers.size() == 0) {
pullImage(image);
- List<ExposedPort> exposedPorts = getPortsFromString(ports).values().stream().map(i -> ExposedPort.tcp(i)).collect(Collectors.toList());
-
- CreateContainerResponse response = getDockerClient().createContainerCmd(image)
- .withName(name)
- .withLabels(labels)
- .withEnv(env)
- .withExposedPorts(exposedPorts)
- .withHostName(name)
- .withHostConfig(getHostConfig(ports, exposedPort, inRange))
- .withHealthcheck(healthCheck)
- .exec();
+ CreateContainerCmd createContainerCmd = getDockerClient().createContainerCmd(image)
+ .withName(name).withLabels(labels).withEnv(env).withHostName(name).withHealthcheck(healthCheck);
+
+ if (exposed != null) {
+ List<ExposedPort> exposedPorts = exposed.stream().map(i -> ExposedPort.tcp(Integer.parseInt(i))).collect(Collectors.toList());
+ createContainerCmd.withExposedPorts(exposedPorts);
+ createContainerCmd.withHostConfig(getHostConfig(ports, exposedPorts, inRange, NETWORK_NAME));
+ } else {
+ createContainerCmd.withHostConfig(getHostConfig(ports, List.of(), inRange, NETWORK_NAME));
+ }
+
+ CreateContainerResponse response = createContainerCmd.exec();
LOGGER.info("Container created: " + response.getId());
return getDockerClient().listContainersCmd().withShowAll(true)
.withIdFilter(Collections.singleton(response.getId())).exec().get(0);
@@ -279,21 +286,18 @@ public class DockerService {
}
}
- public void startContainer(String name) throws InterruptedException {
+ public void runContainer(String name) {
List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
if (containers.size() == 1) {
Container container = containers.get(0);
- if (!container.getState().equals("running")) {
+ if (container.getState().equals("paused")) {
+ getDockerClient().unpauseContainerCmd(container.getId()).exec();
+ } else if (!container.getState().equals("running")) {
getDockerClient().startContainerCmd(container.getId()).exec();
}
}
}
- public void restartContainer(String name) throws InterruptedException {
- stopContainer(name);
- startContainer(name);
- }
-
public void logContainer(String containerName, LogCallback callback) {
try {
Container container = getContainerByName(containerName);
@@ -312,6 +316,16 @@ public class DockerService {
}
}
+ public void pauseContainer(String name) {
+ List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+ if (containers.size() == 1) {
+ Container container = containers.get(0);
+ if (container.getState().equals("running")) {
+ getDockerClient().pauseContainerCmd(container.getId()).exec();
+ }
+ }
+ }
+
public void stopContainer(String name) {
List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
if (containers.size() == 1) {
@@ -342,31 +356,6 @@ public class DockerService {
}
}
- private HostConfig getHostConfig(String ports, boolean exposedPort, boolean inRange) {
- Ports portBindings = new Ports();
-
- getPortsFromString(ports).forEach((hostPort, containerPort) -> {
- Ports.Binding binding = exposedPort
- ? (inRange ? Ports.Binding.bindPortRange(hostPort, hostPort + 1000) : Ports.Binding.bindPort(hostPort))
- : Ports.Binding.bindPort(hostPort);
- portBindings.bind(ExposedPort.tcp(containerPort), binding);
- });
- return new HostConfig()
- .withPortBindings(portBindings)
- .withNetworkMode(NETWORK_NAME);
- }
-
- private Map<Integer, Integer> getPortsFromString(String ports) {
- Map<Integer, Integer> p = new HashMap<>();
- if (ports != null && !ports.isEmpty()) {
- Arrays.stream(ports.split(",")).forEach(s -> {
- String[] values = s.split(":");
- p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
- });
- }
- return p;
- }
-
private DockerClientConfig getDockerClientConfig() {
return DefaultDockerClientConfig.createDefaultConfigBuilder().build();
}
@@ -387,80 +376,4 @@ public class DockerService {
}
return dockerClient;
}
-
- private String formatMemory(Long memory) {
- try {
- if (memory < (1073741824)) {
- return formatMiB.format(memory.doubleValue() / 1048576) + "MiB";
- } else {
- return formatGiB.format(memory.doubleValue() / 1073741824) + "GiB";
- }
- } catch (Exception e) {
- return "";
- }
- }
-
- private ContainerStatus.ContainerType getContainerType(Map<String, String> labels) {
- String type = labels.get(LABEL_TYPE);
- if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
- return ContainerStatus.ContainerType.devmode;
- } else if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
- return ContainerStatus.ContainerType.devservice;
- } else if (Objects.equals(type, ContainerStatus.ContainerType.project.name())) {
- return ContainerStatus.ContainerType.project;
- } else if (Objects.equals(type, ContainerStatus.ContainerType.internal.name())) {
- return ContainerStatus.ContainerType.internal;
- }
- return ContainerStatus.ContainerType.unknown;
- }
-
- private List<ContainerStatus.Command> getContainerCommand(String state) {
- List<ContainerStatus.Command> result = new ArrayList<>();
- if (Objects.equals(state, ContainerStatus.State.created.name())) {
- result.add(ContainerStatus.Command.run);
- result.add(ContainerStatus.Command.delete);
- } else if (Objects.equals(state, ContainerStatus.State.exited.name())) {
- result.add(ContainerStatus.Command.run);
- result.add(ContainerStatus.Command.delete);
- } else if (Objects.equals(state, ContainerStatus.State.running.name())) {
- result.add(ContainerStatus.Command.pause);
- result.add(ContainerStatus.Command.stop);
- result.add(ContainerStatus.Command.delete);
- } else if (Objects.equals(state, ContainerStatus.State.paused.name())) {
- result.add(ContainerStatus.Command.run);
- result.add(ContainerStatus.Command.stop);
- result.add(ContainerStatus.Command.delete);
- } else if (Objects.equals(state, ContainerStatus.State.dead.name())) {
- result.add(ContainerStatus.Command.delete);
- }
- return result;
- }
-
- private String formatCpu(String containerName, Statistics stats) {
- try {
- double cpuUsage = 0;
- long previousCpu = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem1() : -1;
- long previousSystem = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem2() : -1;
-
- CpuStatsConfig cpuStats = stats.getCpuStats();
- if (cpuStats != null) {
- CpuUsageConfig cpuUsageConfig = cpuStats.getCpuUsage();
- long systemUsage = cpuStats.getSystemCpuUsage();
- long totalUsage = cpuUsageConfig.getTotalUsage();
-
- if (previousCpu != -1 && previousSystem != -1) {
- float cpuDelta = totalUsage - previousCpu;
- float systemDelta = systemUsage - previousSystem;
-
- if (cpuDelta > 0 && systemDelta > 0) {
- cpuUsage = cpuDelta / systemDelta * cpuStats.getOnlineCpus() * 100;
- }
- }
- previousStats.put(containerName, Tuple2.of(totalUsage, systemUsage));
- }
- return formatCpu.format(cpuUsage) + "%";
- } catch (Exception e) {
- return "";
- }
- }
}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
new file mode 100644
index 00000000..748c17bc
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.karavan.docker;
+
+import com.github.dockerjava.api.model.*;
+import io.smallrye.mutiny.tuples.Tuple2;
+import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.api.KameletResources;
+import org.apache.camel.karavan.docker.model.DevService;
+import org.apache.camel.karavan.docker.model.HealthCheckConfig;
+import org.apache.camel.karavan.infinispan.model.ContainerStatus;
+import org.apache.camel.karavan.service.CodeService;
+import org.yaml.snakeyaml.Yaml;
+
+import javax.inject.Inject;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE;
+
+public class DockerServiceUtils {
+
+ protected static final DecimalFormat formatCpu = new DecimalFormat("0.00");
+ protected static final DecimalFormat formatMiB = new DecimalFormat("0.0");
+ protected static final DecimalFormat formatGiB = new DecimalFormat("0.00");
+ protected static final Map<String, Tuple2<Long, Long>> previousStats = new ConcurrentHashMap<>();
+
+ @Inject
+ CodeService codeService;
+
+
+ protected ContainerStatus getContainerStatus(Container container, String environment) {
+ String name = container.getNames()[0].replace("/", "");
+ List<Integer> ports = Arrays.stream(container.getPorts()).map(ContainerPort::getPrivatePort).filter(Objects::nonNull).collect(Collectors.toList());
+ List<ContainerStatus.Command> commands = getContainerCommand(container.getState());
+ ContainerStatus.ContainerType type = getContainerType(container.getLabels());
+ String created = Instant.ofEpochSecond(container.getCreated()).toString();
+ return ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, commands, container.getState(), created);
+ }
+
+ protected void updateStatistics(ContainerStatus containerStatus, Container container, Statistics stats) {
+ if (stats != null && stats.getMemoryStats() != null) {
+ String memoryUsage = formatMemory(stats.getMemoryStats().getUsage());
+ String memoryLimit = formatMemory(stats.getMemoryStats().getLimit());
+ containerStatus.setMemoryInfo(memoryUsage + " / " + memoryLimit);
+ containerStatus.setCpuInfo(formatCpu(containerStatus.getContainerName(), stats));
+ }
+ }
+
+ public DevService getDevService(String code, String name) {
+ Yaml yaml = new Yaml();
+ Map<String, Object> obj = yaml.load(code);
+ JsonObject json = JsonObject.mapFrom(obj);
+ JsonObject services = json.getJsonObject("services");
+ if (services.containsKey(name)) {
+ DevService ds = services.getJsonObject(name).mapTo(DevService.class);
+ if (ds.getContainer_name() == null) {
+ ds.setContainer_name(name);
+ }
+ return ds;
+ } else {
+ Optional<JsonObject> j = services.fieldNames().stream()
+ .map(services::getJsonObject)
+ .filter(s -> {
+ s.getJsonObject("container_name");
+ return false;
+ }).findFirst();
+ if (j.isPresent()) {
+ return j.get().mapTo(DevService.class);
+ }
+ }
+ return null;
+ }
+
+ protected HealthCheck getHealthCheck(HealthCheckConfig config) {
+ if (config != null) {
+ HealthCheck healthCheck = new HealthCheck().withTest(config.getTest());
+ if (config.getInterval() != null) {
+ healthCheck.withInterval(convertDuration(config.getInterval()));
+ }
+ if (config.getTimeout() != null) {
+ healthCheck.withTimeout(convertDuration(config.getTimeout()));
+ }
+ if (config.getStart_period() != null) {
+ healthCheck.withStartPeriod(convertDuration(config.getStart_period()));
+ }
+ if (config.getRetries() != null) {
+ healthCheck.withRetries(config.getRetries());
+ }
+ return healthCheck;
+ }
+ return new HealthCheck();
+ }
+
+ protected Long convertDuration(String value) {
+ return Long.parseLong(value.replace("s", "")) * 1000000000L;
+ }
+
+ protected String getResourceFile(String path) {
+ try {
+ InputStream inputStream = KameletResources.class.getResourceAsStream(path);
+ return new BufferedReader(new InputStreamReader(inputStream))
+ .lines().collect(Collectors.joining(System.getProperty("line.separator")));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ protected HostConfig getHostConfig(String ports, List<ExposedPort> exposedPorts, boolean inRange, String network) {
+ Ports portBindings = new Ports();
+
+ getPortsFromString(ports).forEach((hostPort, containerPort) -> {
+ Ports.Binding binding = (exposedPorts.stream().anyMatch(e -> e.getPort() == containerPort))
+ ? (inRange ? Ports.Binding.bindPortRange(hostPort, hostPort + 1000) : Ports.Binding.bindPort(hostPort))
+ : Ports.Binding.bindPort(hostPort);
+ portBindings.bind(ExposedPort.tcp(containerPort), binding);
+ });
+ return new HostConfig()
+ .withPortBindings(portBindings)
+ .withNetworkMode(network);
+ }
+
+ protected Map<Integer, Integer> getPortsFromString(String ports) {
+ Map<Integer, Integer> p = new HashMap<>();
+ if (ports != null && !ports.isEmpty()) {
+ Arrays.stream(ports.split(",")).forEach(s -> {
+ String[] values = s.split(":");
+ p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
+ });
+ }
+ return p;
+ }
+
+ protected String formatMemory(Long memory) {
+ try {
+ if (memory < (1073741824)) {
+ return formatMiB.format(memory.doubleValue() / 1048576) + "MiB";
+ } else {
+ return formatGiB.format(memory.doubleValue() / 1073741824) + "GiB";
+ }
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ protected ContainerStatus.ContainerType getContainerType(Map<String, String> labels) {
+ String type = labels.get(LABEL_TYPE);
+ if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
+ return ContainerStatus.ContainerType.devmode;
+ } else if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
+ return ContainerStatus.ContainerType.devservice;
+ } else if (Objects.equals(type, ContainerStatus.ContainerType.project.name())) {
+ return ContainerStatus.ContainerType.project;
+ } else if (Objects.equals(type, ContainerStatus.ContainerType.internal.name())) {
+ return ContainerStatus.ContainerType.internal;
+ }
+ return ContainerStatus.ContainerType.unknown;
+ }
+
+ protected List<ContainerStatus.Command> getContainerCommand(String state) {
+ List<ContainerStatus.Command> result = new ArrayList<>();
+ if (Objects.equals(state, ContainerStatus.State.created.name())) {
+ result.add(ContainerStatus.Command.run);
+ result.add(ContainerStatus.Command.delete);
+ } else if (Objects.equals(state, ContainerStatus.State.exited.name())) {
+ result.add(ContainerStatus.Command.run);
+ result.add(ContainerStatus.Command.delete);
+ } else if (Objects.equals(state, ContainerStatus.State.running.name())) {
+ result.add(ContainerStatus.Command.pause);
+ result.add(ContainerStatus.Command.stop);
+ result.add(ContainerStatus.Command.delete);
+ } else if (Objects.equals(state, ContainerStatus.State.paused.name())) {
+ result.add(ContainerStatus.Command.run);
+ result.add(ContainerStatus.Command.stop);
+ result.add(ContainerStatus.Command.delete);
+ } else if (Objects.equals(state, ContainerStatus.State.dead.name())) {
+ result.add(ContainerStatus.Command.delete);
+ }
+ return result;
+ }
+
+ protected String formatCpu(String containerName, Statistics stats) {
+ try {
+ double cpuUsage = 0;
+ long previousCpu = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem1() : -1;
+ long previousSystem = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem2() : -1;
+
+ CpuStatsConfig cpuStats = stats.getCpuStats();
+ if (cpuStats != null) {
+ CpuUsageConfig cpuUsageConfig = cpuStats.getCpuUsage();
+ long systemUsage = cpuStats.getSystemCpuUsage();
+ long totalUsage = cpuUsageConfig.getTotalUsage();
+
+ if (previousCpu != -1 && previousSystem != -1) {
+ float cpuDelta = totalUsage - previousCpu;
+ float systemDelta = systemUsage - previousSystem;
+
+ if (cpuDelta > 0 && systemDelta > 0) {
+ cpuUsage = cpuDelta / systemDelta * cpuStats.getOnlineCpus() * 100;
+ }
+ }
+ previousStats.put(containerName, Tuple2.of(totalUsage, systemUsage));
+ }
+ return formatCpu.format(cpuUsage) + "%";
+ } catch (Exception e) {
+ return "";
+ }
+ }
+}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DevService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DevService.java
new file mode 100644
index 00000000..2c0b4e30
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DevService.java
@@ -0,0 +1,103 @@
+package org.apache.camel.karavan.docker.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class DevService {
+
+ private String container_name;
+ private String image;
+ private String restart;
+ private List<String> ports;
+ private List<String> expose;
+ private String depends_on;
+ private Map<String,String> environment;
+ private HealthCheckConfig healthcheck;
+
+ public DevService() {
+ }
+
+ public String getContainer_name() {
+ return container_name;
+ }
+
+ public void setContainer_name(String container_name) {
+ this.container_name = container_name;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public String getRestart() {
+ return restart;
+ }
+
+ public void setRestart(String restart) {
+ this.restart = restart;
+ }
+
+ public List<String> getPorts() {
+ return ports;
+ }
+
+ public void setPorts(List<String> ports) {
+ this.ports = ports;
+ }
+
+ public List<String> getExpose() {
+ return expose;
+ }
+
+ public void setExpose(List<String> expose) {
+ this.expose = expose;
+ }
+
+ public String getDepends_on() {
+ return depends_on;
+ }
+
+ public void setDepends_on(String depends_on) {
+ this.depends_on = depends_on;
+ }
+
+ public Map<String, String> getEnvironment() {
+ return environment;
+ }
+
+ public List<String> getEnvironmentList() {
+ return environment.entrySet().stream()
+ .map(e -> e.getKey().concat("=").concat(e.getValue())).collect(Collectors.toList());
+ }
+
+ public void setEnvironment(Map<String, String> environment) {
+ this.environment = environment;
+ }
+
+ public HealthCheckConfig getHealthcheck() {
+ return healthcheck;
+ }
+
+ public void setHealthcheck(HealthCheckConfig healthcheck) {
+ this.healthcheck = healthcheck;
+ }
+
+ @Override
+ public String toString() {
+ return "DevService{" +
+ "container_name='" + container_name + '\'' +
+ ", image='" + image + '\'' +
+ ", restart='" + restart + '\'' +
+ ", ports=" + ports +
+ ", expose=" + expose +
+ ", depends_on='" + depends_on + '\'' +
+ ", environment=" + environment +
+ ", healthcheck=" + healthcheck +
+ '}';
+ }
+}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/HealthCheckConfig.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/HealthCheckConfig.java
new file mode 100644
index 00000000..46f766df
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/HealthCheckConfig.java
@@ -0,0 +1,55 @@
+package org.apache.camel.karavan.docker.model;
+
+import java.util.List;
+
+public class HealthCheckConfig {
+
+ private String interval;
+ private Integer retries;
+ private String timeout;
+ private String start_period;
+ private List<String> test;
+
+ public HealthCheckConfig() {
+ }
+
+ public String getInterval() {
+ return interval;
+ }
+
+ public void setInterval(String interval) {
+ this.interval = interval;
+ }
+
+ public Integer getRetries() {
+ return retries;
+ }
+
+ public void setRetries(Integer retries) {
+ this.retries = retries;
+ }
+
+ public String getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(String timeout) {
+ this.timeout = timeout;
+ }
+
+ public List<String> getTest() {
+ return test;
+ }
+
+ public void setTest(List<String> test) {
+ this.test = test;
+ }
+
+ public String getStart_period() {
+ return start_period;
+ }
+
+ public void setStart_period(String start_period) {
+ this.start_period = start_period;
+ }
+}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
index a23d93d0..73505065 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
@@ -51,6 +51,7 @@ public class CodeService {
private static final Logger LOGGER = Logger.getLogger(CodeService.class.getName());
public static final String APPLICATION_PROPERTIES_FILENAME = "application.properties";
+ public static final String DEV_SERVICES_FILENAME = "dev-services.yaml";
@Inject
KubernetesService kubernetesService;
@@ -133,8 +134,8 @@ public class CodeService {
public Map<String, String> getServices() {
Map<String, String> result = new HashMap<>();
- String templateText = getResourceFile("/services/dev-services.yaml");
- result.put("dev-services.yaml", templateText);
+ String templateText = getResourceFile("/services/" + DEV_SERVICES_FILENAME);
+ result.put(DEV_SERVICES_FILENAME, templateText);
return result;
}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 7c05cea0..93ec231f 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -16,8 +16,6 @@
*/
package org.apache.camel.karavan.service;
-import io.quarkus.scheduler.Scheduled;
-import io.quarkus.vertx.ConsumeEvent;
import io.smallrye.mutiny.tuples.Tuple2;
import org.apache.camel.karavan.infinispan.InfinispanService;
import org.apache.camel.karavan.infinispan.model.GitRepo;
@@ -37,9 +35,10 @@ import javax.inject.Inject;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
-import static org.apache.camel.karavan.shared.EventType.IMPORT_PROJECTS;
+import static org.apache.camel.karavan.service.CodeService.DEV_SERVICES_FILENAME;
@Default
@Readiness
@@ -263,4 +262,10 @@ public class ProjectService implements HealthCheck{
LOGGER.error("Error during pipelines project creation", e);
}
}
+
+ public String getDevServiceCode() {
+ List <ProjectFile> files = infinispanService.getProjectFiles(Project.Type.services.name());
+ Optional<ProjectFile> file = files.stream().filter(f -> f.getName().equals(DEV_SERVICES_FILENAME)).findFirst();
+ return file.orElse(new ProjectFile()).getCode();
+ }
}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
index 16fcbeb6..a4ba7878 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
@@ -62,10 +62,13 @@ public class ScheduledService {
List<String> namesInDocker = statusesInDocker.stream().map(ContainerStatus::getContainerName).collect(Collectors.toList());
List<ContainerStatus> statusesInInfinispan = infinispanService.getContainerStatuses(environment);
// clean deleted
- statusesInInfinispan.stream().filter(cs -> !namesInDocker.contains(cs.getContainerName())).forEach(containerStatus -> {
- infinispanService.deleteContainerStatus(containerStatus);
- infinispanService.deleteCamelStatuses(containerStatus.getProjectId(), containerStatus.getEnv());
- });
+ statusesInInfinispan.stream()
+ .filter(cs -> !(cs.getContainerId() == null && cs.getInTransit()))
+ .filter(cs -> !namesInDocker.contains(cs.getContainerName()))
+ .forEach(containerStatus -> {
+ infinispanService.deleteContainerStatus(containerStatus);
+ infinispanService.deleteCamelStatuses(containerStatus.getProjectId(), containerStatus.getEnv());
+ });
// send statuses to save
statusesInDocker.forEach(containerStatus -> {
eventBus.send(EventType.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus));
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index fc0ed2e8..4c94484a 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -463,8 +463,8 @@ export class KaravanApi {
});
}
- static async manageContainer(environment: string, name: string, command: 'run' | 'pause' | 'stop', after: (res: AxiosResponse<any>) => void) {
- instance.post('/api/infrastructure/container/' + environment + '/' + name, {command: command})
+ static async manageContainer(environment: string, type: 'devmove' | 'devservice' | 'project' | 'internal' | 'unknown', name: string, command: 'run' | 'pause' | 'stop', after: (res: AxiosResponse<any>) => void) {
+ instance.post('/api/infrastructure/container/' + environment + '/' + type + "/" + name, {command: command})
.then(res => {
after(res);
}).catch(err => {
@@ -472,8 +472,8 @@ export class KaravanApi {
});
}
- static async deleteContainer(environment: string, name: string, after: (res: AxiosResponse<any>) => void) {
- instance.delete('/api/infrastructure/container/' + environment + '/' + name)
+ static async deleteContainer(environment: string, type: 'devmove' | 'devservice' | 'project' | 'internal' | 'unknown', name: string, after: (res: AxiosResponse<any>) => void) {
+ instance.delete('/api/infrastructure/container/' + environment + '/' + type + "/" + name)
.then(res => {
after(res);
}).catch(err => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
index 3a4ced02..82a89413 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -75,7 +75,7 @@ export class ContainerStatus {
deployment: string = '';
projectId: string = '';
env: string = '';
- type: string = '';
+ type: 'devmove' | 'devservice' | 'project' | 'internal' | 'unknown' = 'unknown';
memoryInfo: string = '';
cpuInfo: string = '';
created: string = '';
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 8b043d61..cdfada6f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -1,7 +1,7 @@
-import {KaravanApi} from "./KaravanApi";
-import {DeploymentStatus, ContainerStatus, Project, ProjectFile, ToastMessage} from "./ProjectModels";
-import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
-import {InfrastructureAPI} from "../designer/utils/InfrastructureAPI";
+import {KaravanApi} from './KaravanApi';
+import {DeploymentStatus, ContainerStatus, Project, ProjectFile, ToastMessage} from './ProjectModels';
+import {TemplateApi} from 'karavan-core/lib/api/TemplateApi';
+import {InfrastructureAPI} from '../designer/utils/InfrastructureAPI';
import {unstable_batchedUpdates} from 'react-dom'
import {
useFilesStore,
@@ -9,17 +9,17 @@ import {
useFileStore, useLogStore,
useProjectsStore,
useProjectStore, useDevModeStore
-} from "./ProjectStore";
-import {ProjectEventBus} from "./ProjectEventBus";
+} from './ProjectStore';
+import {ProjectEventBus} from './ProjectEventBus';
export class ProjectService {
public static startDevModeContainer(project: Project, verbose: boolean) {
- useDevModeStore.setState({status: "wip"})
+ useDevModeStore.setState({status: 'wip'})
KaravanApi.startDevModeContainer(project, verbose, res => {
- useDevModeStore.setState({status: "none"})
+ useDevModeStore.setState({status: 'none'})
if (res.status === 200 || res.status === 201) {
- ProjectEventBus.sendLog("set", '');
+ ProjectEventBus.sendLog('set', '');
useLogStore.setState({showLog: true, type: 'container', podName: res.data})
} else {
// Todo notification
@@ -28,9 +28,9 @@ export class ProjectService {
}
public static reloadDevModeCode(project: Project) {
- useDevModeStore.setState({status: "wip"})
+ useDevModeStore.setState({status: 'wip'})
KaravanApi.reloadDevModeCode(project.projectId, res => {
- useDevModeStore.setState({status: "none"})
+ useDevModeStore.setState({status: 'none'})
if (res.status === 200 || res.status === 201) {
// setIsReloadingPod(false);
} else {
@@ -41,38 +41,38 @@ export class ProjectService {
}
public static stopDevModeContainer(project: Project) {
- useDevModeStore.setState({status: "wip"})
- KaravanApi.manageContainer("dev", project.projectId, 'stop', res => {
- useDevModeStore.setState({status: "none"})
+ useDevModeStore.setState({status: 'wip'})
+ KaravanApi.manageContainer('dev', 'devmove', project.projectId, 'stop', res => {
+ useDevModeStore.setState({status: 'none'})
if (res.status === 200) {
useLogStore.setState({showLog: false, type: 'container', isRunning: false})
} else {
- ProjectEventBus.sendAlert(new ToastMessage("Error stopping DevMode container", res.statusText, 'warning'))
+ ProjectEventBus.sendAlert(new ToastMessage('Error stopping DevMode container', res.statusText, 'warning'))
}
});
}
public static pauseDevModeContainer(project: Project) {
- useDevModeStore.setState({status: "wip"})
- KaravanApi.manageContainer("dev", project.projectId, 'pause', res => {
- useDevModeStore.setState({status: "none"})
+ useDevModeStore.setState({status: 'wip'})
+ KaravanApi.manageContainer('dev', 'devmove', project.projectId, 'pause', res => {
+ useDevModeStore.setState({status: 'none'})
if (res.status === 200) {
useLogStore.setState({showLog: false, type: 'container', isRunning: false})
} else {
- ProjectEventBus.sendAlert(new ToastMessage("Error stopping DevMode container", res.statusText, 'warning'))
+ ProjectEventBus.sendAlert(new ToastMessage('Error stopping DevMode container', res.statusText, 'warning'))
}
});
}
public static deleteDevModeContainer(project: Project) {
- useDevModeStore.setState({status: "wip"})
- ProjectEventBus.sendLog("set", '');
+ useDevModeStore.setState({status: 'wip'})
+ ProjectEventBus.sendLog('set', '');
KaravanApi.deleteDevModeContainer(project.projectId, false, res => {
- useDevModeStore.setState({status: "none"})
+ useDevModeStore.setState({status: 'none'})
if (res.status === 202) {
useLogStore.setState({showLog: false, type: 'container', isRunning: false})
} else {
- ProjectEventBus.sendAlert(new ToastMessage("Error delete runner", res.statusText, 'warning'))
+ ProjectEventBus.sendAlert(new ToastMessage('Error delete runner', res.statusText, 'warning'))
}
});
}
@@ -86,14 +86,14 @@ export class ProjectService {
if (useDevModeStore.getState().podName !== containerStatus.containerName){
useDevModeStore.setState({podName: containerStatus.containerName})
}
- if (useDevModeStore.getState().status !== "wip"){
+ if (useDevModeStore.getState().status !== 'wip'){
useLogStore.setState({isRunning: true})
}
useProjectStore.setState({containerStatus: containerStatus});
})
} else {
unstable_batchedUpdates(() => {
- useDevModeStore.setState({status: "none", podName: undefined})
+ useDevModeStore.setState({status: 'none', podName: undefined})
useProjectStore.setState({containerStatus: new ContainerStatus({})});
})
}
@@ -103,8 +103,8 @@ export class ProjectService {
public static pushProject(project: Project, commitMessage: string) {
useProjectStore.setState({isPushing: true})
const params = {
- "projectId": project.projectId,
- "message": commitMessage
+ 'projectId': project.projectId,
+ 'message': commitMessage
};
KaravanApi.push(params, res => {
if (res.status === 200 || res.status === 201) {
@@ -167,10 +167,10 @@ export class ProjectService {
public static deleteProject(project: Project) {
KaravanApi.deleteProject(project, res => {
if (res.status === 204) {
- // this.props.toast?.call(this, "Success", "Project deleted", "success");
+ // this.props.toast?.call(this, 'Success', 'Project deleted', 'success');
ProjectService.refreshProjectData();
} else {
- // this.props.toast?.call(this, "Error", res.statusText, "danger");
+ // this.props.toast?.call(this, 'Error', res.statusText, 'danger');
}
});
}
@@ -179,9 +179,9 @@ export class ProjectService {
KaravanApi.postProject(project, res => {
if (res.status === 200 || res.status === 201) {
ProjectService.refreshProjectData();
- // this.props.toast?.call(this, "Success", "Project created", "success");
+ // this.props.toast?.call(this, 'Success', 'Project created', 'success');
} else {
- // this.props.toast?.call(this, "Error", res.status + ", " + res.statusText, "danger");
+ // this.props.toast?.call(this, 'Error', res.status + ', ' + res.statusText, 'danger');
}
});
}
@@ -211,10 +211,10 @@ export class ProjectService {
KaravanApi.getProject(project.projectId, (project: Project) => {
// ProjectEventBus.selectProject(project);
KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
- files.filter(f => f.name.endsWith("java"))
+ files.filter(f => f.name.endsWith('java'))
.filter(f => f.name.startsWith(project.runtime))
.forEach(f => {
- const name = f.name.replace(project.runtime + "-", '').replace(".java", '');
+ const name = f.name.replace(project.runtime + '-', '').replace('.java', '');
TemplateApi.saveTemplate(name, f.code);
})
});
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts b/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts
index a24f473d..506301d7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts
@@ -11,8 +11,7 @@ export class Healthcheck {
}
}
-export class Service {
- name: string = '';
+export class DevService {
container_name: string = '';
image: string = '';
restart: string = '';
@@ -21,14 +20,14 @@ export class Service {
environment: any = {};
healthcheck?: Healthcheck;
- public constructor(init?: Partial<Service>) {
+ public constructor(init?: Partial<DevService>) {
Object.assign(this, init);
}
}
export class Services {
version: string = '';
- services: Service[] = [];
+ services: DevService[] = [];
public constructor(init?: Partial<Services>) {
Object.assign(this, init);
@@ -43,8 +42,10 @@ export class ServicesYaml {
const result: Services = new Services({version: fromYaml.version});
Object.keys(fromYaml.services).forEach(key => {
const o = fromYaml.services[key];
- const service = new Service(o);
- service.name = key;
+ const service = new DevService(o);
+ if (!service.container_name) {
+ service.container_name = key;
+ }
result.services.push(service);
})
return result;
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
index 13d702d2..0e2a1091 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
@@ -59,15 +59,15 @@ export const ContainerTableRow = (props: Props) => {
{!inTransit && <Label color={color}>{container.state}</Label>}
{inTransit && <Spinner isSVG size="lg" aria-label="spinner"/>}
</Td>
- <Td className="project-action-buttons">
+ <Td>
{container.type !== 'internal' &&
- <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
+ <Flex direction={{default: "row"}} flexWrap={{default: "nowrap"}}
spaceItems={{default: 'spaceItemsNone'}}>
<FlexItem>
<Tooltip content={"Start container"} position={"bottom"}>
<Button variant={"plain"} icon={<PlayIcon/>} isDisabled={!commands.includes('run') || inTransit}
onClick={e => {
- KaravanApi.manageContainer(container.env, container.containerName, 'run', res => {});
+ KaravanApi.manageContainer(container.env, container.type, container.containerName, 'run', res => {});
}}></Button>
</Tooltip>
</FlexItem>
@@ -75,7 +75,7 @@ export const ContainerTableRow = (props: Props) => {
<Tooltip content={"Pause container"} position={"bottom"}>
<Button variant={"plain"} icon={<PauseIcon/>} isDisabled={!commands.includes('pause') || inTransit}
onClick={e => {
- KaravanApi.manageContainer(container.env, container.containerName, 'pause', res => {});
+ KaravanApi.manageContainer(container.env, container.type, container.containerName, 'pause', res => {});
}}></Button>
</Tooltip>
</FlexItem>
@@ -83,7 +83,7 @@ export const ContainerTableRow = (props: Props) => {
<Tooltip content={"Stop container"} position={"bottom"}>
<Button variant={"plain"} icon={<StopIcon/>} isDisabled={!commands.includes('stop') || inTransit}
onClick={e => {
- KaravanApi.manageContainer(container.env, container.containerName, 'stop', res => {});
+ KaravanApi.manageContainer(container.env, container.type, container.containerName, 'stop', res => {});
}}></Button>
</Tooltip>
</FlexItem>
@@ -91,7 +91,7 @@ export const ContainerTableRow = (props: Props) => {
<Tooltip content={"Delete container"} position={"bottom"}>
<Button variant={"plain"} icon={<DeleteIcon/>} isDisabled={!commands.includes('delete') || inTransit}
onClick={e => {
- KaravanApi.deleteContainer(container.env, container.containerName, res => {});
+ KaravanApi.deleteContainer(container.env, container.type, container.containerName, res => {});
}}></Button>
</Tooltip>
</FlexItem>
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
index 9d1f6607..08539413 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
@@ -1,32 +1,24 @@
import React, {useEffect, useState} from 'react';
import {
- Badge, Bullseye,
+ Bullseye,
Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
- Flex,
- FlexItem, HelperText, HelperTextItem, Label, LabelGroup,
PageSection, Spinner,
Text,
TextContent,
TextInput, Title, ToggleGroup, ToggleGroupItem,
Toolbar,
ToolbarContent,
- ToolbarItem, Tooltip
+ ToolbarItem
} from '@patternfly/react-core';
import '../designer/karavan.css';
-import {CamelStatus, ContainerStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
+import {ContainerStatus} from "../api/ProjectModels";
import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
-import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
import {KaravanApi} from "../api/KaravanApi";
-import Icon from "../Logo";
-import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
-import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
import {MainToolbar} from "../designer/MainToolbar";
-import {useAppConfigStore, useProjectsStore, useStatusesStore} from "../api/ProjectStore";
+import {useAppConfigStore, useStatusesStore} from "../api/ProjectStore";
import {shallow} from "zustand/shallow";
-import {Service} from "../api/ServiceModels";
-import {ServicesTableRow} from "../services/ServicesTableRow";
import {ContainerTableRow} from "./ContainerTableRow";
export const ContainersPage = () => {
@@ -39,19 +31,17 @@ export const ContainersPage = () => {
useEffect(() => {
const interval = setInterval(() => {
- onGetProjects()
+ updateContainerStatuses()
}, 700);
return () => {
clearInterval(interval)
};
}, []);
- function onGetProjects() {
- KaravanApi.getConfiguration((config: any) => {
- KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
- setContainers(statuses);
- setLoading(false);
- });
+ function updateContainerStatuses() {
+ KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+ setContainers(statuses);
+ setLoading(false);
});
}
@@ -72,7 +62,7 @@ export const ContainersPage = () => {
return (<Toolbar id="toolbar-group-types">
<ToolbarContent>
<ToolbarItem>
- <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>
+ <Button variant="link" icon={<RefreshIcon/>} onClick={e => updateContainerStatuses()}/>
</ToolbarItem>
<ToolbarItem>
<ToggleGroup aria-label="Default with single selectable">
@@ -148,7 +138,7 @@ export const ContainersPage = () => {
<Th key='cpuInfo'>CPU</Th>
<Th key='memoryInfo'>Memory</Th>
<Th key='state'>State</Th>
- <Th key='action'></Th>
+ <Th key='action'></Th>
</Tr>
</Thead>
{conts?.map((container: ContainerStatus, index: number) => (
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index add844b9..14dc1eaa 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -84,7 +84,7 @@ export class ProjectStatus extends React.Component<Props, State> {
});
break;
case "pod":
- KaravanApi.deleteContainer(environment, name, (res: any) => {
+ KaravanApi.deleteContainer(environment, 'project', name, (res: any) => {
// if (Array.isArray(res) && Array.from(res).length > 0)
// this.onRefresh();
});
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
index 54a101be..87606ac9 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
@@ -3,7 +3,6 @@ import {
Toolbar,
ToolbarContent,
ToolbarItem,
- TextInput,
PageSection,
TextContent,
Text,
@@ -17,28 +16,43 @@ import {
import '../designer/karavan.css';
import RefreshIcon from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
-import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
+import {TableComposable, Td, Th, Thead, Tr} from "@patternfly/react-table";
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
import {ServicesTableRow} from "./ServicesTableRow";
import {DeleteServiceModal} from "./DeleteServiceModal";
import {CreateServiceModal} from "./CreateServiceModal";
-import {useProjectStore} from "../api/ProjectStore";
+import {useProjectStore, useStatusesStore} from "../api/ProjectStore";
import {MainToolbar} from "../designer/MainToolbar";
-import {Project, ProjectFile, ProjectType} from "../api/ProjectModels";
+import {ContainerStatus, Project, ProjectType} from "../api/ProjectModels";
import {KaravanApi} from "../api/KaravanApi";
-import {Service, Services, ServicesYaml} from "../api/ServiceModels";
+import {DevService, Services, ServicesYaml} from "../api/ServiceModels";
+import {shallow} from "zustand/shallow";
export const ServicesPage = () => {
const [services, setServices] = useState<Services>();
+ const [containers, setContainers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
const [operation, setOperation] = useState<'create' | 'delete' | 'none'>('none');
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
getServices();
+ const interval = setInterval(() => {
+ updateContainerStatuses()
+ }, 700);
+ return () => {
+ clearInterval(interval)
+ };
}, []);
+ function updateContainerStatuses() {
+ KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+ setContainers(statuses);
+ setLoading(false);
+ });
+ }
+
function getServices() {
KaravanApi.getFiles(ProjectType.services, files => {
const file = files.at(0);
@@ -92,6 +106,10 @@ export const ServicesPage = () => {
)
}
+ function getContainer(name: string) {
+ return containers.filter(c => c.containerName === name).at(0);
+ }
+
function getServicesTable() {
return (
<TableComposable aria-label="Services" variant={"compact"}>
@@ -102,11 +120,12 @@ export const ServicesPage = () => {
<Th key='container_name'>Container Name</Th>
<Th key='image'>Image</Th>
<Th key='ports'>Ports</Th>
+ <Th key='state'>State</Th>
<Th key='action'></Th>
</Tr>
</Thead>
- {services?.services.map((service: Service, index: number) => (
- <ServicesTableRow key={service.name} index={index} service={service}/>
+ {services?.services.map((service: DevService, index: number) => (
+ <ServicesTableRow key={service.container_name} index={index} service={service} container={getContainer(service.container_name)}/>
))}
{services?.services.length === 0 && getEmptyState()}
</TableComposable>
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
index f6affafe..fab5456d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
@@ -2,35 +2,90 @@ import React, {useState} from 'react';
import {
Button,
Tooltip,
- Flex, FlexItem, Label
+ Flex, FlexItem, Label, ToolbarContent, Toolbar, ToolbarItem, Spinner
} from '@patternfly/react-core';
import '../designer/karavan.css';
-import {ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
+import {ActionsColumn, ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
import StopIcon from "@patternfly/react-icons/dist/js/icons/stop-icon";
import PlayIcon from "@patternfly/react-icons/dist/esm/icons/play-icon";
-import {Service} from "../api/ServiceModels";
+import {DevService} from "../api/ServiceModels";
+import {ContainerStatus} from "../api/ProjectModels";
+import PauseIcon from "@patternfly/react-icons/dist/esm/icons/pause-icon";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import {useAppConfigStore} from "../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {KaravanApi} from "../api/KaravanApi";
interface Props {
index: number
- service: Service
+ service: DevService
+ container?: ContainerStatus
}
export const ServicesTableRow = (props: Props) => {
+ const [config] = useAppConfigStore((state) => [state.config], shallow)
const [isExpanded, setIsExpanded] = useState<boolean>(false);
- const [running, setRunning] = useState<boolean>(false);
+
+
+ function getButtons() {
+ const container = props.container;
+ const commands = container?.commands || ['run'];
+ const inTransit = container?.inTransit;
+ return (
+ <Td noPadding className="project-action-buttons">
+ <Flex direction={{default: "row"}} flexWrap={{default: "nowrap"}}
+ spaceItems={{default: 'spaceItemsNone'}}>
+ <FlexItem>
+ <Tooltip content={"Start container"} position={"bottom"}>
+ <Button variant={"plain"} icon={<PlayIcon/>} isDisabled={!commands.includes('run') || inTransit}
+ onClick={e => {
+ KaravanApi.manageContainer(config.environment, 'devservice', service.container_name, 'run', res => {});
+ }}></Button>
+ </Tooltip>
+ </FlexItem>
+ <FlexItem>
+ <Tooltip content={"Pause container"} position={"bottom"}>
+ <Button variant={"plain"} icon={<PauseIcon/>} isDisabled={!commands.includes('pause') || inTransit}
+ onClick={e => {
+ // KaravanApi.manageContainer(container.env, container.containerName, 'pause', res => {});
+ }}></Button>
+ </Tooltip>
+ </FlexItem>
+ <FlexItem>
+ <Tooltip content={"Stop container"} position={"bottom"}>
+ <Button variant={"plain"} icon={<StopIcon/>} isDisabled={!commands.includes('stop') || inTransit}
+ onClick={e => {
+ KaravanApi.manageContainer(config.environment, 'devservice', service.container_name, 'stop', res => {});
+ }}></Button>
+ </Tooltip>
+ </FlexItem>
+ <FlexItem>
+ <Tooltip content={"Delete container"} position={"bottom"}>
+ <Button variant={"plain"} icon={<DeleteIcon/>} isDisabled={!commands.includes('delete') || inTransit}
+ onClick={e => {
+ KaravanApi.deleteContainer(config.environment, 'devservice', service.container_name, res => {});
+ }}></Button>
+ </Tooltip>
+ </FlexItem>
+ </Flex>
+ </Td>
+ )
+ }
const service = props.service;
const healthcheck = service.healthcheck;
const env = service.environment;
const keys = Object.keys(env);
- const icon = running ? <StopIcon/> : <PlayIcon/>;
- const tooltip = running ? "Stop container" : "Start container";
+ const container = props.container;
+ const isRunning = container?.state === 'running';
+ const inTransit = container?.inTransit;
+ const color = container?.state === 'running' ? "green" : "grey";
return (
<Tbody isExpanded={isExpanded}>
- <Tr key={service.name}>
+ <Tr key={service.container_name}>
<Td expand={
- service.name
+ service.container_name
? {
rowIndex: props.index,
isExpanded: isExpanded,
@@ -41,7 +96,7 @@ export const ServicesTableRow = (props: Props) => {
modifier={"fitContent"}>
</Td>
<Td>
- <Label color={"grey"}>{service.name}</Label>
+ <Label color={color}>{service.container_name}</Label>
</Td>
<Td>{service.container_name}</Td>
<Td>{service.image}</Td>
@@ -50,19 +105,11 @@ export const ServicesTableRow = (props: Props) => {
{service.ports.map(port => <FlexItem key={port}>{port}</FlexItem>)}
</Flex>
</Td>
- {/*<Td>{service.environment}</Td>*/}
- <Td className="project-action-buttons">
- <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
- spaceItems={{default: 'spaceItemsNone'}}>
- <FlexItem>
- <Tooltip content={tooltip} position={"bottom"}>
- <Button variant={"plain"} icon={icon} onClick={e => {
- // setProject(project, "delete");
- }}></Button>
- </Tooltip>
- </FlexItem>
- </Flex>
+ <Td>
+ {!inTransit && container?.state && <Label color={color}>{container?.state}</Label>}
+ {inTransit && <Spinner isSVG size="lg" aria-label="spinner"/>}
</Td>
+ {getButtons()}
</Tr>
{keys.length > 0 && <Tr isExpanded={isExpanded}>
<Td></Td>
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index ac905f64..839de24c 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -276,12 +276,6 @@ public class InfinispanService {
.execute().list();
}
- public void setContainerStatusTransit(String projectId, String env, String containerName) {
- ContainerStatus cs = getContainerStatus(projectId, env, containerName);
- cs.setInTransit(true);
- saveContainerStatus(cs);
- }
-
public ContainerStatus getContainerStatus(String projectId, String env, String containerName) {
return containerStatuses.get(GroupedKey.create(projectId, env, containerName));
}
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
index ef7ca779..c9336b8e 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
@@ -105,6 +105,10 @@ public class ContainerStatus {
return new ContainerStatus(projectId, projectId, null, null, null, env, ContainerType.devmode, null, null, null, List.of(Command.run), null, false, false);
}
+ public static ContainerStatus createByType(String name, String env, ContainerType type) {
+ return new ContainerStatus(name, name, null, null, null, env, type, null, null, null, List.of(Command.run), null, false, false);
+ }
+
public static ContainerStatus createWithId(String name, String env, String containerId, String image, List<Integer> ports, ContainerType type, List<Command> commands, String status, String created) {
return new ContainerStatus(name, name, containerId, image, ports, env, type,
null, null, created, commands, status, false, false);