Installer workflow is done - Docker container is working fine
This commit is contained in:
BIN
cygnus-installer/.DS_Store
vendored
Normal file
BIN
cygnus-installer/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -27,6 +27,12 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-resolver-dns-native-macos</artifactId>
|
||||
<classifier>osx-aarch_64</classifier>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cygnus.installer;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Service
|
||||
public class ActivationClient {
|
||||
@@ -16,20 +17,25 @@ public class ActivationClient {
|
||||
}
|
||||
|
||||
public ActivationDtos.ValidationResponse validate(
|
||||
String cloudServiceUrl,
|
||||
String clientCode,
|
||||
String licenseKey,
|
||||
UUID installationUuid,
|
||||
String installerVersion) {
|
||||
return webClient.post()
|
||||
.uri(settings.validationPath())
|
||||
.uri(endpoint(cloudServiceUrl, settings.validationPath()))
|
||||
.bodyValue(new ActivationDtos.ValidationRequest(
|
||||
clientCode, licenseKey, installationUuid, installerVersion))
|
||||
.retrieve()
|
||||
.onStatus(
|
||||
status -> status.isError(),
|
||||
response -> cloudFailure(response.statusCode().value(), response))
|
||||
.bodyToMono(ActivationDtos.ValidationResponse.class)
|
||||
.block();
|
||||
}
|
||||
|
||||
public ActivationDtos.RegistrationResponse register(
|
||||
String cloudServiceUrl,
|
||||
ActivationDtos.ValidationResponse validation,
|
||||
String installationCode,
|
||||
String installationName,
|
||||
@@ -37,7 +43,7 @@ public class ActivationClient {
|
||||
String softwareVersion,
|
||||
String environment) {
|
||||
return webClient.post()
|
||||
.uri(settings.registrationPath())
|
||||
.uri(endpoint(cloudServiceUrl, settings.registrationPath()))
|
||||
.bodyValue(new ActivationDtos.RegistrationRequest(
|
||||
validation.activationToken(),
|
||||
installationCode,
|
||||
@@ -46,7 +52,25 @@ public class ActivationClient {
|
||||
softwareVersion,
|
||||
environment))
|
||||
.retrieve()
|
||||
.onStatus(
|
||||
status -> status.isError(),
|
||||
response -> cloudFailure(response.statusCode().value(), response))
|
||||
.bodyToMono(ActivationDtos.RegistrationResponse.class)
|
||||
.block();
|
||||
}
|
||||
|
||||
private String endpoint(String cloudServiceUrl, String path) {
|
||||
String base = cloudServiceUrl.trim();
|
||||
return (base.endsWith("/") ? base.substring(0, base.length() - 1) : base)
|
||||
+ path;
|
||||
}
|
||||
|
||||
private Mono<? extends Throwable> cloudFailure(
|
||||
int status,
|
||||
org.springframework.web.reactive.function.client.ClientResponse response) {
|
||||
return response.bodyToMono(String.class)
|
||||
.defaultIfEmpty("No response details were provided")
|
||||
.map(body -> new IllegalStateException(
|
||||
"Cloud service returned HTTP " + status + ": " + body));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.cygnus.installer;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DockerComposeDeploymentService {
|
||||
|
||||
private static final Duration PULL_TIMEOUT = Duration.ofMinutes(5);
|
||||
private static final Duration START_TIMEOUT = Duration.ofMinutes(2);
|
||||
private static final Duration VERIFY_TIMEOUT = Duration.ofSeconds(30);
|
||||
|
||||
private final CommandExecutor executor;
|
||||
|
||||
public DockerComposeDeploymentService(CommandExecutor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void pull(Path deploymentDirectory) {
|
||||
require(
|
||||
command(deploymentDirectory, "pull"),
|
||||
PULL_TIMEOUT,
|
||||
"Could not pull the service image");
|
||||
}
|
||||
|
||||
public void start(Path deploymentDirectory) {
|
||||
require(
|
||||
command(deploymentDirectory, "up", "-d", "--remove-orphans"),
|
||||
START_TIMEOUT,
|
||||
"Could not start the service");
|
||||
}
|
||||
|
||||
public void verifyRunning(Path deploymentDirectory, String serviceName) {
|
||||
CommandResult result = executor.execute(
|
||||
command(deploymentDirectory, "ps", "--status", "running", "--services"),
|
||||
VERIFY_TIMEOUT);
|
||||
boolean running = result.successful()
|
||||
&& result.output().lines().anyMatch(serviceName::equals);
|
||||
if (!running) {
|
||||
throw new IllegalStateException(
|
||||
"The service container did not reach running state. "
|
||||
+ result.output());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> command(Path directory, String... arguments) {
|
||||
Path normalized = directory.toAbsolutePath().normalize();
|
||||
java.util.ArrayList<String> command = new java.util.ArrayList<>(List.of(
|
||||
"docker",
|
||||
"compose",
|
||||
"--project-directory",
|
||||
normalized.toString(),
|
||||
"--env-file",
|
||||
normalized.resolve(".env").toString(),
|
||||
"-f",
|
||||
normalized.resolve("compose.yml").toString()));
|
||||
command.addAll(List.of(arguments));
|
||||
return List.copyOf(command);
|
||||
}
|
||||
|
||||
private void require(
|
||||
List<String> command, Duration timeout, String failureMessage) {
|
||||
CommandResult result = executor.execute(command, timeout);
|
||||
if (!result.successful()) {
|
||||
throw new IllegalStateException(
|
||||
failureMessage + ". " + result.output());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ public class IniConfigurationLoader {
|
||||
"Unsupported product '" + product + "'. Expected matrix or cygnus");
|
||||
}
|
||||
URI cloudUri = absoluteHttpUri(ini.required("installer", "cloud_service_url"));
|
||||
URI containerCloudUri = absoluteHttpUri(ini.optional(
|
||||
"installer", "container_cloud_service_url", cloudUri.toString()));
|
||||
String apiBasePath = normalizedApiPath(
|
||||
ini.required("installer", "installer_api_base_path"));
|
||||
String environment = ini.required("installer", "environment");
|
||||
@@ -31,6 +33,7 @@ public class IniConfigurationLoader {
|
||||
return new InstallerSettings(
|
||||
product,
|
||||
cloudUri,
|
||||
containerCloudUri,
|
||||
apiBasePath,
|
||||
environment,
|
||||
Path.of(output),
|
||||
|
||||
@@ -7,4 +7,7 @@ public record InstallationResult(
|
||||
UUID installationId,
|
||||
String installationCode,
|
||||
String clientId,
|
||||
Path outputDirectory) {}
|
||||
Path outputDirectory,
|
||||
String serviceName,
|
||||
String serviceUrl,
|
||||
String serviceStatus) {}
|
||||
|
||||
@@ -27,9 +27,7 @@ public class InstallerConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebClient installerWebClient(InstallerSettings settings) {
|
||||
return WebClient.builder()
|
||||
.baseUrl(settings.cloudServiceUrl().toString())
|
||||
.build();
|
||||
WebClient installerWebClient() {
|
||||
return WebClient.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.nio.file.Path;
|
||||
public record InstallerSettings(
|
||||
String product,
|
||||
URI cloudServiceUrl,
|
||||
URI containerCloudServiceUrl,
|
||||
String installerApiBasePath,
|
||||
String environment,
|
||||
Path defaultOutputDirectory,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.cygnus.installer;
|
||||
|
||||
public record RuntimeConfiguration(
|
||||
String containerCloudServiceUrl,
|
||||
String databaseUrl,
|
||||
String databaseUsername,
|
||||
String databasePassword,
|
||||
String redisHost,
|
||||
String redisPort,
|
||||
String redisPassword,
|
||||
boolean redisSsl,
|
||||
String cloudRequestTimeout,
|
||||
String tokenRefreshSkew) {}
|
||||
@@ -26,6 +26,9 @@ public class TechnobeeServiceInstallerApplication implements CommandLineRunner {
|
||||
SpringApplication application =
|
||||
new SpringApplication(TechnobeeServiceInstallerApplication.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
// Spring Boot defaults to headless mode, which would force the installer
|
||||
// into its CLI fallback even on desktops that support Swing.
|
||||
application.setHeadless(false);
|
||||
application.setLogStartupInfo(false);
|
||||
try {
|
||||
ConfigurableApplicationContext context = application.run(args);
|
||||
|
||||
@@ -21,6 +21,7 @@ class IniConfigurationLoaderTest {
|
||||
[installer]
|
||||
product=matrix
|
||||
cloud_service_url=https://cloud.example.com/
|
||||
container_cloud_service_url=http://host.docker.internal:8090/
|
||||
installer_api_base_path=/api/v1/installations/
|
||||
environment=production
|
||||
""");
|
||||
@@ -30,6 +31,8 @@ class IniConfigurationLoaderTest {
|
||||
assertThat(settings.product()).isEqualTo("matrix");
|
||||
assertThat(settings.cloudServiceUrl().toString())
|
||||
.isEqualTo("https://cloud.example.com");
|
||||
assertThat(settings.containerCloudServiceUrl().toString())
|
||||
.isEqualTo("http://host.docker.internal:8090");
|
||||
assertThat(settings.validationPath())
|
||||
.isEqualTo("/api/v1/installations/activation/validate");
|
||||
assertThat(settings.registrationPath())
|
||||
@@ -38,6 +41,22 @@ class IniConfigurationLoaderTest {
|
||||
.isEqualTo(Path.of("./matrix-installation"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultsContainerCloudUrlToCanonicalCloudUrl() throws Exception {
|
||||
Path ini = write("""
|
||||
[installer]
|
||||
product=matrix
|
||||
cloud_service_url=https://cloud.example.com/
|
||||
installer_api_base_path=/api/v1/installations
|
||||
environment=production
|
||||
""");
|
||||
|
||||
InstallerSettings settings = loader.load(ini, "1.2.3");
|
||||
|
||||
assertThat(settings.containerCloudServiceUrl())
|
||||
.isEqualTo(settings.cloudServiceUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnknownProduct() throws Exception {
|
||||
Path ini = write("""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.cygnus.installer.DockerComposeDeploymentServiceTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.cygnus.installer.DockerComposeDeploymentServiceTest
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user