mirror of
https://github.com/DerDavidBohl/dirigent-spring.git
synced 2026-01-08 00:39:35 -06:00
Introduced stop feature and state persistence
https://github.com/DerDavidBohl/dirigent-spring/issues/21
This commit is contained in:
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'latest'
|
||||
- 'experimental'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,3 +37,4 @@ build/
|
||||
### Config Files ###
|
||||
/src/main/resources/application.properties
|
||||
/src/main/resources/application-local.properties
|
||||
/data/
|
||||
|
||||
52
README.md
52
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
7
pom.xml
7
pom.xml
@@ -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>
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.davidbohl.dirigent.deployments.state;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface DeploymentStateRepository extends CrudRepository<DeploymentState, String> {
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
dirigent.deployments.git.url=
|
||||
dirigent.git.authToken=
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
@@ -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 * * * *
|
||||
|
||||
Reference in New Issue
Block a user