commit 63552dfc7b1ae0e0ac8edf0d42ad2c96fc9592a7 Author: eater <=@eater.me> Date: Mon Jan 27 20:04:30 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91ea741 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..8a163f1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java' + id 'idea' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.beryx.runtime' version '1.2.0' + id 'org.jetbrains.kotlin.jvm' version '1.3.40' +} + +group 'me.eater.hefbrug' +version '1.0-SNAPSHOT' + + +repositories { + mavenCentral() +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation "org.jetbrains.kotlin:kotlin-reflect" + + // Scripting dependencies + implementation 'org.jetbrains.kotlin:kotlin-compiler-embeddable' + implementation 'org.jetbrains.kotlin:kotlin-scripting-common' + implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm' + implementation 'org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable' + implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm-host-embeddable' + + // Coroutines + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.0-M2' + + // Logging + implementation "org.apache.logging.log4j:log4j-api-kotlin:1.0.0" + implementation "org.apache.logging.log4j:log4j-api:2.11.1" + implementation "org.apache.logging.log4j:log4j-core:2.11.1" + // Allows ANSI in logs + implementation 'org.fusesource.jansi:jansi:1.18' +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +shadowJar { + baseName = 'hefbrug' + classifier = null + version = null +} + +runtime { + options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] +} + +mainClassName = "me.eater.hefbrug.Main" diff --git a/examples/build.gradle b/examples/build.gradle new file mode 100644 index 0000000..779f6e0 --- /dev/null +++ b/examples/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.40' +} + +group 'me.eater.hefbrug.examples' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +assemble { + dependsOn gradle.includedBuild('hefbrug').task(':jar') +} + +dependencies { + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + + // Hefbrug + implementation 'me.eater.hefbrug:hefbrug:1.0-SNAPSHOT' +} + +sourceSets { + main { + kotlin { + srcDir "src/" + } + } +} \ No newline at end of file diff --git a/examples/gradle b/examples/gradle new file mode 120000 index 0000000..3337596 --- /dev/null +++ b/examples/gradle @@ -0,0 +1 @@ +../gradle \ No newline at end of file diff --git a/examples/gradlew b/examples/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/examples/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/examples/settings.gradle b/examples/settings.gradle new file mode 100644 index 0000000..717915f --- /dev/null +++ b/examples/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "hefbrug-examples" +includeBuild("..") \ No newline at end of file diff --git a/examples/src/entry.hb.kts b/examples/src/entry.hb.kts new file mode 100644 index 0000000..4ee6eda --- /dev/null +++ b/examples/src/entry.hb.kts @@ -0,0 +1,6 @@ +include( + "./selectors.hb.kts", + "./packages.hb.kts" +) + + diff --git a/examples/src/packages.hb.kts b/examples/src/packages.hb.kts new file mode 100644 index 0000000..094ef24 --- /dev/null +++ b/examples/src/packages.hb.kts @@ -0,0 +1,17 @@ +group("dev") { + pkg( + "openssh", + "neovim", + "git", + "curl", + "youtube-dl", + "PackageKit" + ) { + // installed + upgraded + + if (id == "curl") { + require += pkg["xbps"] + } + } +} \ No newline at end of file diff --git a/examples/src/selectors.hb.kts b/examples/src/selectors.hb.kts new file mode 100644 index 0000000..5efd987 --- /dev/null +++ b/examples/src/selectors.hb.kts @@ -0,0 +1,5 @@ +assign { + node("momo") { + groups += "dev" + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..94336fc Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e0c4de3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..838071e --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'hefbrug' \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/Main.kt b/src/main/kotlin/me/eater/hefbrug/Main.kt new file mode 100644 index 0000000..ba5200f --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/Main.kt @@ -0,0 +1,38 @@ +package me.eater.hefbrug + +import kotlinx.coroutines.runBlocking +import me.eater.hefbrug.executor.ExecutionInstance +import me.eater.hefbrug.executor.Executor +import me.eater.hefbrug.logging.LoggerConfig +import me.eater.hefbrug.node.Node +import org.apache.logging.log4j.kotlin.loggerOf +import java.nio.file.Paths +import java.time.Instant + +object Main { + @JvmStatic + fun main(args: Array) { + System.setProperty("log4j2.configurationFactory", LoggerConfig::class.qualifiedName!!) + System.setProperty("log4j.skipJansi", "false") + val logger = loggerOf(this.javaClass) + logger.info("Booting hefbrug") + logger.info("Compiling configuration") + val start = Instant.now() + val scope = Executor().apply { + run(Paths.get(args[0]).toAbsolutePath().toString()) + } + .getScope() + + val end = Instant.now() + logger.info("Compiled configuration in ${end.epochSecond - start.epochSecond}s") + + runBlocking { + val executionInstance = ExecutionInstance.forNode( + Node(Runtime.getRuntime().exec("hostname").inputStream.bufferedReader().readText().trim()), + scope + ) + + executionInstance.apply(false) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/access/AccessSkeleton.kt b/src/main/kotlin/me/eater/hefbrug/access/AccessSkeleton.kt new file mode 100644 index 0000000..976f4a4 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/AccessSkeleton.kt @@ -0,0 +1,15 @@ +package me.eater.hefbrug.access + +interface AccessSkeleton { + fun id(): String + suspend fun execute( + vararg command: String, + environment: Map = mapOf(), + workingDirectory: String? = null + ): ExecutionOutput + + suspend fun exists(fileName: String, type: FileType = FileType.Anything): Boolean { + val res = execute("test", type.switch, fileName) + return res.exitCode == 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/access/ExecutionCommand.kt b/src/main/kotlin/me/eater/hefbrug/access/ExecutionCommand.kt new file mode 100644 index 0000000..76dba64 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/ExecutionCommand.kt @@ -0,0 +1,27 @@ +package me.eater.hefbrug.access + +data class ExecutionCommand( + val command: Array, + val environment: Map, + val workingDirectory: String? +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ExecutionCommand + + if (!command.contentEquals(other.command)) return false + if (environment != other.environment) return false + if (workingDirectory != other.workingDirectory) return false + + return true + } + + override fun hashCode(): Int { + var result = command.contentHashCode() + result = 31 * result + environment.hashCode() + result = 31 * result + (workingDirectory?.hashCode() ?: 0) + return result + } +} diff --git a/src/main/kotlin/me/eater/hefbrug/access/ExecutionOutput.kt b/src/main/kotlin/me/eater/hefbrug/access/ExecutionOutput.kt new file mode 100644 index 0000000..01f0bbf --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/ExecutionOutput.kt @@ -0,0 +1,16 @@ +package me.eater.hefbrug.access + +import me.eater.hefbrug.utils.escape + +data class ExecutionOutput(val command: ExecutionCommand, val exitCode: Int, val stdout: String, val stderr: String) { + fun orThrow() { + if (exitCode != 0) { + throw ExecutionException( + "Execution of [${command.command.joinToString(" ") { it.escape() }}] failed with exit code $exitCode", + this + ) + } + } + + class ExecutionException(message: String?, val output: ExecutionOutput) : RuntimeException(message) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/access/FileType.kt b/src/main/kotlin/me/eater/hefbrug/access/FileType.kt new file mode 100644 index 0000000..e559e78 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/FileType.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.access + +enum class FileType(val switch: String) { + Anything("-e"), + Directory("-d"), + File("-f"); +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/access/Local.kt b/src/main/kotlin/me/eater/hefbrug/access/Local.kt new file mode 100644 index 0000000..46da78c --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/Local.kt @@ -0,0 +1,41 @@ +package me.eater.hefbrug.access + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import org.apache.logging.log4j.kotlin.Logging +import java.io.File +import java.nio.charset.Charset + +class Local : AccessSkeleton, Logging { + override fun id() = "local" + + override suspend fun execute( + vararg command: String, + environment: Map, + workingDirectory: String? + ): ExecutionOutput { + val process = GlobalScope.async(Dispatchers.Unconfined) { + ProcessBuilder() + .apply { + command(*command) + + if (workingDirectory != null) + directory(File(workingDirectory)) + + environment().putAll(environment) + } + .start() + .apply { + waitFor() + } + }.await() + + return ExecutionOutput( + ExecutionCommand(command, environment, workingDirectory), + process.exitValue(), + process.inputStream.bufferedReader(Charset.forName("UTF-8")).readText(), + process.errorStream.bufferedReader(Charset.forName("UTF-8")).readText() + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/access/NoopAccess.kt b/src/main/kotlin/me/eater/hefbrug/access/NoopAccess.kt new file mode 100644 index 0000000..a4cfd93 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/NoopAccess.kt @@ -0,0 +1,15 @@ +package me.eater.hefbrug.access + +import me.eater.hefbrug.logging.Logging + +class NoopAccess : AccessSkeleton, Logging { + override fun id() = "noop" + + override suspend fun execute( + vararg command: String, + environment: Map, + workingDirectory: String? + ): ExecutionOutput { + return ExecutionOutput(ExecutionCommand(command, environment, workingDirectory), 0, "", "") + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/access/Wrapper.kt b/src/main/kotlin/me/eater/hefbrug/access/Wrapper.kt new file mode 100644 index 0000000..55f7c43 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/access/Wrapper.kt @@ -0,0 +1,22 @@ +package me.eater.hefbrug.access + +import me.eater.hefbrug.logging.Logging +import me.eater.hefbrug.utils.escape +import org.apache.logging.log4j.Level + +class Wrapper(private val parent: AccessSkeleton, val rw: Boolean = true) : AccessSkeleton by parent, Logging { + + + override suspend fun execute( + vararg command: String, + environment: Map, + workingDirectory: String? + ): ExecutionOutput { + log(if (rw) Level.INFO else Level.DEBUG, "Executing [${id()}][${if (rw) "rw" else "ro"}] [${command.joinToString(" ") { it.escape() }}]") + return parent.execute(*command, environment = environment, workingDirectory = workingDirectory) + } + + override suspend fun exists(fileName: String, type: FileType): Boolean { + return super.exists(fileName, type) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/collector/AbstractCollector.kt b/src/main/kotlin/me/eater/hefbrug/collector/AbstractCollector.kt new file mode 100644 index 0000000..4f84569 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/collector/AbstractCollector.kt @@ -0,0 +1,9 @@ +package me.eater.hefbrug.collector + +import me.eater.hefbrug.definition.DefinitionWildcard +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.AbstractState + +abstract class AbstractCollector>(val context: ExecutionContext) { + abstract suspend fun collect(definition: D): S +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/collector/impl/PackageCollector.kt b/src/main/kotlin/me/eater/hefbrug/collector/impl/PackageCollector.kt new file mode 100644 index 0000000..807e6bf --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/collector/impl/PackageCollector.kt @@ -0,0 +1,21 @@ +package me.eater.hefbrug.collector.impl + +import me.eater.hefbrug.collector.AbstractCollector +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.definition.impl.PackageDefinition +import me.eater.hefbrug.state.ExistenceStatus +import me.eater.hefbrug.state.impl.PackageState + +class PackageCollector(context: ExecutionContext) : AbstractCollector(context) { + override suspend fun collect(definition: PackageDefinition): PackageState { + val pm = context.getPackageManager() + + return PackageState(definition.id).apply { + name = definition.state.name + status = if (pm.isInstalled(definition.state.name)) + ExistenceStatus.Present + else + ExistenceStatus.Absent + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/collector/impl/ServiceCollector.kt b/src/main/kotlin/me/eater/hefbrug/collector/impl/ServiceCollector.kt new file mode 100644 index 0000000..96743fa --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/collector/impl/ServiceCollector.kt @@ -0,0 +1,20 @@ +package me.eater.hefbrug.collector.impl + +import me.eater.hefbrug.collector.AbstractCollector +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.definition.impl.ServiceDefinition +import me.eater.hefbrug.state.impl.ServiceState + +class ServiceCollector(context: ExecutionContext) : AbstractCollector(context) { + override suspend fun collect(definition: ServiceDefinition): ServiceState { + val serviceName = definition.state.name + + val sm = context.getServiceManager() + + return ServiceState(definition.id).apply { + name = serviceName + enabled = sm.isEnabled(serviceName) + running = sm.isRunning(serviceName) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/definition/AbstractDefinition.kt b/src/main/kotlin/me/eater/hefbrug/definition/AbstractDefinition.kt new file mode 100644 index 0000000..bf60b92 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/AbstractDefinition.kt @@ -0,0 +1,15 @@ +package me.eater.hefbrug.definition + +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.state.AbstractState + +abstract class AbstractDefinition>( + override val key: DefinitionKey, + override val state: S +) : DefinitionSkeleton { + override val id: String get() = state.id + override val require: MutableSet = mutableSetOf() + override val before: MutableSet = mutableSetOf() + override val after: MutableSet = mutableSetOf() +} + diff --git a/src/main/kotlin/me/eater/hefbrug/definition/DefinitionFactory.kt b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionFactory.kt new file mode 100644 index 0000000..4048dbd --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionFactory.kt @@ -0,0 +1,23 @@ +package me.eater.hefbrug.definition + +import me.eater.hefbrug.collector.AbstractCollector +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.dsl.context.extension_util.FactoryRegister +import me.eater.hefbrug.enforcer.AbstractEnforcer +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.AbstractState + +abstract class DefinitionFactory, D : DefinitionSkeleton>( + val id: String, + val builder: (String) -> D +) { + fun build(id: String): D = builder(id) + open fun key(id: String): DefinitionKey = DefinitionKey(this.id, id) + + abstract fun createCollector(context: ExecutionContext): AbstractCollector + abstract fun createEnforcer(context: ExecutionContext): AbstractEnforcer + + init { + FactoryRegister += this + } +} diff --git a/src/main/kotlin/me/eater/hefbrug/definition/DefinitionKey.kt b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionKey.kt new file mode 100644 index 0000000..293d9d6 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionKey.kt @@ -0,0 +1,26 @@ +package me.eater.hefbrug.definition + +import me.eater.hefbrug.logging.LogFormat + +open class DefinitionKey(val group: String, val id: String) : LogFormat { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DefinitionKey + + if (group != other.group) return false + if (id != other.id) return false + + return true + } + + override fun hashCode(): Int { + var result = group.hashCode() + result = 31 * result + id.hashCode() + return result + } + + override fun toString(): String = "$group.$id" + override fun logFormat() = "@|blue $this|@" +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/definition/DefinitionKeyAction.kt b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionKeyAction.kt new file mode 100644 index 0000000..484d9ae --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionKeyAction.kt @@ -0,0 +1,3 @@ +package me.eater.hefbrug.definition + +open class DefinitionKeyAction(name: String, id: String, val action: String) : DefinitionKey(name, id) \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/definition/DefinitionSkeleton.kt b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionSkeleton.kt new file mode 100644 index 0000000..cc52e92 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionSkeleton.kt @@ -0,0 +1,16 @@ +package me.eater.hefbrug.definition + +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.state.AbstractState + +interface DefinitionSkeleton> { + val key: DefinitionKey + val state: S + + val id: String + val require: MutableSet + val before: MutableSet + val after: MutableSet + + fun getContext(): C +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/definition/DefinitionWildcard.kt b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionWildcard.kt new file mode 100644 index 0000000..3c6f2a7 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/DefinitionWildcard.kt @@ -0,0 +1,5 @@ +package me.eater.hefbrug.definition + +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton + +typealias DefinitionWildcard = DefinitionSkeleton> diff --git a/src/main/kotlin/me/eater/hefbrug/definition/impl/PackageDefinition.kt b/src/main/kotlin/me/eater/hefbrug/definition/impl/PackageDefinition.kt new file mode 100644 index 0000000..9e6bc43 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/impl/PackageDefinition.kt @@ -0,0 +1,31 @@ +package me.eater.hefbrug.definition.impl + +import me.eater.hefbrug.collector.impl.PackageCollector +import me.eater.hefbrug.definition.AbstractDefinition +import me.eater.hefbrug.definition.DefinitionFactory +import me.eater.hefbrug.dsl.context.impl.PackageContext +import me.eater.hefbrug.enforcer.impl.PackageEnforcer +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.impl.PackageState + +class PackageDefinition(id: String) : + AbstractDefinition(factory.key(id), PackageState(id)) { + override fun getContext(): PackageContext = PackageContext(this) + + class Factory : DefinitionFactory("package", ::PackageDefinition) { + override fun createCollector(context: ExecutionContext): PackageCollector = + PackageCollector(context) + + override fun createEnforcer(context: ExecutionContext): PackageEnforcer = + PackageEnforcer(context) + } + + object KeyHelper { + operator fun get(id: String) = factory.key(id) + } + + companion object { + val factory: Factory = + Factory() + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/definition/impl/ServiceDefinition.kt b/src/main/kotlin/me/eater/hefbrug/definition/impl/ServiceDefinition.kt new file mode 100644 index 0000000..a51d24a --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/definition/impl/ServiceDefinition.kt @@ -0,0 +1,39 @@ +package me.eater.hefbrug.definition.impl + +import me.eater.hefbrug.collector.impl.ServiceCollector +import me.eater.hefbrug.definition.AbstractDefinition +import me.eater.hefbrug.definition.DefinitionFactory +import me.eater.hefbrug.definition.DefinitionKeyAction +import me.eater.hefbrug.dsl.context.impl.ServiceContext +import me.eater.hefbrug.enforcer.impl.ServiceEnforcer +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.impl.ServiceState + +class ServiceDefinition(id: String) : + AbstractDefinition(factory.key(id), ServiceState(id)) { + override fun getContext(): ServiceContext = ServiceContext(this) + + class Factory : DefinitionFactory("service", ::ServiceDefinition) { + override fun key(id: String) = Key(this.id, id, "default") + override fun createCollector(context: ExecutionContext): ServiceCollector = + ServiceCollector(context) + + override fun createEnforcer(context: ExecutionContext): ServiceEnforcer = + ServiceEnforcer(context) + } + + + class Key(name: String, id: String, action: String) : DefinitionKeyAction(name, id, action) { + fun reload() = Key(group, id, "reload") + fun restart() = Key(group, id, "restart") + } + + object KeyHelper { + operator fun get(id: String) = factory.key(id) + } + + companion object { + val factory: Factory = + Factory() + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/Location.kt b/src/main/kotlin/me/eater/hefbrug/dsl/Location.kt new file mode 100644 index 0000000..a80c998 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/Location.kt @@ -0,0 +1,20 @@ +package me.eater.hefbrug.dsl + +import java.nio.file.Paths + +sealed class Location { + data class File(override val path: String) : Location() { + override val directory: String + get() = java.io.File(path).parent.toString() + } + + object Memory : Location() { + override val path: String = "-" + override val directory = System.getProperty("user.dir") ?: "-" + } + + abstract val path: String + abstract val directory: String + + fun resolve(file: String) = Paths.get(directory, file).toAbsolutePath().toString() +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/annotation/HefbrugDSL.kt b/src/main/kotlin/me/eater/hefbrug/dsl/annotation/HefbrugDSL.kt new file mode 100644 index 0000000..8b95c3e --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/annotation/HefbrugDSL.kt @@ -0,0 +1,4 @@ +package me.eater.hefbrug.dsl.annotation + +@DslMarker +annotation class HefbrugDSL \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/AssignContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/AssignContext.kt new file mode 100644 index 0000000..43b275c --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/AssignContext.kt @@ -0,0 +1,17 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.node.Node +import me.eater.hefbrug.selector.SelectorInterface + +class AssignContext(private val node: Node) : + SelectionDefinitionContext Unit> { + + val groups: MutableSet + get() = node.groups + + override fun select(selector: SelectorInterface, block: AssignContext.() -> Unit) { + if (selector.matches(node)) { + block(this) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/CollectionContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/CollectionContext.kt new file mode 100644 index 0000000..1709dd8 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/CollectionContext.kt @@ -0,0 +1,20 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.dsl.context.extension_util.RuntimeRegister +import me.eater.hefbrug.dsl.scope.AbstractScope +import me.eater.hefbrug.dsl.scope.RootScope +import me.eater.hefbrug.dsl.scope.SelectorScope +import java.util.* + +@HefbrugDSL +class CollectionContext(override val runtimeUUID: UUID, private val parentScope: SelectorScope, private val rootScope: RootScope) : + TargetedContext() { + + override val contextUUID: UUID + get() = parentScope.contextUUID + + init { + RuntimeRegister[runtimeUUID].registerScope(this, AbstractScope(rootScope)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/ContextInterface.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/ContextInterface.kt new file mode 100644 index 0000000..851439b --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/ContextInterface.kt @@ -0,0 +1,8 @@ +package me.eater.hefbrug.dsl.context + +import java.util.* + + +interface ContextInterface { + val contextUUID: UUID +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/DefinitionContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/DefinitionContext.kt new file mode 100644 index 0000000..51d3cd3 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/DefinitionContext.kt @@ -0,0 +1,27 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.state.AbstractState +import me.eater.hefbrug.state.property.ProxyDelegate +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KMutableProperty0 + + +@HefbrugDSL +open class DefinitionContext(private val definition: DefinitionSkeleton>) : + DefinitionContextSkeleton { + protected fun proxy(reference: KMutableProperty0): ReadWriteProperty, T> = + ProxyDelegate(reference) + + protected val state: S + get() = definition.state + + override val after: MutableSet + get() = definition.after + override val before: MutableSet + get() = definition.before + override val require: MutableSet + get() = definition.require +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/DefinitionContextSkeleton.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/DefinitionContextSkeleton.kt new file mode 100644 index 0000000..b9f557d --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/DefinitionContextSkeleton.kt @@ -0,0 +1,10 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.state.AbstractState + +interface DefinitionContextSkeleton { + val require: MutableSet + val before: MutableSet + val after: MutableSet +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/Emitter.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/Emitter.kt new file mode 100644 index 0000000..9d06e44 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/Emitter.kt @@ -0,0 +1,12 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionKeyAction + +interface Emitter { + val notify: MutableSet + + operator fun MutableSet.plusAssign(definitionKey: Array) { + this.addAll(definitionKey.map { DefinitionKeyAction(it.group, it.id, "default") }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/EmitterContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/EmitterContext.kt new file mode 100644 index 0000000..d90bdc3 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/EmitterContext.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.definition.DefinitionKeyAction + +open class EmitterContext : Emitter { + override val notify: MutableSet = mutableSetOf() +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/Listener.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/Listener.kt new file mode 100644 index 0000000..fdc3e01 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/Listener.kt @@ -0,0 +1,5 @@ +package me.eater.hefbrug.dsl.context + +interface Listener { + val listen: MutableSet +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/ListenerContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/ListenerContext.kt new file mode 100644 index 0000000..96ffb2f --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/ListenerContext.kt @@ -0,0 +1,5 @@ +package me.eater.hefbrug.dsl.context + +class ListenerContext : Listener { + override val listen: MutableSet = mutableSetOf() +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/ModuleContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/ModuleContext.kt new file mode 100644 index 0000000..1411470 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/ModuleContext.kt @@ -0,0 +1,28 @@ +@file:Suppress("UNUSED") + +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.module.Module +import me.eater.hefbrug.utils.ArgumentDelegate +import java.util.* + +@HefbrugDSL +class ModuleContext( + override val runtimeUUID: UUID, + override val contextUUID: UUID, + private val module: Module, + private val arguments: Map +) : + TargetedContext() { + val id: String + get() = module.id + + fun arg(name: String, default: T? = null) = + ArgumentDelegate(arguments, name, default) + + fun arg(default: T? = null) = + ArgumentDelegate(arguments, null, default) + + +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/RootContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/RootContext.kt new file mode 100644 index 0000000..671c52d --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/RootContext.kt @@ -0,0 +1,32 @@ +package me.eater.hefbrug.dsl.context + + +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.dsl.context.extension_util.Register +import me.eater.hefbrug.selector.NodeSelector +import me.eater.hefbrug.selector.SelectorInterface +import java.util.* + + +@HefbrugDSL +open class RootContext : ContextInterface, RootContextSkeleton, + SelectionDefinitionContext Unit> { + override val contextUUID: UUID = UUID.randomUUID() + + override fun select(selector: SelectorInterface, block: suspend TargetedContext.() -> Unit) { + Register[contextUUID].selectorScope.addSelector(selector, block) + } + + override fun module(id: String, block: suspend ModuleContext.() -> Unit) { + Register[contextUUID].selectorScope.getModule(id).addBlock(block) + } + + override fun assign(block: AssignContext.() -> Unit) { + Register[contextUUID].selectorScope.addAssigner(block) + } + + @Suppress("UNUSED") + fun node(selector: Regex, block: suspend TargetedContext.() -> Unit = {}) { + Register[contextUUID].selectorScope.addSelector(NodeSelector(selector), block) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/RootContextSkeleton.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/RootContextSkeleton.kt new file mode 100644 index 0000000..3305b1a --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/RootContextSkeleton.kt @@ -0,0 +1,9 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.dsl.annotation.HefbrugDSL + +@HefbrugDSL +interface RootContextSkeleton { + fun module(id: String, block: suspend ModuleContext.() -> Unit) + fun assign(block: AssignContext.() -> Unit) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/SelectionDefinitionContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/SelectionDefinitionContext.kt new file mode 100644 index 0000000..8774de0 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/SelectionDefinitionContext.kt @@ -0,0 +1,34 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.selector.* + +@HefbrugDSL +interface SelectionDefinitionContext { + fun select(selector: SelectorInterface, block: C) + fun all() = TrueSelector() + fun node(selector: String): SelectorInterface = NodeSelector(selector) + fun group(name: String): SelectorInterface = GroupSelector(name) + fun group(vararg name: String): SelectorInterface = AllSelector(name.map { GroupSelector(it) }) + + + fun group(name: String, block: C) { + select(GroupSelector(name), block) + } + + fun group(vararg name: String, block: C) { + select(group(*name), block) + } + + fun node(selector: String, block: C) { + select(node(selector), block) + } + + fun all(block: C) { + select(all(), block) + } + + operator fun SelectorInterface.invoke(block: C) { + this@SelectionDefinitionContext.select(this, block) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/SourceContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/SourceContext.kt new file mode 100644 index 0000000..b8677d0 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/SourceContext.kt @@ -0,0 +1,18 @@ +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.dsl.Location +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.executor.Executor + +@HefbrugDSL +@Suppress("UNUSED") +open class SourceContext(private val root: RootContext, var location: Location, private val executor: Executor) : + RootContextSkeleton by root, + SelectionDefinitionContext Unit> by root, + SourceContextSkeleton { + fun include(vararg file: String) { + for (f in file) { + executor.run(location.resolve(f)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/SourceContextSkeleton.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/SourceContextSkeleton.kt new file mode 100644 index 0000000..c4fe061 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/SourceContextSkeleton.kt @@ -0,0 +1,5 @@ +package me.eater.hefbrug.dsl.context + +interface SourceContextSkeleton : RootContextSkeleton { + +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/TargetedContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/TargetedContext.kt new file mode 100644 index 0000000..51280f0 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/TargetedContext.kt @@ -0,0 +1,21 @@ +@file:Suppress("UNUSED") + +package me.eater.hefbrug.dsl.context + +import me.eater.hefbrug.definition.impl.PackageDefinition +import me.eater.hefbrug.definition.impl.ServiceDefinition +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.dsl.context.extension_util.DefinitionHelper.runBlock +import me.eater.hefbrug.dsl.context.impl.PackageContext +import me.eater.hefbrug.dsl.context.impl.ServiceContext +import java.util.* + +@HefbrugDSL +abstract class TargetedContext : ContextInterface { + abstract val runtimeUUID: UUID + suspend fun sv(vararg id: String, block: suspend ServiceContext.() -> Unit = {}) = + runBlock(this, ServiceDefinition.factory, id, block) + + suspend fun pkg(vararg id: String, block: suspend PackageContext.() -> Unit = {}) = + runBlock(this, PackageDefinition.factory, id, block) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/DefinitionHelper.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/DefinitionHelper.kt new file mode 100644 index 0000000..65d2e4c --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/DefinitionHelper.kt @@ -0,0 +1,33 @@ +package me.eater.hefbrug.dsl.context.extension_util + +import me.eater.hefbrug.definition.DefinitionFactory +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.dsl.context.TargetedContext +import me.eater.hefbrug.dsl.scope.TargetedScope +import me.eater.hefbrug.state.AbstractState + + +object DefinitionHelper { + @Suppress("UNCHECKED_CAST") + suspend fun , D : DefinitionSkeleton, F : DefinitionFactory> runBlock( + context: TargetedContext, + factory: F, + id: Array, + block: suspend C.() -> Unit + ) { + for (idx in id) { + block( + RuntimeRegister[context.runtimeUUID] + .getScope(context) + .getDefinition(idx, factory) + .getContext() + ) + } + } + + class KeyHelper(private val factory: DefinitionFactory<*, *, *>) { + operator fun get(id: String) = factory.key(id) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/FactoryRegister.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/FactoryRegister.kt new file mode 100644 index 0000000..12d0e67 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/FactoryRegister.kt @@ -0,0 +1,13 @@ +package me.eater.hefbrug.dsl.context.extension_util + +import me.eater.hefbrug.definition.DefinitionFactory + +object FactoryRegister { + private val map = mutableMapOf>() + + operator fun plusAssign(factory: DefinitionFactory<*, *, *>) { + map[factory.id] = factory + } + + operator fun get(id: String) = map[id]!! +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/Register.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/Register.kt new file mode 100644 index 0000000..e818615 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/Register.kt @@ -0,0 +1,19 @@ +package me.eater.hefbrug.dsl.context.extension_util + +import me.eater.hefbrug.dsl.scope.SelectorScope +import me.eater.hefbrug.executor.Executor +import java.util.* + +class Register(val id: UUID) { + val selectorScope: SelectorScope = SelectorScope(id) + + companion object { + private val registerMap = mutableMapOf() + + operator fun get(id: UUID) = registerMap[id]!! + + fun register(register: Register) { + registerMap[register.id] = register + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/RuntimeRegister.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/RuntimeRegister.kt new file mode 100644 index 0000000..a7914e9 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/extension_util/RuntimeRegister.kt @@ -0,0 +1,28 @@ +package me.eater.hefbrug.dsl.context.extension_util + +import me.eater.hefbrug.dsl.context.ContextInterface +import me.eater.hefbrug.dsl.scope.ScopeInterface +import java.util.* + +class RuntimeRegister { + private val scopeMap = mutableMapOf() + + @Suppress("UNCHECKED_CAST") + fun getScope(context: ContextInterface): T = + scopeMap[context]!! as T + + fun registerScope(context: ContextInterface, scope: ScopeInterface) { + scopeMap[context] = scope + } + + companion object { + private val runtimeRegisterMap = mutableMapOf() + + operator fun get(runtimeUUID: UUID) = + runtimeRegisterMap.getOrPut(runtimeUUID, { RuntimeRegister() }) + + fun remove(runtimeUUID: UUID) { + runtimeRegisterMap.remove(runtimeUUID) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/impl/PackageContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/impl/PackageContext.kt new file mode 100644 index 0000000..6a9e885 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/impl/PackageContext.kt @@ -0,0 +1,42 @@ +package me.eater.hefbrug.dsl.context.impl + +import me.eater.hefbrug.definition.impl.PackageDefinition +import me.eater.hefbrug.dsl.context.DefinitionContext +import me.eater.hefbrug.dsl.context.Emitter +import me.eater.hefbrug.dsl.context.EmitterContext +import me.eater.hefbrug.state.ExistenceStatus +import me.eater.hefbrug.state.impl.PackageState + +class PackageContext(definition: PackageDefinition) : DefinitionContext(definition), + Emitter by EmitterContext() { + val id: String + get() = state.id + + fun upgrade(upgrade: Boolean) { + state.upgrade = upgrade + } + + var ensure by proxy(state::status) + + val upgraded: Unit + get() { + state.upgrade = true + } + + val held: Unit + get() { + state.upgrade = false + } + + val installed: Unit + get() { + state.status = ExistenceStatus.Present + } + + val absent: Unit + get() { + state.status = ExistenceStatus.Absent + } + + var name: String by proxy(state::name) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/context/impl/ServiceContext.kt b/src/main/kotlin/me/eater/hefbrug/dsl/context/impl/ServiceContext.kt new file mode 100644 index 0000000..b09cc68 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/context/impl/ServiceContext.kt @@ -0,0 +1,41 @@ +package me.eater.hefbrug.dsl.context.impl + +import me.eater.hefbrug.definition.impl.ServiceDefinition +import me.eater.hefbrug.dsl.context.* +import me.eater.hefbrug.state.impl.ServiceState + +class ServiceContext(definition: ServiceDefinition) : DefinitionContext(definition), + Emitter by EmitterContext(), Listener by ListenerContext() { + + var name: String by proxy(state::name) + + val enabled: Unit + get() { + state.enabled = true + } + + val running: Unit + get() { + state.running = true + } + + val stopped: Unit + get() { + state.running = false + } + + val disabled: Unit + get() { + state.enabled = false + } + + val autostart: Unit + get() { + state.autostart = true + } + + val noAutostart: Unit + get() { + state.autostart = false + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/scope/AbstractScope.kt b/src/main/kotlin/me/eater/hefbrug/dsl/scope/AbstractScope.kt new file mode 100644 index 0000000..b6ee182 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/scope/AbstractScope.kt @@ -0,0 +1,15 @@ +package me.eater.hefbrug.dsl.scope + +import me.eater.hefbrug.definition.DefinitionFactory +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.state.AbstractState + +class AbstractScope(private val parentScope: ScopeInterface) : ScopeInterface, TargetedScope { + override fun , D : DefinitionSkeleton> getDefinition( + id: String, + factory: DefinitionFactory + ): D = + parentScope.getDefinition(id, factory) + +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/scope/RootScope.kt b/src/main/kotlin/me/eater/hefbrug/dsl/scope/RootScope.kt new file mode 100644 index 0000000..187440c --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/scope/RootScope.kt @@ -0,0 +1,23 @@ +package me.eater.hefbrug.dsl.scope + +import me.eater.hefbrug.definition.DefinitionFactory +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.state.AbstractState + +class RootScope : ScopeInterface { + private val definitionsMut = + mutableMapOf>() + + val definitions by lazy { definitionsMut } + + @Suppress("UNCHECKED_CAST") + override fun , D : DefinitionSkeleton> getDefinition( + id: String, + factory: DefinitionFactory + ): D = + definitionsMut.getOrPut(factory.key(id)) { + factory.build(id) + } as D +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/scope/ScopeInterface.kt b/src/main/kotlin/me/eater/hefbrug/dsl/scope/ScopeInterface.kt new file mode 100644 index 0000000..35576ab --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/scope/ScopeInterface.kt @@ -0,0 +1,13 @@ +package me.eater.hefbrug.dsl.scope + +import me.eater.hefbrug.definition.DefinitionFactory +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.state.AbstractState + +interface ScopeInterface { + fun , D : DefinitionSkeleton> getDefinition( + id: String, + factory: DefinitionFactory + ): D +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/scope/SelectorScope.kt b/src/main/kotlin/me/eater/hefbrug/dsl/scope/SelectorScope.kt new file mode 100644 index 0000000..0fcf23f --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/scope/SelectorScope.kt @@ -0,0 +1,56 @@ +package me.eater.hefbrug.dsl.scope + +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.context.AssignContext +import me.eater.hefbrug.dsl.context.CollectionContext +import me.eater.hefbrug.dsl.context.ContextInterface +import me.eater.hefbrug.dsl.context.TargetedContext +import me.eater.hefbrug.dsl.context.extension_util.RuntimeRegister +import me.eater.hefbrug.logging.Logging +import me.eater.hefbrug.module.Module +import me.eater.hefbrug.node.Node +import me.eater.hefbrug.selector.SelectorInterface +import java.util.* + +class SelectorScope( + override val contextUUID: UUID +) : ContextInterface, Logging { + private val selectors: MutableSet Unit>> = mutableSetOf() + private val modules: MutableMap = mutableMapOf() + private val assigners: MutableSet Unit> = mutableSetOf() + + fun getModule(id: String): Module = + modules.getOrPut(id, { Module(id, contextUUID) }) + + + fun addSelector(selector: SelectorInterface, block: suspend TargetedContext.() -> Unit) = + selectors.add(selector to block) + + fun addAssigner(block: AssignContext.() -> Unit) { + assigners.add(block) + } + + suspend fun collect(node: Node): Map> { + val assign = AssignContext(node) + assigners.forEach { it(assign) } + info("Collecting definitions for $node") + + val rootScope = RootScope() + val collection = CollectionContext(UUID.randomUUID(), this, rootScope) + + + for ((selector, block) in selectors) { + if (!selector.matches(node)) { + continue + } + + block(collection) + } + + RuntimeRegister.remove(collection.runtimeUUID) + + info("Collected ${rootScope.definitions.size} for $node") + return rootScope.definitions + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/dsl/scope/TargetedScope.kt b/src/main/kotlin/me/eater/hefbrug/dsl/scope/TargetedScope.kt new file mode 100644 index 0000000..db81965 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/dsl/scope/TargetedScope.kt @@ -0,0 +1,3 @@ +package me.eater.hefbrug.dsl.scope + +interface TargetedScope : ScopeInterface \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/enforcer/AbstractEnforcer.kt b/src/main/kotlin/me/eater/hefbrug/enforcer/AbstractEnforcer.kt new file mode 100644 index 0000000..8eb8994 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/enforcer/AbstractEnforcer.kt @@ -0,0 +1,13 @@ +package me.eater.hefbrug.enforcer + +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.AbstractState +import kotlin.reflect.KProperty + +abstract class AbstractEnforcer(val context: ExecutionContext) { + operator fun Set.contains(property: KProperty<*>): Boolean { + return property.name in this + } + + abstract suspend fun enforce(currentState: S, desiredState: S, changeSet: Set) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/enforcer/impl/PackageEnforcer.kt b/src/main/kotlin/me/eater/hefbrug/enforcer/impl/PackageEnforcer.kt new file mode 100644 index 0000000..b5637a7 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/enforcer/impl/PackageEnforcer.kt @@ -0,0 +1,25 @@ +package me.eater.hefbrug.enforcer.impl + +import me.eater.hefbrug.enforcer.AbstractEnforcer +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.ExistenceStatus +import me.eater.hefbrug.state.impl.PackageState + +class PackageEnforcer(context: ExecutionContext) : AbstractEnforcer(context) { + override suspend fun enforce(currentState: PackageState, desiredState: PackageState, changeSet: Set) { + val pm = context.getPackageManager() + pm.sync() + + if (PackageState::status in changeSet) { + when (desiredState.status) { + ExistenceStatus.Absent -> pm.remove(desiredState.name) + ExistenceStatus.Present -> pm.install(desiredState.name) + else -> Unit + } + } + + if (currentState.status == ExistenceStatus.Present && (desiredState.upgrade && !(PackageState::status in changeSet || desiredState.status == ExistenceStatus.Absent))) { + pm.upgrade(desiredState.name) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/enforcer/impl/ServiceEnforcer.kt b/src/main/kotlin/me/eater/hefbrug/enforcer/impl/ServiceEnforcer.kt new file mode 100644 index 0000000..eba2daa --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/enforcer/impl/ServiceEnforcer.kt @@ -0,0 +1,18 @@ +package me.eater.hefbrug.enforcer.impl + +import me.eater.hefbrug.enforcer.AbstractEnforcer +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.state.impl.ServiceState + +class ServiceEnforcer(context: ExecutionContext) : AbstractEnforcer(context) { + override suspend fun enforce(currentState: ServiceState, desiredState: ServiceState, changeSet: Set) { + val sm = context.getServiceManager() + + sm.setState( + desiredState.name, + enabled = desiredState.enabled, + running = desiredState.running, + autostart = desiredState.autostart + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/ExecutionContext.kt b/src/main/kotlin/me/eater/hefbrug/executor/ExecutionContext.kt new file mode 100644 index 0000000..517dbd9 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/ExecutionContext.kt @@ -0,0 +1,44 @@ +package me.eater.hefbrug.executor + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import me.eater.hefbrug.access.AccessSkeleton +import me.eater.hefbrug.access.NoopAccess +import me.eater.hefbrug.access.Wrapper +import me.eater.hefbrug.node.Node +import me.eater.hefbrug.platform_utils.`package`.PackageManager +import me.eater.hefbrug.platform_utils.service.ServiceManager + +class ExecutionContext(access: AccessSkeleton, val node: Node, val noop: Boolean = true) { + private var packageManager: PackageManager? = null + private var packageManagerLock = Mutex() + private var serviceManager: ServiceManager? = null + private var serviceManagerLock = Mutex() + + val roAccess: AccessSkeleton = Wrapper(access, false) + val rwAccess: AccessSkeleton = Wrapper(if (noop) NoopAccess() else access) + + suspend fun getPackageManager(): PackageManager { + if (packageManager == null) { + packageManagerLock.withLock { + if (packageManager == null) { + packageManager = PackageManager.getManager(this@ExecutionContext) + } + } + } + + return packageManager!! + } + + suspend fun getServiceManager(): ServiceManager { + if (serviceManager == null) { + serviceManagerLock.withLock { + if (serviceManager == null) { + serviceManager = ServiceManager.getManager(this) + } + } + } + + return serviceManager!! + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/ExecutionInstance.kt b/src/main/kotlin/me/eater/hefbrug/executor/ExecutionInstance.kt new file mode 100644 index 0000000..1949839 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/ExecutionInstance.kt @@ -0,0 +1,58 @@ +package me.eater.hefbrug.executor + +import me.eater.hefbrug.access.ExecutionOutput +import me.eater.hefbrug.access.Local +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionSkeleton +import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton +import me.eater.hefbrug.dsl.scope.SelectorScope +import me.eater.hefbrug.logging.Logging +import me.eater.hefbrug.logging.message.Messages +import me.eater.hefbrug.node.Node +import me.eater.hefbrug.state.AbstractState +import me.eater.hefbrug.utils.parallel +import org.apache.logging.log4j.MarkerManager +import org.jetbrains.kotlin.utils.mapToIndex + +class ExecutionInstance(private val node: Node, private val definitions: Map>) : + Logging { + + suspend fun apply(noop: Boolean = true) { + val layers = GraphBuilder.makeGraph(definitions) + debug("Graph created ${layers.size} layers of definitions to apply") + val context = ExecutionContext(Local(), node, noop) + val collector = StateCollector(context) + val enforcer = StateEnforcer(context) + for ((layer, i) in layers.mapToIndex()) { + debug("Start applying layer #$i for @|green $node|@") + parallel(layer) { item -> + debug(Messages.applying(item, node)) + val def = definitions[item] ?: run { + error("Missing definition for @|blue $item|@") + return@parallel + } + + try { + trace(Messages.collectingCurrentState(item, node)) + @Suppress("UNCHECKED_CAST") val state = + collector.collect(def as DefinitionSkeleton>) + trace(Messages.startEnforcing(item, node)) + enforcer.enforce(def, state) + } catch (e: ExecutionOutput.ExecutionException) { + error(Messages.failedDefinition(def.key, node, e.output)) + } catch (t: Throwable) { + error( + MarkerManager.getMarker("DefinitionError"), + "Failed collecting and enforcing state for @|blue $item|@ for @|green $node|@", + t + ) + } + } + } + } + + + companion object { + suspend fun forNode(node: Node, scope: SelectorScope) = ExecutionInstance(node, scope.collect(node)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/Executor.kt b/src/main/kotlin/me/eater/hefbrug/executor/Executor.kt new file mode 100644 index 0000000..84fc698 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/Executor.kt @@ -0,0 +1,64 @@ +package me.eater.hefbrug.executor + +import me.eater.hefbrug.dsl.Location +import me.eater.hefbrug.dsl.context.RootContext +import me.eater.hefbrug.dsl.context.SourceContext +import me.eater.hefbrug.dsl.context.extension_util.Register +import me.eater.hefbrug.dsl.scope.SelectorScope +import me.eater.hefbrug.logging.Logging +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.MarkerManager +import java.io.File +import java.nio.file.Paths +import kotlin.script.experimental.api.ScriptDiagnostic +import kotlin.script.experimental.api.constructorArgs +import kotlin.script.experimental.api.implicitReceivers +import kotlin.script.experimental.host.toScriptSource +import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost +import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate +import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate + +class Executor(val context: RootContext = RootContext()) : Logging { + private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate() + + fun run(name: String) { + val location = Location.File(Paths.get(name).toRealPath().toString()) + val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate { + constructorArgs(context, location) + implicitReceivers(SourceContext(context, location, this@Executor)) + } + + val diags = + BasicJvmScriptingHost().eval( + File(location.path).toScriptSource(), + compilationConfiguration, + evaluationConfiguration + ) + + for (d in diags.reports) { + + log( + when (d.severity) { + ScriptDiagnostic.Severity.DEBUG -> Level.forName("SCRIPT", 700) + ScriptDiagnostic.Severity.INFO -> Level.DEBUG + ScriptDiagnostic.Severity.WARNING -> Level.INFO + ScriptDiagnostic.Severity.ERROR -> Level.ERROR + ScriptDiagnostic.Severity.FATAL -> Level.FATAL + }, + MarkerManager.getMarker("script"), + "[@|blue ${location.path}|@] ${d.message} ${d.location ?: ""}" + ) + + if (d.exception != null) { + d.exception!!.printStackTrace() + } + } + } + + fun getScope(): SelectorScope = + Register[context.contextUUID].selectorScope + + init { + Register.register(Register(context.contextUUID)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/GraphBuilder.kt b/src/main/kotlin/me/eater/hefbrug/executor/GraphBuilder.kt new file mode 100644 index 0000000..ac1a21f --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/GraphBuilder.kt @@ -0,0 +1,42 @@ +package me.eater.hefbrug.executor + +import me.eater.hefbrug.definition.AbstractDefinition +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.definition.DefinitionSkeleton + +object GraphBuilder { + fun makeGraph(input: Map>): List> { + val dependencies = mutableMapOf>() + + for ((key, definition) in input) { + definition.before.forEach { + dependencies.getOrPut(it, ::mutableSetOf) + .add(key) + } + + definition.after.forEach { + dependencies.getOrPut(key, ::mutableSetOf) + .add(it) + } + } + + val todo = input.keys.toMutableSet() + val fulfilled = mutableSetOf() + val graphLayers = mutableListOf>() + + while (todo.isNotEmpty()) { + val nextLayer = mutableSetOf() + for (item in todo) { + if (dependencies[item]?.all { it in fulfilled } != false) { + nextLayer.add(item) + } + } + + fulfilled += nextLayer + todo -= nextLayer + graphLayers.add(nextLayer) + } + + return graphLayers + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/HefbrugCompilationConfiguration.kt b/src/main/kotlin/me/eater/hefbrug/executor/HefbrugCompilationConfiguration.kt new file mode 100644 index 0000000..deff554 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/HefbrugCompilationConfiguration.kt @@ -0,0 +1,46 @@ +package me.eater.hefbrug.executor + +import me.eater.hefbrug.dsl.context.SourceContext +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.dependenciesFromCurrentContext +import kotlin.script.experimental.jvm.jvm + +class HefbrugCompilationConfiguration : ScriptCompilationConfiguration({ + defaultImports( + listOf( + // Default kotlin imports + "kotlin.*", + "kotlin.annotations.*", + "kotlin.collections.*", + "kotlin.comparisons.*", + "kotlin.io.*", + "kotlin.ranges.*", + "kotlin.sequences.*", + "kotlin.text.*", + "kotlin.jvm.*", + "java.lang.*", + + + // Own imports + "me.eater.hefbrug.dsl.context.utils.*", + "me.eater.hefbrug.state.ExistenceStatus.*", + "me.eater.hefbrug.dsl.annotation.include" + ) + ) + displayName("Hefbrug definition script") + baseClass(HefbrugScript::class) + implicitReceivers(SourceContext::class) + + + jvm { + dependenciesFromCurrentContext(wholeClasspath = true) + } + + ide { + acceptedLocations( + ScriptAcceptedLocation.Everywhere, + ScriptAcceptedLocation.Project, + ScriptAcceptedLocation.Sources + ) + } +}) \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/HefbrugEvaluationConfiguration.kt b/src/main/kotlin/me/eater/hefbrug/executor/HefbrugEvaluationConfiguration.kt new file mode 100644 index 0000000..a77ed81 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/HefbrugEvaluationConfiguration.kt @@ -0,0 +1,9 @@ +package me.eater.hefbrug.executor + +import kotlin.script.experimental.api.ScriptEvaluationConfiguration +import kotlin.script.experimental.api.constructorArgs +import kotlin.script.experimental.api.enableScriptsInstancesSharing + +class HefbrugEvaluationConfiguration : ScriptEvaluationConfiguration({ + enableScriptsInstancesSharing() +}) \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/HefbrugScript.kt b/src/main/kotlin/me/eater/hefbrug/executor/HefbrugScript.kt new file mode 100644 index 0000000..1047e48 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/HefbrugScript.kt @@ -0,0 +1,21 @@ +@file:Suppress("UNUSED") + +package me.eater.hefbrug.executor + +import me.eater.hefbrug.definition.impl.PackageDefinition +import me.eater.hefbrug.definition.impl.ServiceDefinition +import me.eater.hefbrug.dsl.Location +import me.eater.hefbrug.dsl.context.RootContext +import kotlin.script.experimental.annotations.KotlinScript + +@Suppress("UNUSED_PARAMETER") +@KotlinScript( + displayName = "Hefbrug definition script", + fileExtension = "hb.kts", + compilationConfiguration = HefbrugCompilationConfiguration::class, + evaluationConfiguration = HefbrugEvaluationConfiguration::class +) +abstract class HefbrugScript(root: RootContext, location: Location) { + val sv = ServiceDefinition.KeyHelper + val pkg = PackageDefinition.KeyHelper +} diff --git a/src/main/kotlin/me/eater/hefbrug/executor/StateCollector.kt b/src/main/kotlin/me/eater/hefbrug/executor/StateCollector.kt new file mode 100644 index 0000000..3e5a538 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/StateCollector.kt @@ -0,0 +1,18 @@ +package me.eater.hefbrug.executor + +import me.eater.hefbrug.collector.AbstractCollector +import me.eater.hefbrug.definition.DefinitionWildcard +import me.eater.hefbrug.dsl.context.extension_util.FactoryRegister +import me.eater.hefbrug.state.AbstractState + +class StateCollector(val context: ExecutionContext) { + private val collectors = mutableMapOf>() + + @Suppress("UNCHECKED_CAST") + suspend fun collect(definition: DefinitionWildcard): S { + val collector = collectors.getOrPut( + definition.key.group, + { FactoryRegister[definition.key.group].createCollector(context) }) as AbstractCollector> + return collector.collect(definition) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/executor/StateEnforcer.kt b/src/main/kotlin/me/eater/hefbrug/executor/StateEnforcer.kt new file mode 100644 index 0000000..c01eef6 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/executor/StateEnforcer.kt @@ -0,0 +1,42 @@ +package me.eater.hefbrug.executor + +import me.eater.hefbrug.definition.DefinitionWildcard +import me.eater.hefbrug.dsl.context.extension_util.FactoryRegister +import me.eater.hefbrug.enforcer.AbstractEnforcer +import me.eater.hefbrug.logging.Logging +import me.eater.hefbrug.logging.message.Messages.foundDifference +import me.eater.hefbrug.logging.message.Messages.noDifferences +import me.eater.hefbrug.state.AbstractState + +class StateEnforcer(private val context: ExecutionContext) : Logging { + private val enforcers = mutableMapOf>() + + @Suppress("UNCHECKED_CAST") + suspend fun > enforce( + definition: D, + currentState: S + ) { + val enforcer = enforcers.getOrPut( + definition.key.group, + { FactoryRegister[definition.key.group].createEnforcer(context) }) as AbstractEnforcer + + val diff = definition.state.diff(currentState) + + if (diff.isNotEmpty()) + info( + foundDifference( + definition.key, + context.node, + mutableMapOf>().apply { + for (k in diff) { + put(k, currentState[k].get() to definition.state[k].get()) + } + } + ) + ) + else + trace(noDifferences(definition.key, context.node)) + + return enforcer.enforce(currentState, definition.state, diff) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/logging/LogFormat.kt b/src/main/kotlin/me/eater/hefbrug/logging/LogFormat.kt new file mode 100644 index 0000000..1dd395d --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/logging/LogFormat.kt @@ -0,0 +1,5 @@ +package me.eater.hefbrug.logging + +interface LogFormat { + fun logFormat(): String +} diff --git a/src/main/kotlin/me/eater/hefbrug/logging/LoggerConfig.kt b/src/main/kotlin/me/eater/hefbrug/logging/LoggerConfig.kt new file mode 100644 index 0000000..ef5acb0 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/logging/LoggerConfig.kt @@ -0,0 +1,71 @@ +package me.eater.hefbrug.logging + +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.core.LoggerContext +import org.apache.logging.log4j.core.appender.ConsoleAppender +import org.apache.logging.log4j.core.config.Configuration +import org.apache.logging.log4j.core.config.ConfigurationFactory +import org.apache.logging.log4j.core.config.ConfigurationSource +import org.apache.logging.log4j.core.config.Order +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory +import org.apache.logging.log4j.core.config.plugins.Plugin +import java.net.URI + +@Plugin(name = "LoggerConfig", category = ConfigurationFactory.CATEGORY) +@Order(50) +class LoggerConfig : ConfigurationFactory() { + fun getConfiguration(): Configuration { + val builder = ConfigurationBuilderFactory.newConfigurationBuilder() + builder.add( + builder.newCustomLevel("SCRIPT", 700) + ) + builder.setConfigurationName("HefbrugDefault") + builder.setStatusLevel(Level.INFO) + builder.add( + builder.newAppender("Stdout", "CONSOLE") + .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT) + .add( + builder.newLayout("PatternLayout") + .addAttribute( + "pattern", + "%blue{[%d{HH:mm:ss}]}%highlight{[%-5level]}{SCRIPT=white} %msg{ansi}%n" + ) + ) + ) + + builder.add( + builder.newRootLogger( + when (System.getenv("HEFBRUG_LOG")?.toLowerCase()) { + "all" -> Level.ALL + "trace" -> Level.TRACE + "debug" -> Level.DEBUG + "warn" -> Level.WARN + "error" -> Level.ERROR + "fatal" -> Level.FATAL + "script" -> Level.forName("SCRIPT", 700) + "off" -> Level.OFF + else -> Level.INFO + } + ) + .add(builder.newAppenderRef("Stdout")) + ) + + return builder.build() + } + + override fun getConfiguration(loggerContext: LoggerContext, source: ConfigurationSource): Configuration = + getConfiguration() + + override fun getConfiguration(loggerContext: LoggerContext, name: String, configLocation: URI?): Configuration = + getConfiguration() + + override fun getConfiguration( + loggerContext: LoggerContext, + name: String, + configLocation: URI?, + loader: ClassLoader? + ) = getConfiguration() + + override fun getSupportedTypes(): Array = arrayOf("*") + +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/logging/Logging.kt b/src/main/kotlin/me/eater/hefbrug/logging/Logging.kt new file mode 100644 index 0000000..7dba6e4 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/logging/Logging.kt @@ -0,0 +1,106 @@ +package me.eater.hefbrug.logging + +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.Marker +import org.apache.logging.log4j.kotlin.Logging +import org.apache.logging.log4j.message.EntryMessage +import org.apache.logging.log4j.message.Message + +@Suppress("UNUSED") +interface Logging : Logging { + fun log(level: Level, marker: Marker, msg: Message) = logger.log(level, marker, msg) + fun log(level: Level, marker: Marker, msg: Message, t: Throwable?) = logger.log(level, marker, msg, t) + fun log(level: Level, marker: Marker, msg: CharSequence) = logger.log(level, marker, msg) + fun log(level: Level, marker: Marker, msg: CharSequence, t: Throwable?) = logger.log(level, marker, msg, t) + fun log(level: Level, marker: Marker, msg: Any) = logger.log(level, marker, msg) + fun log(level: Level, marker: Marker, msg: Any, t: Throwable?) = logger.log(level, marker, msg, t) + fun log(level: Level, msg: Message) = logger.log(level, msg) + fun log(level: Level, msg: Message, t: Throwable?) = logger.log(level, msg, t) + fun log(level: Level, msg: CharSequence) = logger.log(level, msg) + fun log(level: Level, msg: CharSequence, t: Throwable?) = logger.log(level, msg, t) + fun log(level: Level, msg: Any) = logger.log(level, msg) + fun log(level: Level, msg: Any, t: Throwable?) = logger.log(level, msg, t) + fun log(level: Level, supplier: () -> Any?) = logger.log(level, supplier) + fun log(level: Level, t: Throwable, supplier: () -> Any?) = logger.log(level, t, supplier) + fun log(level: Level, marker: Marker, supplier: () -> Any?) = logger.log(level, marker, supplier) + fun log(level: Level, marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.log(level, marker, t, supplier) + fun trace(marker: Marker, msg: Message) = logger.trace(marker, msg) + fun trace(marker: Marker, msg: Message, t: Throwable?) = logger.trace(marker, msg, t) + fun trace(marker: Marker, msg: CharSequence) = logger.trace(marker, msg) + fun trace(marker: Marker, msg: CharSequence, t: Throwable?) = logger.trace(marker, msg, t) + fun trace(marker: Marker, msg: Any) = logger.trace(marker, msg) + fun trace(marker: Marker, msg: Any, t: Throwable?) = logger.trace(marker, msg, t) + fun trace(msg: Message) = logger.trace(msg) + fun trace(msg: CharSequence) = logger.trace(msg) + fun trace(msg: Any) = logger.trace(msg) + fun trace(supplier: () -> Any?) = logger.trace(supplier) + fun trace(marker: Marker, supplier: () -> Any?) = logger.trace(marker, supplier) + fun trace(marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.trace(marker, t, supplier) + fun traceEntry(msg: CharSequence) = logger.traceEntry(msg) + fun traceEntry(supplier: () -> CharSequence) = logger.traceEntry(supplier) + fun traceEntry(vararg paramSuppliers: () -> Any?) = logger.traceEntry(*paramSuppliers) + fun traceEntry(vararg params: Any?) = logger.traceEntry(*params) + fun traceEntry(message: Message) = logger.traceEntry(message) + fun runInTrace(block: () -> R): R = logger.runInTrace(block) + fun runInTrace(entryMessage: EntryMessage, block: () -> R): R = logger.runInTrace(entryMessage, block) + fun debug(marker: Marker, msg: Message) = logger.debug(marker, msg) + fun debug(marker: Marker, msg: Message, t: Throwable?) = logger.debug(marker, msg, t) + fun debug(marker: Marker, msg: CharSequence) = logger.debug(marker, msg) + fun debug(marker: Marker, msg: CharSequence, t: Throwable?) = logger.debug(marker, msg, t) + fun debug(marker: Marker, msg: Any) = logger.debug(marker, msg) + fun debug(marker: Marker, msg: Any, t: Throwable?) = logger.debug(marker, msg, t) + fun debug(msg: Message) = logger.debug(msg) + fun debug(msg: CharSequence) = logger.debug(msg) + fun debug(msg: Any) = logger.debug(msg) + fun debug(supplier: () -> Any?) = logger.debug(supplier) + fun debug(marker: Marker, supplier: () -> Any?) = logger.debug(marker, supplier) + fun debug(marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.debug(marker, t, supplier) + fun info(marker: Marker, msg: Message) = logger.info(marker, msg) + fun info(marker: Marker, msg: Message, t: Throwable?) = logger.info(marker, msg, t) + fun info(marker: Marker, msg: CharSequence) = logger.info(marker, msg) + fun info(marker: Marker, msg: CharSequence, t: Throwable?) = logger.info(marker, msg, t) + fun info(marker: Marker, msg: Any) = logger.info(marker, msg) + fun info(marker: Marker, msg: Any, t: Throwable?) = logger.info(marker, msg, t) + fun info(msg: Message) = logger.info(msg) + fun info(msg: CharSequence) = logger.info(msg) + fun info(msg: Any) = logger.info(msg) + fun info(supplier: () -> Any?) = logger.info(supplier) + fun info(marker: Marker, supplier: () -> Any?) = logger.info(marker, supplier) + fun info(marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.info(marker, t, supplier) + fun warn(marker: Marker, msg: Message) = logger.warn(marker, msg) + fun warn(marker: Marker, msg: Message, t: Throwable?) = logger.warn(marker, msg, t) + fun warn(marker: Marker, msg: CharSequence) = logger.warn(marker, msg) + fun warn(marker: Marker, msg: CharSequence, t: Throwable?) = logger.warn(marker, msg, t) + fun warn(marker: Marker, msg: Any) = logger.warn(marker, msg) + fun warn(marker: Marker, msg: Any, t: Throwable?) = logger.warn(marker, msg, t) + fun warn(msg: Message) = logger.warn(msg) + fun warn(msg: CharSequence) = logger.warn(msg) + fun warn(msg: Any) = logger.warn(msg) + fun warn(supplier: () -> Any?) = logger.warn(supplier) + fun warn(marker: Marker, supplier: () -> Any?) = logger.warn(marker, supplier) + fun warn(marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.warn(marker, t, supplier) + fun error(marker: Marker, msg: Message) = logger.error(marker, msg) + fun error(marker: Marker, msg: Message, t: Throwable?) = logger.error(marker, msg, t) + fun error(marker: Marker, msg: CharSequence) = logger.error(marker, msg) + fun error(marker: Marker, msg: CharSequence, t: Throwable?) = logger.error(marker, msg, t) + fun error(marker: Marker, msg: Any) = logger.error(marker, msg) + fun error(marker: Marker, msg: Any, t: Throwable?) = logger.error(marker, msg, t) + fun error(msg: Message) = logger.error(msg) + fun error(msg: CharSequence) = logger.error(msg) + fun error(msg: Any) = logger.error(msg) + fun error(supplier: () -> Any?) = logger.error(supplier) + fun error(marker: Marker, supplier: () -> Any?) = logger.error(marker, supplier) + fun error(marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.error(marker, t, supplier) + fun fatal(marker: Marker, msg: Message) = logger.fatal(marker, msg) + fun fatal(marker: Marker, msg: Message, t: Throwable?) = logger.fatal(marker, msg, t) + fun fatal(marker: Marker, msg: CharSequence) = logger.fatal(marker, msg) + fun fatal(marker: Marker, msg: CharSequence, t: Throwable?) = logger.fatal(marker, msg, t) + fun fatal(marker: Marker, msg: Any) = logger.fatal(marker, msg) + fun fatal(marker: Marker, msg: Any, t: Throwable?) = logger.fatal(marker, msg, t) + fun fatal(msg: Message) = logger.fatal(msg) + fun fatal(msg: CharSequence) = logger.fatal(msg) + fun fatal(msg: Any) = logger.fatal(msg) + fun fatal(supplier: () -> Any?) = logger.fatal(supplier) + fun fatal(marker: Marker, supplier: () -> Any?) = logger.fatal(marker, supplier) + fun fatal(marker: Marker, t: Throwable?, supplier: () -> Any?) = logger.fatal(marker, t, supplier) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/logging/message/HefbrugMessage.kt b/src/main/kotlin/me/eater/hefbrug/logging/message/HefbrugMessage.kt new file mode 100644 index 0000000..276cd8e --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/logging/message/HefbrugMessage.kt @@ -0,0 +1,27 @@ +package me.eater.hefbrug.logging.message + +import me.eater.hefbrug.utils.handlebars +import org.apache.logging.log4j.message.Message +import org.apache.logging.log4j.util.StringBuilderFormattable + +open class HefbrugMessage(private val message: String, private val items: Map) : Message, + StringBuilderFormattable { + constructor(message: String, vararg items: Pair) : this(message, items.toMap()) + + override fun getThrowable(): Throwable? = null + override fun getParameters(): Array { + return items.entries.toTypedArray() + } + + override fun getFormattedMessage(): String { + return format.handlebars(items) + } + + override fun getFormat(): String { + return message + } + + override fun formatTo(buffer: StringBuilder) { + buffer.append(formattedMessage) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/logging/message/messages.kt b/src/main/kotlin/me/eater/hefbrug/logging/message/messages.kt new file mode 100644 index 0000000..c9f5f34 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/logging/message/messages.kt @@ -0,0 +1,60 @@ +package me.eater.hefbrug.logging.message + +import me.eater.hefbrug.access.ExecutionOutput +import me.eater.hefbrug.definition.DefinitionKey +import me.eater.hefbrug.logging.LogFormat +import me.eater.hefbrug.node.Node +import me.eater.hefbrug.utils.escape +import me.eater.hefbrug.utils.handlebars + +object Messages { + + fun collectingCurrentState(key: DefinitionKey, node: Node) = + HefbrugMessage("Collecting current state for {{key}} for {{node}}", "key" to key, "node" to node) + + + fun applying(key: DefinitionKey, node: Node) = + HefbrugMessage("Applying {{key}} for {{node}}", "key" to key, "node" to node) + + fun startEnforcing(key: DefinitionKey, node: Node) = + HefbrugMessage("Enforcing desired state for {{key}} for {{node}}", "key" to key, "node" to node) + + fun foundDifference(key: DefinitionKey, node: Node, differences: Map>) = + HefbrugMessage( + "enforcing {{key}} on {{node}} to desired state {{diff}}", + "key" to key, + "node" to node, + "diff" to Diff(differences) + ) + + fun noDifferences(key: DefinitionKey, node: Node) = + HefbrugMessage( + "{{key}} on {{node}} is in desired state", + "key" to key, + "node" to node + ) + + fun failedDefinition( + key: DefinitionKey, + node: Node, + executionOutput: ExecutionOutput + ) = + HefbrugMessage( + "Failed enforcing state {{key}} for {{node}} with command [@|yellow {{cmd}}|@] with exit code @|red {{exitcode}}|@", + "key" to key, + "node" to node, + "cmd" to executionOutput.command.command.joinToString(" ") { it.escape() }, + "exitcode" to executionOutput.exitCode, + "execution" to executionOutput + ) + + + class Diff(val body: Map>) : LogFormat { + override fun logFormat(): String { + return "[${body.map { (k, v) -> + val (old, new) = v + "@|yellow $k|@: @|red {{old}}|@ => @|green {{new}}|@".handlebars("old" to old, "new" to new) + }.joinToString(", ")}]" + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/module/Module.kt b/src/main/kotlin/me/eater/hefbrug/module/Module.kt new file mode 100644 index 0000000..a7d4040 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/module/Module.kt @@ -0,0 +1,10 @@ +package me.eater.hefbrug.module + +import me.eater.hefbrug.dsl.context.ModuleContext +import java.util.* + +class Module(val id: String, private val contextUUID: UUID) { + private val blocks: MutableSet Unit> = mutableSetOf() + fun getContext(runtimeUUID: UUID, arguments: Map): ModuleContext = ModuleContext(runtimeUUID, contextUUID, this, arguments) + fun addBlock(block: suspend ModuleContext.() -> Unit) = blocks.add(block) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/node/Node.kt b/src/main/kotlin/me/eater/hefbrug/node/Node.kt new file mode 100644 index 0000000..dfb21ca --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/node/Node.kt @@ -0,0 +1,11 @@ +package me.eater.hefbrug.node + +import me.eater.hefbrug.logging.LogFormat + +data class Node(val hostname: String) : LogFormat { + val groups = mutableSetOf() + + + override fun toString() = "Node[$hostname,groups={${groups.joinToString(",")}}]" + override fun logFormat() = "@|green $this|@" +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/PlatformUtil.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/PlatformUtil.kt new file mode 100644 index 0000000..6732acc --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/PlatformUtil.kt @@ -0,0 +1,31 @@ +package me.eater.hefbrug.platform_utils + +import me.eater.hefbrug.access.AccessSkeleton +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.logging.Logging + +interface PlatformUtil { + suspend fun isSupported(access: AccessSkeleton): Boolean + suspend fun getManager(context: ExecutionContext): M + + abstract class Provider(val name: String, vararg manager: PlatformUtil) : Logging { + private val managers: MutableSet> = mutableSetOf(*manager) + + suspend fun getManager(context: ExecutionContext): M { + val manager = managers.find { + debug("Trying $name ${it.javaClass.name}") + it.isSupported(context.roAccess) + }?.getManager(context) + ?: throw RuntimeException("Couldn't find suitable $name for this system") + + debug("Selected $name ${manager.javaClass.name}") + + return manager + } + + + fun register(packageManager: PlatformUtil) { + managers.add(packageManager) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/package/PackageManager.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/package/PackageManager.kt new file mode 100644 index 0000000..0455cdb --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/package/PackageManager.kt @@ -0,0 +1,34 @@ +package me.eater.hefbrug.platform_utils.`package` + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.platform_utils.PlatformUtil + +abstract class PackageManager(protected val context: ExecutionContext) { + protected val ro = context.roAccess + protected val rw = context.rwAccess + private var hasSynced = false + private var syncLock = Mutex() + + abstract suspend fun isInstalled(name: String): Boolean + abstract suspend fun install(name: String) + abstract suspend fun remove(name: String) + protected abstract suspend fun sync() + + suspend fun sync(force: Boolean = false) { + syncLock.withLock { + if (!hasSynced || force) { + sync() + hasSynced = true + } + } + } + + abstract suspend fun upgrade(name: String) + + companion object : PlatformUtil.Provider( + "package manager", + XbpsManager.Util + ) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/package/XbpsManager.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/package/XbpsManager.kt new file mode 100644 index 0000000..1537b11 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/package/XbpsManager.kt @@ -0,0 +1,51 @@ +package me.eater.hefbrug.platform_utils.`package` + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import me.eater.hefbrug.access.AccessSkeleton +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.platform_utils.PlatformUtil + +class XbpsManager(context: ExecutionContext) : PackageManager(context) { + private val executionLock = Mutex() + + override suspend fun isInstalled(name: String): Boolean { + + return executionLock.withLock { + ro.execute("xbps-query", name).exitCode == 0 + } + } + + override suspend fun install(name: String) { + executionLock.withLock { + rw.execute("xbps-install", "-y", name).orThrow() + } + } + + override suspend fun remove(name: String) { + executionLock.withLock { + rw.execute("xbps-remove", "-y", name).orThrow() + } + } + + override suspend fun sync() { + executionLock.withLock { + rw.execute("xbps-install", "-S").orThrow() + } + } + + override suspend fun upgrade(name: String) { + executionLock.withLock { + rw.execute("xbps-install", "-uy", name).orThrow() + } + } + + object Util : PlatformUtil { + override suspend fun isSupported(access: AccessSkeleton): Boolean { + return access.execute("which", "xbps-install").exitCode == 0 && + access.execute("which", "xbps-remove").exitCode == 0 + } + + override suspend fun getManager(context: ExecutionContext) = XbpsManager(context) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/service/RunitManager.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/service/RunitManager.kt new file mode 100644 index 0000000..e16000c --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/service/RunitManager.kt @@ -0,0 +1,63 @@ +package me.eater.hefbrug.platform_utils.service + +import me.eater.hefbrug.access.AccessSkeleton +import me.eater.hefbrug.access.FileType +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.platform_utils.PlatformUtil + +class RunitManager(context: ExecutionContext) : ServiceManager(context) { + + override suspend fun isRunning(name: String): Boolean { + return ro.execute("sv", "status", name).stdout.startsWith("run:") + } + + override suspend fun isEnabled(name: String): Boolean { + return ro.exists("/var/service/$name", FileType.Directory) + } + + override suspend fun hasAutoStart(name: String): Boolean { + return !ro.exists("/etc/sv/$name/stop") + } + + override suspend fun setState(name: String, enabled: Boolean, running: Boolean, autostart: Boolean) { + val serviceConf = "/etc/sv/$name" + val stopFile = "$serviceConf/stop" + val activeService = "/var/service/$name" + + if (!autostart) { + rw.execute("touch", stopFile).orThrow() + } else if (!hasAutoStart(name)) { + rw.execute("rm", stopFile).orThrow() + } + + val isEnabled = isEnabled(name) + + if (isEnabled && !enabled) { + rw.execute("rm", activeService).orThrow() + } + + if (!isEnabled && (enabled || running)) { + rw.execute("ln", "-s", serviceConf, "/var/service").orThrow() + } + + val isRunning = isRunning(name) + + if (isRunning && !running) { + rw.execute("sv", "stop", name).orThrow() + } + + if (!isRunning && running) { + rw.execute("sv", "start", name).orThrow() + } + } + + object Util : PlatformUtil { + override suspend fun isSupported(access: AccessSkeleton): Boolean { + return access.exists("/var/service", FileType.Directory) && + access.exists("/etc/sv", FileType.Directory) && + access.execute("which", "sv").exitCode == 0 + } + + override suspend fun getManager(context: ExecutionContext) = RunitManager(context) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/service/ServiceManager.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/service/ServiceManager.kt new file mode 100644 index 0000000..b138cee --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/service/ServiceManager.kt @@ -0,0 +1,24 @@ +package me.eater.hefbrug.platform_utils.service + +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.platform_utils.PlatformUtil + +abstract class ServiceManager(protected val context: ExecutionContext) { + protected val ro = context.roAccess + protected val rw = context.rwAccess + + abstract suspend fun isRunning(name: String): Boolean + abstract suspend fun isEnabled(name: String): Boolean + abstract suspend fun hasAutoStart(name: String): Boolean + abstract suspend fun setState( + name: String, + enabled: Boolean, + running: Boolean, + autostart: Boolean = enabled && running + ) + + companion object : PlatformUtil.Provider( + "service manager", + RunitManager.Util + ) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/user/UserManager.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/user/UserManager.kt new file mode 100644 index 0000000..4e0a615 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/user/UserManager.kt @@ -0,0 +1,21 @@ +package me.eater.hefbrug.platform_utils.user + +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.platform_utils.PlatformUtil + +abstract class UserManager(val context: ExecutionContext) { + protected val ro = context.roAccess + protected val rw = context.rwAccess + + + companion object { + private val managers: Set> = setOf( + UsermodManager.Util + ) + + suspend fun getManager(context: ExecutionContext): UserManager { + return managers.find { it.isSupported(context.roAccess) }?.getManager(context) + ?: error("Can't find appropriate User Manager for your system") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/platform_utils/user/UsermodManager.kt b/src/main/kotlin/me/eater/hefbrug/platform_utils/user/UsermodManager.kt new file mode 100644 index 0000000..dbb9063 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/platform_utils/user/UsermodManager.kt @@ -0,0 +1,17 @@ +package me.eater.hefbrug.platform_utils.user + +import me.eater.hefbrug.access.AccessSkeleton +import me.eater.hefbrug.executor.ExecutionContext +import me.eater.hefbrug.platform_utils.PlatformUtil + +class UsermodManager(context: ExecutionContext) : UserManager(context) { + + + object Util : PlatformUtil { + override suspend fun isSupported(access: AccessSkeleton): Boolean { + return access.execute("which", "usermod").exitCode == 0 + } + + override suspend fun getManager(context: ExecutionContext) = UsermodManager(context) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/selector/AllSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/AllSelector.kt new file mode 100644 index 0000000..262f4da --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/AllSelector.kt @@ -0,0 +1,11 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class AllSelector(private val selectors: Collection) : SelectorInterface { + override fun matches(node: Node) = selectors.all { + it.matches(node) + } + + constructor(vararg selector: SelectorInterface) : this(selector.asList()) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/selector/AndSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/AndSelector.kt new file mode 100644 index 0000000..a913ce7 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/AndSelector.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class AndSelector(private val left: SelectorInterface, private val right: SelectorInterface) : SelectorInterface { + override fun matches(node: Node): Boolean = left.matches(node) && right.matches(node) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/selector/GroupSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/GroupSelector.kt new file mode 100644 index 0000000..78cd489 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/GroupSelector.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class GroupSelector(private val group: String) : SelectorInterface { + override fun matches(node: Node): Boolean = node.groups.contains(group) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/selector/NodeSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/NodeSelector.kt new file mode 100644 index 0000000..03e18ae --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/NodeSelector.kt @@ -0,0 +1,42 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class NodeSelector(private val selector: NameSelector) : SelectorInterface { + override fun matches(node: Node): Boolean { + return selector.match(node.hostname) + } + + constructor(selector: String) : this( + if ("*" in selector) { + NameSelector.RegexMatch(globToRegex(selector)) + } else { + NameSelector.ExactMatch(selector) + } + ) + + constructor(selector: Regex) : this(NameSelector.RegexMatch(selector)) + + sealed class NameSelector { + data class ExactMatch(val name: String) : NameSelector() { + override fun match(name: String) = name == this.name + } + + data class RegexMatch(val regex: Regex) : NameSelector() { + override fun match(name: String) = regex.matches(name) + } + + abstract fun match(name: String): Boolean + } + + companion object { + fun globToRegex(glob: String): Regex { + val regexStr = glob + .replace(Regex("""\*+""")) { """\E.+\Q""" } + .replace(Regex("""\?+""")) { """\E[^\.]+\Q""" } + .replace("""\Q\E""", "") + + return Regex("""^\Q$regexStr\E$""", RegexOption.IGNORE_CASE) + } + } +} diff --git a/src/main/kotlin/me/eater/hefbrug/selector/NotSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/NotSelector.kt new file mode 100644 index 0000000..368ae71 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/NotSelector.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class NotSelector(private val selector: SelectorInterface) : SelectorInterface { + override fun matches(node: Node): Boolean = !selector.matches(node) +} diff --git a/src/main/kotlin/me/eater/hefbrug/selector/OrSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/OrSelector.kt new file mode 100644 index 0000000..8ca5950 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/OrSelector.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class OrSelector(private val left: SelectorInterface, private val right: SelectorInterface) : SelectorInterface { + override fun matches(node: Node): Boolean = left.matches(node) || right.matches(node) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/selector/SelectorInterface.kt b/src/main/kotlin/me/eater/hefbrug/selector/SelectorInterface.kt new file mode 100644 index 0000000..8b54f29 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/SelectorInterface.kt @@ -0,0 +1,13 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.dsl.annotation.HefbrugDSL +import me.eater.hefbrug.node.Node + +@HefbrugDSL +interface SelectorInterface { + fun matches(node: Node): Boolean + + operator fun not(): SelectorInterface = NotSelector(this) + infix fun and(right: SelectorInterface): SelectorInterface = AndSelector(this, right) + infix fun or(right: SelectorInterface): SelectorInterface = OrSelector(this, right) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/selector/TrueSelector.kt b/src/main/kotlin/me/eater/hefbrug/selector/TrueSelector.kt new file mode 100644 index 0000000..3bae291 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/selector/TrueSelector.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.selector + +import me.eater.hefbrug.node.Node + +class TrueSelector : SelectorInterface { + override fun matches(node: Node) = true +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/state/AbstractState.kt b/src/main/kotlin/me/eater/hefbrug/state/AbstractState.kt new file mode 100644 index 0000000..e62a890 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/AbstractState.kt @@ -0,0 +1,75 @@ +package me.eater.hefbrug.state + +import me.eater.hefbrug.state.property.ExtendedPropertyDelegate +import me.eater.hefbrug.state.property.PropertyDelegate +import kotlin.reflect.KProperty + +abstract class AbstractState(val id: String) { + val properties: MutableMap> = mutableMapOf() + + protected fun state( + default: T? = null, + required: Boolean = false, + writeOnce: Boolean = false + ): PropertyDelegate = + state(required, writeOnce) { default } + + protected fun extState( + default: T? = null, + required: Boolean = false, + writeOnce: Boolean = false, + equals: ExtendedPropertyDelegate.DiffScope.() -> Boolean + ): ExtendedPropertyDelegate = + extState(required, writeOnce, { default }, equals) + + protected fun state(required: Boolean = false, writeOnce: Boolean = false, default: () -> T?) = + PropertyDelegate(default, required, writeOnce) + + protected fun extState( + required: Boolean = false, + writeOnce: Boolean = false, + default: () -> T?, + equals: ExtendedPropertyDelegate.DiffScope.() -> Boolean + ) = + ExtendedPropertyDelegate(default, required, writeOnce, equals) + + + @Suppress("UNCHECKED_CAST") + operator fun get(property: KProperty): PropertyDelegate = + (properties[property.name] as? PropertyDelegate)!! + + @Suppress("UNCHECKED_CAST") + operator fun get(property: String): PropertyDelegate<*> = + properties[property]!! + + override fun toString(): String = + "${this::class.simpleName ?: "State"}[id=$id] {${if (properties.isNotEmpty()) + "\n ${this.properties.map { (k, v) -> "[$k]: ${v.get()}" }.joinToString("\n ")}\n" + else "" + }}" + + open fun diff(currentState: AbstractState): Set { + if (javaClass != currentState.javaClass) { + error("When diffing states both should be the same kind of state") + } + + val changed = mutableSetOf() + for ((name, property) in properties) { + val otherProperty = currentState.properties[name] ?: continue + + if (property is ExtendedPropertyDelegate<*> && otherProperty is ExtendedPropertyDelegate<*>) { + if (property.hasChanged(otherProperty)) { + changed.add(name) + } + } else { + if (property.get() != otherProperty.get()) { + changed.add(name) + } + } + } + + return changed + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/state/ExistenceStatus.kt b/src/main/kotlin/me/eater/hefbrug/state/ExistenceStatus.kt new file mode 100644 index 0000000..ab0eb64 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/ExistenceStatus.kt @@ -0,0 +1,7 @@ +package me.eater.hefbrug.state + +enum class ExistenceStatus { + Allow, + Present, + Absent +} diff --git a/src/main/kotlin/me/eater/hefbrug/state/impl/PackageState.kt b/src/main/kotlin/me/eater/hefbrug/state/impl/PackageState.kt new file mode 100644 index 0000000..95b2ff6 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/impl/PackageState.kt @@ -0,0 +1,35 @@ +package me.eater.hefbrug.state.impl + +import me.eater.hefbrug.state.AbstractState +import me.eater.hefbrug.state.ExistenceStatus +import me.eater.hefbrug.state.ExistenceStatus.Allow + +class PackageState(id: String) : AbstractState(id) { + var upgrade: Boolean by extState(false) { + if (status == Allow) + true + else + left == right + } + var name: String by state(id, true) + var status: ExistenceStatus by extState(Allow) { + Allow == right || Allow == left || left == right + } + + /*override fun diff(currentState: AbstractState): Set { + if (currentState is PackageState) { + return diff(currentState) + } + + return super.diff(currentState) + } + + fun diff(currentState: PackageState): Set { + val diff = super.diff(currentState) + if ("upgrade" in diff && status == Allow && currentState.status == Absent) { + return diff - "upgrade" + } + + return diff + }*/ +} diff --git a/src/main/kotlin/me/eater/hefbrug/state/impl/ServiceState.kt b/src/main/kotlin/me/eater/hefbrug/state/impl/ServiceState.kt new file mode 100644 index 0000000..48bcd9c --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/impl/ServiceState.kt @@ -0,0 +1,10 @@ +package me.eater.hefbrug.state.impl + +import me.eater.hefbrug.state.AbstractState + +class ServiceState(id: String) : AbstractState(id) { + var name by state(id) + var running by state(true) + var enabled by state(true) + var autostart: Boolean by state { running && enabled } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/state/property/ExtendedPropertyDelegate.kt b/src/main/kotlin/me/eater/hefbrug/state/property/ExtendedPropertyDelegate.kt new file mode 100644 index 0000000..4b4bd53 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/property/ExtendedPropertyDelegate.kt @@ -0,0 +1,20 @@ +package me.eater.hefbrug.state.property + +import me.eater.hefbrug.state.AbstractState +import kotlin.properties.ReadWriteProperty + +class ExtendedPropertyDelegate( + default: () -> T?, + required: Boolean, + writeOnce: Boolean, + private val isEqual: DiffScope.() -> Boolean +) : + PropertyDelegate(default, required, writeOnce), + ReadWriteProperty { + + @Suppress("UNCHECKED_CAST") + fun hasChanged(other: ExtendedPropertyDelegate<*>): Boolean = !isEqual(DiffScope(this.get(), other.get() as T)) + + + data class DiffScope(val left: T, val right: T) +} diff --git a/src/main/kotlin/me/eater/hefbrug/state/property/PropertyDelegate.kt b/src/main/kotlin/me/eater/hefbrug/state/property/PropertyDelegate.kt new file mode 100644 index 0000000..8f613ce --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/property/PropertyDelegate.kt @@ -0,0 +1,57 @@ +package me.eater.hefbrug.state.property + +import me.eater.hefbrug.state.AbstractState +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +open class PropertyDelegate( + val default: () -> T? = { null }, + val required: Boolean = false, + val writeOnce: Boolean = false +) : + ReadWriteProperty { + var reference = "???.???" + var value: T? = null + var written: Boolean = false + var weight: Long = 0 + + fun get(): T { + val nullable = getNullable() + + return nullable!! + } + + fun getNullable() = (if (written) { + value + } else { + default() + }) + + fun set(value: T, weight: Long = 0) { + if (written && writeOnce) { + throw RuntimeException("Can only write $reference once") + } + + if (weight < this.weight) { + return + } + + written = true + this.value = value + this.weight = weight + } + + override fun getValue(thisRef: AbstractState, property: KProperty<*>): T { + return get() + } + + override fun setValue(thisRef: AbstractState, property: KProperty<*>, value: T) { + set(value) + } + + operator fun provideDelegate(thisRef: AbstractState, property: KProperty<*>): PropertyDelegate { + this.reference = "${thisRef::class.qualifiedName ?: "???"}.${property.name}" + thisRef.properties[property.name] = this + return this + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/state/property/ProxyDelegate.kt b/src/main/kotlin/me/eater/hefbrug/state/property/ProxyDelegate.kt new file mode 100644 index 0000000..545ebde --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/state/property/ProxyDelegate.kt @@ -0,0 +1,14 @@ +package me.eater.hefbrug.state.property + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.KProperty + +class ProxyDelegate(val reference: KMutableProperty0) : ReadWriteProperty { + override fun getValue(thisRef: R, property: KProperty<*>): T = + reference.get() + + + override fun setValue(thisRef: R, property: KProperty<*>, value: T) = + reference.set(value) +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/utils/ArgumentDelegate.kt b/src/main/kotlin/me/eater/hefbrug/utils/ArgumentDelegate.kt new file mode 100644 index 0000000..5497e62 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/utils/ArgumentDelegate.kt @@ -0,0 +1,14 @@ +package me.eater.hefbrug.utils + +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class ArgumentDelegate(val map: Map, val name: String? = null, val default: T? = null) : + ReadOnlyProperty { + @Suppress("UNCHECKED_CAST") + override fun getValue(thisRef: Nothing?, property: KProperty<*>): T { + val key = name ?: property.name + if (key !in map) return default as T + return map[key] as T + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/utils/CoroutinesUtil.kt b/src/main/kotlin/me/eater/hefbrug/utils/CoroutinesUtil.kt new file mode 100644 index 0000000..ae5cc73 --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/utils/CoroutinesUtil.kt @@ -0,0 +1,50 @@ +package me.eater.hefbrug.utils + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.channels.Channel +import java.util.concurrent.Executors + +suspend fun parallel(input: Iterable, threads: Int = 10, block: suspend (T) -> Unit) { + val ch = Channel(Channel.RENDEZVOUS) + val ech = Channel(threads) + val dispatcher = Executors + .newFixedThreadPool(threads) + .asCoroutineDispatcher() + + val promises = (0..threads).map { + GlobalScope.async(dispatcher) { + for (item in ch) { + try { + block(item) + } catch (t: Throwable) { + ech.send(t) + return@async + } + } + } + } + + for (item in input) { + ch.send(item) + + val t = ech.poll() + if (t != null) { + ch.close() + promises.awaitAll() + dispatcher.close() + throw t + } + } + + ch.close() + promises.awaitAll() + dispatcher.close() + + val t = ech.poll() + if (t != null) { + throw t + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/eater/hefbrug/utils/StringUtil.kt b/src/main/kotlin/me/eater/hefbrug/utils/StringUtil.kt new file mode 100644 index 0000000..043294b --- /dev/null +++ b/src/main/kotlin/me/eater/hefbrug/utils/StringUtil.kt @@ -0,0 +1,31 @@ +package me.eater.hefbrug.utils + +import me.eater.hefbrug.logging.LogFormat +import org.apache.logging.log4j.util.StringBuilderFormattable + + +fun String.escape(): String = + if (contains('"') || contains('\\') || contains('!') || contains('$') || contains('#')) { + "\"${escapeChar('\\').escapeChar('"').escapeChar('$').escapeChar('#')}\"" + } else this + + +fun String.escapeChar(char: Char, escapeChar: Char = '\\'): String { + return replace("$char", "$escapeChar$char") +} + + +fun String.handlebars(vararg items: Pair): String = handlebars(items.toMap()) + +fun String.handlebars(map: Map): String { + return replace(Regex("""\{\{([a-z0-9_-]+)}}""")) { res -> + when (val item = map[res.groupValues[1]]) { + is LogFormat -> item.logFormat() + is StringBuilderFormattable -> StringBuilder().also { + item.formatTo(it) + it.toString() + } + else -> "$item" + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/kotlin/script/templates/me.eater.hefbrug.executor.HefbrugScript b/src/main/resources/META-INF/kotlin/script/templates/me.eater.hefbrug.executor.HefbrugScript new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/blaat.hb.kts b/src/main/resources/blaat.hb.kts new file mode 100644 index 0000000..ae63e7d --- /dev/null +++ b/src/main/resources/blaat.hb.kts @@ -0,0 +1,11 @@ +module("ok") { + val x: String by arg() + + sv("name") { + stopped + } + + pkg("bash") { + installed + } +}