Introduced stop feature and state persistence

https://github.com/DerDavidBohl/dirigent-spring/issues/21
This commit is contained in:
DerDavidBohl
2025-02-07 14:37:16 +01:00
parent 59b3b5fc6f
commit 0b87a775ee
27 changed files with 310 additions and 158 deletions

View File

@@ -8,6 +8,7 @@ on:
push:
branches:
- 'latest'
- 'experimental'
tags:
- 'v*'

1
.gitignore vendored
View File

@@ -37,3 +37,4 @@ build/
### Config Files ###
/src/main/resources/application.properties
/src/main/resources/application-local.properties
/data/

View File

@@ -4,8 +4,6 @@ Tool to manage your docker compose deployments via git.
## Table of Contents
- [Dirigent](#dirigent)
- [Table of Contents](#table-of-contents)
- [Setup](#setup)
- [docker-compose](#docker-compose)
- [docker CLI](#docker-cli)
@@ -16,11 +14,16 @@ Tool to manage your docker compose deployments via git.
- [API](#api)
- [Gitea Webhook](#gitea-webhook)
- [Deployments](#deployments)
- [Start All Deployments](#start-all-deployments)
- [Start Deployment by name](#start-deployment-by-name)
- [Start](#start)
- [All Deployments](#all-deployments)
- [Deployment by name](#deployment-by-name)
- [Stop](#stop)
- [Deployment by name](#deployment-by-name-1)
- [State](#state)
- [Develop](#develop)
- [Setup for local Tests](#setup-for-local-tests)
## Setup
### docker-compose
@@ -45,6 +48,7 @@ services:
volumes:
- /path/to/config:/app/config
- /path/to/deployments:/app/deployments
- /path/to/data:/app/data
- /var/run/docker.sock:/var/run/docker.sock
```
@@ -70,6 +74,7 @@ docker run -d \
-e DIRIGENT_GOTIFY_TOKEN= \
-v /path/to/config:/app/config \
-v /path/to/deployments:/app/deployments \
-v /path/to/data:/app/data \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/derdavidbohl/dirigent-spring:latest
```
@@ -104,11 +109,12 @@ deployments:
### Volumes
| Volume | Description |
|----------------------|------------------------------------|
| /app/config | Config directory for Dirigent |
| /app/deployments | Deployments directory for Dirigent |
| /var/run/docker.sock | Docker socket for Dirigent |
| Volume | Description |
|----------------------|----------------------------------------|
| /app/config | Config directory for Dirigent |
| /app/deployments | Deployments directory for Dirigent |
| /app/data | Data directory containing the database |
| /var/run/docker.sock | Docker socket for Dirigent |
### Step by Step (Gitea)
@@ -144,13 +150,33 @@ Store all your repositories for one host in one gitea organization. This way you
### Deployments
#### Start All Deployments:
#### Start
`POST` to `/api/v1/deployments/all/start` optional add `force=true` if you want to force deployment and recreation of containers.
**Parameters**
#### Start Deployment by name:
| Parameter | Description |
|-----------------|------------------------------------------------------|
| `force=true` | forces Recreation and Run of targeted deployment(s) |
| `forceRun=true` | only forces run of targeted deployment(s) |
| `forceRecreate` | only forces recreation of the targeted deployment(s) |
`POST` to `/api/v1/deployments/{name}/start` optional add `force=true` if you want to force deployment and recreation of containers.
##### All Deployments:
`POST` to `/api/v1/deployments/all/start`
##### Deployment by name:
`POST` to `/api/v1/deployments/{name}/start`
#### Stop
##### Deployment by name:
`POST` to `/api/v1/deployments/{name}/stop`
#### State
`GET` to `/api/v1/deployment-states`
## Develop

View File

@@ -1,2 +1,9 @@
POST http://localhost:8080/api/v1/deployments/test2/stop
###
POST http://localhost:8080/api/v1/deployments/all/start
###
GET http://localhost:8080/api/v1/deployment-states

View File

@@ -10,7 +10,7 @@
</parent>
<groupId>org.davidbohl</groupId>
<artifactId>dirigent</artifactId>
<version>0.2.0-SNAPSHOT</version>
<version>0.3.0-SNAPSHOT</version>
<name>dirigent</name>
<description>Helper for Docker Composes</description>
<url/>
@@ -63,6 +63,11 @@
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@@ -7,8 +7,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.IOException;
@SpringBootApplication
@EnableConfigurationProperties
@@ -17,7 +15,7 @@ public class DirigentApplication {
static Logger logger = LoggerFactory.getLogger(DirigentApplication.class);
public static void main(String[] args) throws IOException, InterruptedException {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DirigentApplication.class, args);
String composeCommand = context.getEnvironment().getProperty("dirigent.compose.command");
if(!isComposeInstalled(composeCommand)) {

View File

@@ -0,0 +1,26 @@
package org.davidbohl.dirigent.deployments.api;
import org.davidbohl.dirigent.deployments.state.DeploymentState;
import org.davidbohl.dirigent.deployments.state.DeploymentStatePersistingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController()
@RequestMapping(path = "/api/v1/deployment-states")
public class DeploymentStatesController {
private final DeploymentStatePersistingService deploymentStatePersistingService;
public DeploymentStatesController(DeploymentStatePersistingService deploymentStatePersistingService) {
this.deploymentStatePersistingService = deploymentStatePersistingService;
}
@GetMapping
public List<DeploymentState> getDeploymentStates() {
return deploymentStatePersistingService.getDeploymentStates();
}
}

View File

@@ -2,6 +2,7 @@ package org.davidbohl.dirigent.deployments.api;
import org.davidbohl.dirigent.deployments.events.AllDeploymentsStartRequestedEvent;
import org.davidbohl.dirigent.deployments.events.NamedDeploymentStartRequestedEvent;
import org.davidbohl.dirigent.deployments.events.NamedDeploymentStopRequestedEvent;
import org.davidbohl.dirigent.deployments.management.DeploymentNameNotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.ProblemDetail;
@@ -23,9 +24,18 @@ public class DeploymentsController {
applicationEventPublisher.publishEvent(new NamedDeploymentStartRequestedEvent(this, name, force));
}
@PostMapping("/{name}/stop")
public void stopDeployment(@PathVariable String name) {
applicationEventPublisher.publishEvent(new NamedDeploymentStopRequestedEvent(this, name));
}
@PostMapping("/all/start")
public void startAllDeployments(@RequestParam(required = false) boolean force) {
applicationEventPublisher.publishEvent(new AllDeploymentsStartRequestedEvent(this, force));
public void startAllDeployments(@RequestParam(required = false) boolean force,
@RequestParam(required = false) boolean forceRun,
@RequestParam(required = false) boolean forceRecreate) {
applicationEventPublisher.publishEvent(new AllDeploymentsStartRequestedEvent(this,
force || forceRun,
force || forceRecreate));
}
@ExceptionHandler(DeploymentNameNotFoundException.class)

View File

@@ -2,7 +2,6 @@ package org.davidbohl.dirigent.deployments.api;
import org.davidbohl.dirigent.deployments.events.AllDeploymentsStartRequestedEvent;
import org.davidbohl.dirigent.deployments.events.SourceDeploymentStartRequestedEvent;
import org.davidbohl.dirigent.deployments.management.DeploymentsService;
import org.davidbohl.dirigent.deployments.models.GiteaRequestBody;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
@@ -20,7 +19,7 @@ public class GiteaDeploymentsController {
@Value("${dirigent.deployments.git.url}")
private String configUrl;
public GiteaDeploymentsController(DeploymentsService deploymentsService, ApplicationEventPublisher applicationEventPublisher) {
public GiteaDeploymentsController(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@@ -28,7 +27,7 @@ public class GiteaDeploymentsController {
public void webHook(@RequestBody GiteaRequestBody body) {
if(body.repository().cloneUrl().equals(configUrl)) {
applicationEventPublisher.publishEvent(new AllDeploymentsStartRequestedEvent(this, false));
applicationEventPublisher.publishEvent(new AllDeploymentsStartRequestedEvent(this, true, true));
return;
}

View File

@@ -1,17 +1,17 @@
package org.davidbohl.dirigent.deployments.events;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class AllDeploymentsStartRequestedEvent extends ApplicationEvent {
private final boolean forced;
private final boolean forceRun;
private final boolean forceRecreate;
public AllDeploymentsStartRequestedEvent(Object source, boolean forced) {
public AllDeploymentsStartRequestedEvent(Object source, boolean forceRun, boolean forceRecreate) {
super(source);
this.forced = forced;
}
public boolean isForced() {
return forced;
this.forceRun = forceRun;
this.forceRecreate = forceRecreate;
}
}

View File

@@ -1,24 +0,0 @@
package org.davidbohl.dirigent.deployments.events;
import org.springframework.context.ApplicationEvent;
public class DeploymentStartFailedEvent extends ApplicationEvent {
private final String deploymentName;
private final String message;
public DeploymentStartFailedEvent(Object source, String deploymentName, String string) {
super(source);
this.deploymentName = deploymentName;
this.message = string;
}
public String getMessage() {
return message;
}
public String getDeploymentName() {
return deploymentName;
}
}

View File

@@ -1,16 +0,0 @@
package org.davidbohl.dirigent.deployments.events;
import org.springframework.context.ApplicationEvent;
public class DeploymentStartSucceededEvent extends ApplicationEvent {
private final String deploymentName;
public DeploymentStartSucceededEvent(Object source, String deploymentName) {
super(source);
this.deploymentName = deploymentName;
}
public String getDeploymentName() {
return deploymentName;
}
}

View File

@@ -0,0 +1,21 @@
package org.davidbohl.dirigent.deployments.events;
import lombok.Getter;
import org.davidbohl.dirigent.deployments.state.DeploymentState;
import org.springframework.context.ApplicationEvent;
@Getter
public class DeploymentStateChangedEvent extends ApplicationEvent {
final String deploymentName;
final DeploymentState.State state;
final String context;
public DeploymentStateChangedEvent(Object source, String deploymentName, DeploymentState.State state, String context) {
super(source);
this.deploymentName = deploymentName;
this.state = state;
this.context = context;
}
}

View File

@@ -1,7 +1,9 @@
package org.davidbohl.dirigent.deployments.events;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class NamedDeploymentStartRequestedEvent extends ApplicationEvent {
private final String name;
@@ -13,11 +15,4 @@ public class NamedDeploymentStartRequestedEvent extends ApplicationEvent {
this.forced = forced;
}
public String getName() {
return name;
}
public boolean isForced() {
return forced;
}
}

View File

@@ -0,0 +1,16 @@
package org.davidbohl.dirigent.deployments.events;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class NamedDeploymentStopRequestedEvent extends ApplicationEvent {
private final String name;
public NamedDeploymentStopRequestedEvent(Object source, String name) {
super(source);
this.name = name;
}
}

View File

@@ -1,16 +0,0 @@
package org.davidbohl.dirigent.deployments.events;
import org.springframework.context.ApplicationEvent;
public class NotConfiguredDeploymentStopped extends ApplicationEvent {
private final String deploymentName;
public NotConfiguredDeploymentStopped(Object source, String deploymentName) {
super(source);
this.deploymentName = deploymentName;
}
public String getDeploymentName() {
return deploymentName;
}
}

View File

@@ -0,0 +1,9 @@
package org.davidbohl.dirigent.deployments.events;
import org.springframework.context.ApplicationEvent;
public class RecreateAllDeploymentStatesEvent extends ApplicationEvent {
public RecreateAllDeploymentStatesEvent(Object source) {
super(source);
}
}

View File

@@ -1,17 +1,16 @@
package org.davidbohl.dirigent.deployments.events;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class SourceDeploymentStartRequestedEvent extends ApplicationEvent {
private String deploymentSource;
private final String deploymentSource;
public SourceDeploymentStartRequestedEvent(Object source, String deploymentSource) {
super(source);
this.deploymentSource = deploymentSource;
}
public String getDeploymentSource() {
return deploymentSource;
}
}

View File

@@ -1,6 +1,6 @@
package org.davidbohl.dirigent.deployments.management;
import org.davidbohl.dirigent.deployments.events.AllDeploymentsStartRequestedEvent;
import org.davidbohl.dirigent.deployments.events.RecreateAllDeploymentStatesEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@@ -33,14 +33,14 @@ public class DeploymentScheduler {
void runScheduledDeployments() {
if(enabled) {
logger.info("Starting all deployments scheduled");
this.applicationEventPublisher.publishEvent(new AllDeploymentsStartRequestedEvent(this, false));
this.applicationEventPublisher.publishEvent(new RecreateAllDeploymentStatesEvent(this));
}
}
@EventListener(ContextRefreshedEvent.class)
public void onContextRefreshed() {
if(startAllDeploymentsOnStartup)
applicationEventPublisher.publishEvent(new AllDeploymentsStartRequestedEvent(this, false));
applicationEventPublisher.publishEvent(new RecreateAllDeploymentStatesEvent(this));
}
}

View File

@@ -4,6 +4,8 @@ import org.davidbohl.dirigent.deployments.config.DeploymentsConfigurationProvide
import org.davidbohl.dirigent.deployments.events.*;
import org.davidbohl.dirigent.deployments.models.Deployment;
import org.davidbohl.dirigent.deployments.models.DeploynentConfiguration;
import org.davidbohl.dirigent.deployments.state.DeploymentState;
import org.davidbohl.dirigent.deployments.state.DeploymentStatePersistingService;
import org.davidbohl.dirigent.deployments.utility.GitService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,16 +31,18 @@ public class DeploymentsService {
private final DeploymentsConfigurationProvider deploymentsConfigurationProvider;
private final Logger logger = LoggerFactory.getLogger(DeploymentsService.class);
private final ApplicationEventPublisher applicationEventPublisher;
private final DeploymentStatePersistingService deploymentStatePersistingService;
@Value("${dirigent.compose.command}")
private String composeCommand;
public DeploymentsService(
DeploymentsConfigurationProvider deploymentsConfigurationProvider,
GitService gitService, ApplicationEventPublisher applicationEventPublisher) {
GitService gitService, ApplicationEventPublisher applicationEventPublisher, DeploymentStatePersistingService deploymentStatePersistingService) {
this.deploymentsConfigurationProvider = deploymentsConfigurationProvider;
this.gitService = gitService;
this.applicationEventPublisher = applicationEventPublisher;
this.deploymentStatePersistingService = deploymentStatePersistingService;
}
@EventListener(AllDeploymentsStartRequestedEvent.class)
@@ -47,7 +51,7 @@ public class DeploymentsService {
makeDeploymentsDir();
DeploynentConfiguration deploymentsConfiguration = tryGetConfiguration();
deployListOfDeployments(deploymentsConfiguration.deployments(), event.isForced());
deployListOfDeployments(deploymentsConfiguration.deployments(), event.isForceRun(), event.isForceRecreate());
stopNotConfiguredDeployments(deploymentsConfiguration.deployments());
}
@@ -64,10 +68,10 @@ public class DeploymentsService {
Optional<Deployment> first = deploynentConfiguration.deployments().stream().filter(d -> Objects.equals(d.name(), event.getName())).findFirst();
if(first.isEmpty())
if (first.isEmpty())
throw new DeploymentNameNotFoundException(event.getName());
deploy(first.get(), event.isForced());
deploy(first.get(), event.isForced(), event.isForced());
}
@EventListener(SourceDeploymentStartRequestedEvent.class)
@@ -80,10 +84,40 @@ public class DeploymentsService {
.filter(d -> Objects.equals(d.source(), event.getDeploymentSource()))
.collect(Collectors.toList());
deployListOfDeployments(deployments, true);
deployListOfDeployments(deployments, true, true);
}
private void deploy(Deployment deployment, boolean force) {
@EventListener(NamedDeploymentStopRequestedEvent.class)
public void onNamedDeploymentStopRequested(NamedDeploymentStopRequestedEvent event) throws IOException, InterruptedException {
makeDeploymentsDir();
stopDeployment(event.getName());
}
@EventListener(RecreateAllDeploymentStatesEvent.class)
public void onRecreateAllDeploymentStatesEvent() {
makeDeploymentsDir();
DeploynentConfiguration deploynentConfiguration = tryGetConfiguration();
List<String> stoppedDeployments = deploymentStatePersistingService.getDeploymentStates().stream()
.filter(d -> d.getState() == DeploymentState.State.STOPPED)
.map(DeploymentState::getName)
.toList();
stoppedDeployments.forEach(d -> {
try {
stopDeployment(d);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
List<Deployment> deployments = deploynentConfiguration.deployments().stream()
.filter(d -> !stoppedDeployments.contains(d.name()))
.toList();
deployListOfDeployments(deployments, true, false);
}
private void deploy(Deployment deployment, boolean forceRun, boolean forceRecreate) {
logger.info("Deploying {}", deployment.name());
File deploymentDir = new File("deployments/" + deployment.name());
@@ -91,7 +125,8 @@ public class DeploymentsService {
try {
boolean updated = gitService.updateRepo(deployment.source(), deploymentDir.getAbsolutePath());
if(!updated && !force) {
if (!updated && !forceRun) {
applicationEventPublisher.publishEvent(new DeploymentStateChangedEvent(this, deployment.name(), DeploymentState.State.STARTED, "Deployment '%s' successfully started".formatted(deployment.name())));
logger.info("No changes in deployment. Skipping {}", deployment.name());
return;
}
@@ -101,7 +136,7 @@ public class DeploymentsService {
commandArgs.add("-d");
commandArgs.add("--remove-orphans");
if(force)
if (forceRecreate)
commandArgs.add("--force-recreate");
Process process = new ProcessBuilder(commandArgs)
@@ -109,22 +144,23 @@ public class DeploymentsService {
.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder output = new StringBuilder();
StringBuilder errorOutput = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
errorOutput.append(line).append("\n");
}
int exitVal = process.waitFor();
if (exitVal != 0) {
applicationEventPublisher.publishEvent(new DeploymentStartFailedEvent(this, deployment.name(), output.toString()));
int exitCode = process.waitFor();
if ((exitCode != 0)) {
applicationEventPublisher.publishEvent(new DeploymentStateChangedEvent(this, deployment.name(), DeploymentState.State.FAILED, errorOutput.toString()));
return;
}
} catch (IOException | InterruptedException e) {
applicationEventPublisher.publishEvent(new DeploymentStartFailedEvent(this, deployment.name(), e.getMessage()));
applicationEventPublisher.publishEvent(new DeploymentStateChangedEvent(this, deployment.name(), DeploymentState.State.FAILED, e.getMessage()));
return;
}
applicationEventPublisher.publishEvent(new DeploymentStartSucceededEvent(this, deployment.name()));
applicationEventPublisher.publishEvent(new DeploymentStateChangedEvent(this, deployment.name(), DeploymentState.State.STARTED, "Deployment '%s' successfully started".formatted(deployment.name())));
}
private void stopNotConfiguredDeployments(List<Deployment> deployments) {
@@ -132,21 +168,15 @@ public class DeploymentsService {
File deploymentsDir = new File("deployments");
File[] files = deploymentsDir.listFiles();
if(files == null)
if (files == null)
return;
for (File file : files) {
if (file.isDirectory() && deployments.stream().noneMatch(d -> d.name().equals(file.getName()))) {
try {
logger.info("Stopping deployment {}", file.getName());
List<String> commandArgs = new java.util.ArrayList<>(Arrays.stream(composeCommand.split(" ")).toList());
commandArgs.add("down");
new ProcessBuilder(commandArgs)
.directory(file)
.start()
.waitFor();
stopDeployment(file.getName());
deleteDirectory(file);
applicationEventPublisher.publishEvent(new NotConfiguredDeploymentStopped(this, file.getName()));
applicationEventPublisher.publishEvent(new DeploymentStateChangedEvent(this, file.getName(), DeploymentState.State.REMOVED, "Deployment '%s' removed (Not configured)".formatted(file.getName())));
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
@@ -155,6 +185,17 @@ public class DeploymentsService {
logger.info("Not configured deployments stopped");
}
private void stopDeployment(String deploymentName) throws InterruptedException, IOException {
logger.info("Stopping deployment {}", deploymentName);
List<String> commandArgs = new ArrayList<>(Arrays.stream(composeCommand.split(" ")).toList());
commandArgs.add("down");
new ProcessBuilder(commandArgs)
.directory(new File(DEPLOYMENTS_DIR_NAME + "/" + deploymentName))
.start()
.waitFor();
applicationEventPublisher.publishEvent(new DeploymentStateChangedEvent(this, deploymentName, DeploymentState.State.STOPPED, "Deployment '%s' stopped".formatted(deploymentName)));
}
void deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
@@ -164,11 +205,11 @@ public class DeploymentsService {
}
boolean deleted = directoryToBeDeleted.delete();
if(!deleted)
if (!deleted)
throw new RuntimeException("Could not delete directory " + directoryToBeDeleted);
}
private void deployListOfDeployments(List<Deployment> deployments, boolean force) {
private void deployListOfDeployments(List<Deployment> deployments, boolean forceRun, boolean forceRecreate) {
makeDeploymentsDir();
@@ -185,7 +226,7 @@ public class DeploymentsService {
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
for (Deployment deployment : deploymentsOrderUnit) {
executorService.submit(() -> deploy(deployment, force));
executorService.submit(() -> deploy(deployment, forceRun, forceRecreate));
}
executorService.shutdown();

View File

@@ -1,17 +1,14 @@
package org.davidbohl.dirigent.deployments.notification;
import org.davidbohl.dirigent.deployments.events.DeploymentStartFailedEvent;
import org.davidbohl.dirigent.deployments.events.DeploymentStartSucceededEvent;
import org.davidbohl.dirigent.deployments.events.NotConfiguredDeploymentStopped;
import org.davidbohl.dirigent.deployments.management.DeploymentsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.davidbohl.dirigent.deployments.events.DeploymentStateChangedEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
@Slf4j
public class NotificationService {
@Value("${dirigent.gotify.baseUrl:}")
@@ -20,28 +17,13 @@ public class NotificationService {
@Value("${dirigent.gotify.token:}")
private String gotifyToken;
private final Logger logger = LoggerFactory.getLogger(DeploymentsService.class);
@EventListener(DeploymentStateChangedEvent.class)
public void onDeploymentStateChanged(DeploymentStateChangedEvent event) {
String title = "Deployment \"%s\" state changed to %s".formatted(event.getDeploymentName(), event.getState());
String context = event.getContext();
sendGotifyMessage(title, context);
@EventListener(DeploymentStartFailedEvent.class)
public void onDeploymentStartFailed(DeploymentStartFailedEvent event) {
sendGotifyMessage(event.getMessage(), "Deployment \"%s\" Failed".formatted(event.getDeploymentName()));
logger.warn("Deployment '{}' failed. Error: {}", event.getDeploymentName(), event.getMessage());
}
@EventListener(DeploymentStartSucceededEvent.class)
public void onDeploymentStartSucceeded(DeploymentStartSucceededEvent event) {
sendGotifyMessage("Deployment succeeded", "Deployment \"%s\" Succeeded".formatted(event.getDeploymentName()));
logger.info("Deployment '{}' succeeded.", event.getDeploymentName());
}
@EventListener(NotConfiguredDeploymentStopped.class)
public void onNotConfiguredDeploymentStopped(NotConfiguredDeploymentStopped event) {
sendGotifyMessage("Deployment stopped", "Deployment \"%s\" stopped because it is not configured".formatted(event.getDeploymentName()));
logger.info("Deployment '{}' stopped because it is not configured.", event.getDeploymentName());
log.info("Deployment '{}' state changed to {}. Context: {}", event.getDeploymentName(), event.getState(), context);
}
private void sendGotifyMessage(String title, String message) {

View File

@@ -0,0 +1,27 @@
package org.davidbohl.dirigent.deployments.state;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
public class DeploymentState {
@Id
private String name;
@Enumerated(EnumType.STRING)
private State state;
@Column(length = 65535)
private String message;
public enum State {
STARTED, STOPPED, FAILED, REMOVED
}
}

View File

@@ -0,0 +1,31 @@
package org.davidbohl.dirigent.deployments.state;
import org.davidbohl.dirigent.deployments.events.DeploymentStateChangedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.StreamSupport;
@Service
public class DeploymentStatePersistingService {
final DeploymentStateRepository deploymentStateRepository;
public DeploymentStatePersistingService(DeploymentStateRepository deploymentStateRepository) {
this.deploymentStateRepository = deploymentStateRepository;
}
@EventListener(DeploymentStateChangedEvent.class)
public void handleDeploymentStateChangedEvent(DeploymentStateChangedEvent event) {
DeploymentState deploymentState = new DeploymentState(event.getDeploymentName(), event.getState(), event.getContext());
deploymentStateRepository.save(deploymentState);
}
public List<DeploymentState> getDeploymentStates() {
return StreamSupport.stream(deploymentStateRepository.findAll().spliterator(), false)
.toList();
}
}

View File

@@ -0,0 +1,6 @@
package org.davidbohl.dirigent.deployments.state;
import org.springframework.data.repository.CrudRepository;
public interface DeploymentStateRepository extends CrudRepository<DeploymentState, String> {
}

View File

@@ -27,7 +27,7 @@ public class GitService {
File destinationDir = new File(destination);
boolean changed = false;
boolean changed;
if (destinationDir.exists() && Arrays.asList(Objects.requireNonNull(destinationDir.list())).contains(".git")) {
logger.debug("Local Repo exists. Pulling latest changes.");

View File

@@ -1,2 +1,3 @@
dirigent.deployments.git.url=
dirigent.git.authToken=
spring.h2.console.enabled=true

View File

@@ -1,8 +1,15 @@
spring.application.name=dirigent
dirigent.compose.command=docker compose
dirigent.deployments.cache.evict.interval=60000
dirigent.start.all.on.startup=true
spring.profiles.active=local
spring.datasource.url=jdbc:h2:file:./data/statedb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=false
spring.jpa.hibernate.ddl-auto=update
dirigent.compose.command=docker compose
dirigent.start.all.on.startup=true
dirigent.git.authToken=
dirigent.delpoyments.schedule.enabled=true
dirigent.delpoyments.schedule.cron=0 */5 * * * *
dirigent.delpoyments.schedule.cron=0 */5 * * * *