diff --git a/build.gradle b/build.gradle.kts similarity index 51% rename from build.gradle rename to build.gradle.kts index 99e15f4..bc21b0b 100644 --- a/build.gradle +++ b/build.gradle.kts @@ -1,89 +1,74 @@ +object Dependency { + const val KOTLIN = "1.4.10" + const val MICRONAUT = "2.0.3" +} + plugins { - id "org.jetbrains.kotlin.jvm" version "${kotlinVersion}" - id "org.jetbrains.kotlin.kapt" version "${kotlinVersion}" - id "org.jetbrains.kotlin.plugin.allopen" version "${kotlinVersion}" - id "com.github.johnrengelman.shadow" version "6.0.0" - id "com.gorylenko.gradle-git-properties" version "2.2.3" - id "application" + kotlin("jvm") version "1.4.10" + kotlin("kapt") version "1.4.10" + kotlin("plugin.allopen") version "1.4.10" + id("com.github.johnrengelman.shadow") version "5.2.0" + application } -version "0.1" -group "com.ilunos.agent.docker" +version = "0.1" +group = "com.ilunos.agent.docker" repositories { + maven("https://dl.bintray.com/nanabell/ilunos") mavenCentral() jcenter() } -configurations { - // for dependencies that are needed for development only - developmentOnly -} +val developmentOnly = configurations.create("developmentOnly") + dependencies { - kapt(platform("io.micronaut:micronaut-bom:$micronautVersion")) + kapt(platform("io.micronaut:micronaut-bom:${Dependency.MICRONAUT}")) kapt("io.micronaut:micronaut-inject-java") kapt("io.micronaut:micronaut-validation") kapt("io.micronaut.security:micronaut-security-annotations") - implementation(platform("io.micronaut:micronaut-bom:$micronautVersion")) + + implementation(kotlin("stdlib-jdk8")) + implementation(kotlin("reflect")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8") + implementation("com.ilunos.common:common:1.0.1") + + implementation(platform("io.micronaut:micronaut-bom:${Dependency.MICRONAUT}")) implementation("io.micronaut:micronaut-inject") implementation("io.micronaut:micronaut-validation") implementation("io.micronaut:micronaut-management") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8") implementation("io.micronaut.kotlin:micronaut-kotlin-runtime") implementation("io.micronaut:micronaut-runtime") - implementation("javax.annotation:javax.annotation-api") + implementation("io.micronaut:micronaut-http-server-netty") implementation("io.micronaut:micronaut-http-client") implementation("io.micronaut.security:micronaut-security") implementation("io.micronaut.security:micronaut-security-oauth2") implementation("io.micronaut.security:micronaut-security-jwt") implementation("io.micronaut.kotlin:micronaut-kotlin-extension-functions") + implementation("javax.annotation:javax.annotation-api") + implementation("com.github.docker-java:docker-java:3.2.5") implementation("com.github.docker-java:docker-java-transport-httpclient5:3.2.5") + runtimeOnly("ch.qos.logback:logback-classic") runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin") - kaptTest(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion")) + + kaptTest(enforcedPlatform("io.micronaut:micronaut-bom:${Dependency.MICRONAUT}")) kaptTest("io.micronaut:micronaut-inject-java") - testImplementation(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion")) + + testImplementation(enforcedPlatform("io.micronaut:micronaut-bom:${Dependency.MICRONAUT}")) testImplementation("io.micronaut.test:micronaut-test-kotlintest") testImplementation("io.mockk:mockk:1.9.3") testImplementation("io.kotlintest:kotlintest-runner-junit5:3.3.2") } -test.classpath += configurations.developmentOnly - -mainClassName = "com.ilunos.agent.docker.IlunosKt" - -// use JUnit 5 platform -test { - useJUnitPlatform() -} - -java { - sourceCompatibility = JavaVersion.toVersion('13') -} - allOpen { - annotation("io.micronaut.aop.Around") -} -compileKotlin { - kotlinOptions { - jvmTarget = '13' - //Will retain parameter names for Java reflection - javaParameters = true - } -} -compileTestKotlin { - kotlinOptions { - jvmTarget = '13' - javaParameters = true - } + annotations("io.micronaut.aop.Around", "io.micronaut.scheduling.annotation.Scheduled") } + kapt { - correctErrorTypes = true arguments { arg("micronaut.processing.incremental", true) arg("micronaut.processing.annotations", "com.ilunos.agent.docker.*") @@ -92,21 +77,32 @@ kapt { } } -shadowJar { - mergeServiceFiles() -} +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "13" + javaParameters = true + } + } + compileTestKotlin { + kotlinOptions { + jvmTarget = "13" + javaParameters = true + } + } -tasks.withType(JavaExec) { - classpath += configurations.developmentOnly - jvmArgs('-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote') - if (gradle.startParameter.continuous) { - systemProperties( - 'micronaut.io.watch.restart': 'true', - 'micronaut.io.watch.enabled': 'true', - "micronaut.io.watch.paths": "src/main" - ) + shadowJar { + mergeServiceFiles() } -} + // use JUnit 5 platform + test { + useJUnitPlatform() + } +} +tasks.withType { + classpath += developmentOnly + options.compilerArgs.addAll(arrayOf("-XX:TieredStopAtLevel=1", "-Dcom.sun.management.jmxremote")) +} \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/settings.gradle b/settings.gradle.kts similarity index 100% rename from settings.gradle rename to settings.gradle.kts diff --git a/src/main/kotlin/com/ilunos/agent/docker/Ilunos.kt b/src/main/kotlin/com/ilunos/agent/docker/Ilunos.kt index 22b9e49..2b97e9a 100644 --- a/src/main/kotlin/com/ilunos/agent/docker/Ilunos.kt +++ b/src/main/kotlin/com/ilunos/agent/docker/Ilunos.kt @@ -1,6 +1,6 @@ package com.ilunos.agent.docker -import com.ilunos.agent.docker.util.ConfigUtils +import com.ilunos.common.config.ConfigUtils import io.micronaut.context.ApplicationContext import io.micronaut.context.annotation.Context import io.micronaut.context.env.Environment diff --git a/src/main/kotlin/com/ilunos/agent/docker/config/AgentConfig.kt b/src/main/kotlin/com/ilunos/agent/docker/config/AgentConfig.kt index bc431b1..66d8316 100644 --- a/src/main/kotlin/com/ilunos/agent/docker/config/AgentConfig.kt +++ b/src/main/kotlin/com/ilunos/agent/docker/config/AgentConfig.kt @@ -5,14 +5,12 @@ import com.ilunos.agent.docker.Ilunos import edu.umd.cs.findbugs.annotations.NonNull import io.micronaut.context.annotation.ConfigurationProperties import io.micronaut.core.annotation.Introspected -import jdk.jfr.BooleanFlag import java.io.Serializable @Introspected @ConfigurationProperties("agent") class AgentConfig : Serializable { - @BooleanFlag @JsonProperty("auto-connect") var autoConnect: Boolean = true diff --git a/src/main/kotlin/com/ilunos/agent/docker/config/AgentSSLConfig.kt b/src/main/kotlin/com/ilunos/agent/docker/config/AgentSSLConfig.kt new file mode 100644 index 0000000..f4a9845 --- /dev/null +++ b/src/main/kotlin/com/ilunos/agent/docker/config/AgentSSLConfig.kt @@ -0,0 +1,26 @@ +package com.ilunos.agent.docker.config + +import com.fasterxml.jackson.annotation.JsonProperty +import io.micronaut.context.annotation.ConfigurationProperties +import java.nio.file.Path + +@ConfigurationProperties("agent.ssl") +class AgentSSLConfig { + + var type: AgentSSLType = AgentSSLType.NONE + + @JsonProperty("keystore-file") + var keystoreFile: Path? = null + + @JsonProperty("keystore-pass") + var keystorePass: String? = null + + @JsonProperty("pem-directory") + var pemDirectory: Path? = null + + enum class AgentSSLType { + NONE, + KEYSTORE, + PEM_FILES + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/config/OrchestratorConfig.kt b/src/main/kotlin/com/ilunos/agent/docker/config/OrchestratorConfig.kt index 611bac1..2233a44 100644 --- a/src/main/kotlin/com/ilunos/agent/docker/config/OrchestratorConfig.kt +++ b/src/main/kotlin/com/ilunos/agent/docker/config/OrchestratorConfig.kt @@ -11,6 +11,8 @@ class OrchestratorConfig { lateinit var url: URI + var enabled: Boolean = false + @Nullable var token: String? = null } diff --git a/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorConnectRequest.kt b/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorConnectRequest.kt new file mode 100644 index 0000000..850328a --- /dev/null +++ b/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorConnectRequest.kt @@ -0,0 +1,9 @@ +package com.ilunos.agent.docker.domain + +data class OrchestratorConnectRequest( + val name: String, + val token: String?, + val hostname: String, + val port: Int +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorConnectResponse.kt b/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorConnectResponse.kt new file mode 100644 index 0000000..1581f3d --- /dev/null +++ b/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorConnectResponse.kt @@ -0,0 +1,7 @@ +package com.ilunos.agent.docker.domain + +data class OrchestratorConnectResponse( + val token: String?, + val connected: Boolean, + val errorMessage: String? +) \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorRequest.kt b/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorRequest.kt deleted file mode 100644 index 4c131e8..0000000 --- a/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.ilunos.agent.docker.domain - -data class OrchestratorRequest( - val name: String, - val url: String -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorResponse.kt b/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorResponse.kt deleted file mode 100644 index 0a30faa..0000000 --- a/src/main/kotlin/com/ilunos/agent/docker/domain/OrchestratorResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.ilunos.agent.docker.domain - -data class OrchestratorResponse( - val status: String -) { -} \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/service/DockerContext.kt b/src/main/kotlin/com/ilunos/agent/docker/service/DockerContext.kt index ffa1e82..1d5d7c5 100644 --- a/src/main/kotlin/com/ilunos/agent/docker/service/DockerContext.kt +++ b/src/main/kotlin/com/ilunos/agent/docker/service/DockerContext.kt @@ -8,10 +8,8 @@ import com.github.dockerjava.api.model.Container import com.github.dockerjava.api.model.Image import com.github.dockerjava.api.model.Info import com.github.dockerjava.api.model.Version -import com.github.dockerjava.core.DefaultDockerClientConfig -import com.github.dockerjava.core.DockerClientBuilder -import com.github.dockerjava.httpclient5.ApacheDockerHttpClient import com.ilunos.agent.docker.config.AgentConfig +import com.ilunos.agent.docker.config.AgentSSLConfig import com.ilunos.agent.docker.exception.AgentNotConnectedException import com.ilunos.agent.docker.model.ConnectionStatus import io.micronaut.context.annotation.Context @@ -24,12 +22,11 @@ import java.util.* @Context @Infrastructure -class DockerContext(private val agentConfig: AgentConfig) { +class DockerContext(private val agentConfig: AgentConfig, private val agentSSLConfig: AgentSSLConfig) { private val logger: Logger = LoggerFactory.getLogger(DockerContext::class.java) private lateinit var client: DockerClient - private var status: ConnectionStatus = ConnectionStatus.UNKNOWN init { @@ -156,22 +153,13 @@ class DockerContext(private val agentConfig: AgentConfig) { fun connect() { disconnect() - - val clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder() - .withDockerHost(agentConfig.url) - .build() - - val httpClient = ApacheDockerHttpClient.Builder() - .dockerHost(clientConfig.dockerHost) - .sslConfig(clientConfig.sslConfig) - .build() - this.status = ConnectionStatus.BUILDING try { - this.client = DockerClientBuilder.getInstance(clientConfig).withDockerHttpClient(httpClient).build() - client.pingCmd().exec() + this.client = DockerContextBuilder(agentConfig, agentSSLConfig).build() + logger.debug("Attempting to connect to Docker System at '${agentConfig.url}'") + client.pingCmd().exec() this.status = ConnectionStatus.CONNECTED logger.info("Connected to Docker System at ${agentConfig.url}") diff --git a/src/main/kotlin/com/ilunos/agent/docker/service/DockerContextBuilder.kt b/src/main/kotlin/com/ilunos/agent/docker/service/DockerContextBuilder.kt new file mode 100644 index 0000000..cb085b7 --- /dev/null +++ b/src/main/kotlin/com/ilunos/agent/docker/service/DockerContextBuilder.kt @@ -0,0 +1,68 @@ +package com.ilunos.agent.docker.service + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.core.* +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient +import com.ilunos.agent.docker.config.AgentConfig +import com.ilunos.agent.docker.config.AgentSSLConfig +import org.slf4j.LoggerFactory +import java.nio.file.Files +import javax.naming.ConfigurationException + +class DockerContextBuilder(private val agentConfig: AgentConfig, private val agentSSLConfig: AgentSSLConfig) { + + private val logger = LoggerFactory.getLogger(DockerContextBuilder::class.java) + + fun build(): DockerClient { + logger.debug("Start building new DockerClient...") + + val configBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost(agentConfig.url) + val clientConfig = loadSSlConfig(configBuilder) + + val httpClient = ApacheDockerHttpClient.Builder() + .dockerHost(clientConfig.dockerHost) + .sslConfig(clientConfig.sslConfig) + .build() + + return DockerClientImpl.getInstance(clientConfig, httpClient) + } + + + private fun loadSSlConfig(builder: DefaultDockerClientConfig.Builder): DockerClientConfig { + return when (agentSSLConfig.type) { + AgentSSLConfig.AgentSSLType.NONE -> loadNoSSL(builder) + AgentSSLConfig.AgentSSLType.KEYSTORE -> loadKeystoreSSl(builder) + AgentSSLConfig.AgentSSLType.PEM_FILES -> loadPemSSL(builder) + } + } + + private fun loadNoSSL(builder: DefaultDockerClientConfig.Builder): DockerClientConfig { + logger.debug("Building DockerClient without SSL Config") + + return builder.build() + } + + private fun loadKeystoreSSl(builder: DefaultDockerClientConfig.Builder): DockerClientConfig { + logger.debug("Building DockerClient with Keystore SSL Config") + + TODO("Keystore SSL Config is not yet implemented") + } + + private fun loadPemSSL(builder: DefaultDockerClientConfig.Builder): DockerClientConfig { + logger.debug("Building DockerClient with PemDirectory SSL Config") + + val directory = agentSSLConfig.pemDirectory + ?: throw ConfigurationException("Property 'agent.ssl.pem-directory' is required when using SSL Type 'PEM_FILES'!") + + if (!Files.isDirectory(directory)) + throw ConfigurationException("Property 'agent.ssl.pem-directory' does not exist or is not a directory!") + + logger.debug("Using PemDirectory at '$directory'") + return builder + .withCustomSslConfig(LocalDirectorySSLConfig(directory.toAbsolutePath().toString())) + .withDockerTlsVerify(true) + .build() + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/service/HeartbeatService.kt b/src/main/kotlin/com/ilunos/agent/docker/service/HeartbeatService.kt new file mode 100644 index 0000000..f7f36b4 --- /dev/null +++ b/src/main/kotlin/com/ilunos/agent/docker/service/HeartbeatService.kt @@ -0,0 +1,59 @@ +package com.ilunos.agent.docker.service + +import com.ilunos.agent.docker.config.OrchestratorConfig +import com.ilunos.agent.docker.domain.OrchestratorConnectRequest +import com.ilunos.agent.docker.domain.OrchestratorConnectResponse +import io.micronaut.context.annotation.Requires +import io.micronaut.core.util.StringUtils +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.client.HttpClient +import io.micronaut.runtime.server.EmbeddedServer +import io.micronaut.scheduling.annotation.Async +import io.micronaut.scheduling.annotation.Scheduled +import io.reactivex.Flowable +import org.slf4j.LoggerFactory +import java.net.InetAddress +import javax.inject.Singleton + +@Singleton +@Requires(property = "agent.orchestrator.enabled", value = StringUtils.TRUE, defaultValue = StringUtils.FALSE) +open class HeartbeatService(private val config: OrchestratorConfig, private val server: EmbeddedServer) { + + private val logger = LoggerFactory.getLogger(HeartbeatService::class.java) + private val client = HttpClient.create(config.url.toURL()) + + private var token: String? = null + private var name: String = InetAddress.getLocalHost().hostName + + @Async + @Scheduled(initialDelay = "2s") + open fun connect() { + val request = HttpRequest.PUT("/agents", OrchestratorConnectRequest(name, token, InetAddress.getLocalHost().hostName, server.port)) + request.basicAuth("agent", config.token ?: "none") + + Flowable.fromPublisher(client.exchange(request, OrchestratorConnectResponse::class.java)).subscribe({ + if (it.status != HttpStatus.OK) { + logger.warn("Unexpected Server Response: ${it.status}") + return@subscribe + } + + val response = it.body() ?: throw IllegalStateException("OrchestratorConnectResponse Body is empty!?") + if (response.connected) { + this.token = response.token + + logger.info("Initial Heartbeat check successful. Orchestrator: ${config.url}") + return@subscribe + } + + logger.error("Failed Initial Heartbeat check: ${response.errorMessage}") + + }, { + if (logger.isDebugEnabled) + logger.error("Failed to connect to Orchestrator! Is it running?", it) + else + logger.error("Failed to connect to Orchestrator! Is it running?") + }) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/ilunos/agent/docker/service/OrchestratorRegister.kt b/src/main/kotlin/com/ilunos/agent/docker/service/OrchestratorRegister.kt deleted file mode 100644 index d047a40..0000000 --- a/src/main/kotlin/com/ilunos/agent/docker/service/OrchestratorRegister.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.ilunos.agent.docker.service - -import com.ilunos.agent.docker.config.OrchestratorConfig -import com.ilunos.agent.docker.domain.OrchestratorRequest -import com.ilunos.agent.docker.domain.OrchestratorResponse -import io.micronaut.context.annotation.Context -import io.micronaut.context.annotation.Requires -import io.micronaut.http.HttpRequest -import io.micronaut.http.HttpStatus -import io.micronaut.http.client.HttpClient -import io.micronaut.http.server.util.HttpHostResolver -import io.reactivex.Flowable -import org.slf4j.LoggerFactory -import java.net.InetAddress - -@Context -@Requires(property = "agent.orchestrator.url") -class OrchestratorRegister( - private val config: OrchestratorConfig, - private val resolver: HttpHostResolver -) { - - private val logger = LoggerFactory.getLogger(OrchestratorRegister::class.java) - - init { - initialize() - } - - private fun initialize() { - val client = HttpClient.create(config.url.toURL()) - - val hostname = InetAddress.getLocalHost().hostName - val selfUrl = resolver.resolve(HttpRequest.GET("/")) - - val request = HttpRequest.PUT("/agents", OrchestratorRequest(hostname, selfUrl)) - request.basicAuth("agent", config.token ?: "none") - - Flowable.fromPublisher(client.exchange(request, OrchestratorResponse::class.java)).subscribe({ - if (it.status == HttpStatus.OK) { - when (it.body()?.status) { - "CREATED" -> logger.info("Registered Agent at Orchestrator at ${config.url}") - "ALREADY_EXISTING" -> logger.info("Connected to Orchestrator at ${config.url}") - else -> logger.warn("Unexpected response from Orchestrator. Status: ${it.body()?.status}") - } - } else { - logger.error("Failed to register at Orchestrator ${config.url}, Status: ${it.status}") - } - }, { - logger.error("Failed to register at Orchestrator! Is it Running and reachable?", it) - }) - } -} diff --git a/src/main/kotlin/com/ilunos/agent/docker/util/ConfigUtils.kt b/src/main/kotlin/com/ilunos/agent/docker/util/ConfigUtils.kt deleted file mode 100644 index 53a23b5..0000000 --- a/src/main/kotlin/com/ilunos/agent/docker/util/ConfigUtils.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.ilunos.agent.docker.util - -import java.nio.file.Files -import java.nio.file.Path - -object ConfigUtils { - - private val config = System.getProperty("ilunos.configurationPath", "config") - - fun initialize() { - val configPath = Path.of(config) - - // Ensure Config Path exists, create if necessary - if (!Files.exists(configPath)) - Files.createDirectories(configPath) - - // Ensure Config/application.yml exists, copy template file of necessary - val appConfig = configPath.resolve("application.yml") - if (!Files.exists(appConfig)) - copyTemplateConfig(appConfig) - - // If config includes a custom logback.xml use that instead of the bundled - val loggerConfig = configPath.resolve("logback.xml") - if (Files.exists(loggerConfig)) - System.setProperty("logback.configurationFile", loggerConfig.toString()) - } - - private fun copyTemplateConfig(targetPath: Path) { - val stream = {}.javaClass.classLoader.getResourceAsStream("templates/application.yml") - ?: throw IllegalStateException("Unable to find 'templates/application.yml'!") - - Files.copy(stream, targetPath) - } -} \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index af9827e..2c9cb11 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -5,8 +5,7 @@ - %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n - + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n diff --git a/src/main/resources/templates/application.yml b/src/main/resources/templates/application.yml index 310f45e..b744e98 100644 --- a/src/main/resources/templates/application.yml +++ b/src/main/resources/templates/application.yml @@ -112,13 +112,27 @@ agent: # # TCP does currently not support TLS authenticated against the Docker Engine # and would require to open an unsecured tcp port on your machine. - # url: unix:///var/run/docker.sock + url: unix:///var/run/docker.sock # Attempt to connect to the specified docker url above on startup. # If disabled you will need to manually call the /docker/connect endpoint to initiate the connection - # auto-connect: true + auto-connect: true + + ssl: + + # type of SSL configuration. Can be 'none', 'keystore' or 'pem_files' + # Setting this to none disables SSL configuration + type: none + + keystore-file: # Path to either a .jks or .pkcs12 file. + keystore-pass: # Password for the keystore or pkcs12 file. + + # Path to a directory containing a ca.pem, key.pem & cert.pem + # These files must be named and have the file extension as described above! + pem-directory: orchestrator: + enabled: false # Base Url of the Orchestrator, The orchestrator needs to live somewhere from where it can reach the agent # url: http://localhost:8080