Initial commit

master
eater 4 years ago
commit 63552dfc7b
Signed by: eater
GPG Key ID: AD2560A0F84F0759

2
.gitignore vendored

@ -0,0 +1,2 @@
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"

@ -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/"
}
}
}

@ -0,0 +1 @@
../gradle

1
examples/gradlew vendored

@ -0,0 +1 @@
../gradlew

@ -0,0 +1,2 @@
rootProject.name = "hefbrug-examples"
includeBuild("..")

@ -0,0 +1,6 @@
include(
"./selectors.hb.kts",
"./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"]
}
}
}

@ -0,0 +1,5 @@
assign {
node("momo") {
groups += "dev"
}
}

@ -0,0 +1 @@
kotlin.code.style=official

Binary file not shown.

@ -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

172
gradlew vendored

@ -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" "$@"

84
gradlew.bat vendored

@ -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

@ -0,0 +1 @@
rootProject.name = 'hefbrug'

@ -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<String>) {
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)
}
}
}

@ -0,0 +1,15 @@
package me.eater.hefbrug.access
interface AccessSkeleton {
fun id(): String
suspend fun execute(
vararg command: String,
environment: Map<String, String> = 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
}
}

@ -0,0 +1,27 @@
package me.eater.hefbrug.access
data class ExecutionCommand(
val command: Array<out String>,
val environment: Map<String, String>,
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
}
}

@ -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)
}

@ -0,0 +1,7 @@
package me.eater.hefbrug.access
enum class FileType(val switch: String) {
Anything("-e"),
Directory("-d"),
File("-f");
}

@ -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<String, String>,
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()
)
}
}

@ -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<String, String>,
workingDirectory: String?
): ExecutionOutput {
return ExecutionOutput(ExecutionCommand(command, environment, workingDirectory), 0, "", "")
}
}

@ -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<String, String>,
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)
}
}

@ -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<S : AbstractState, D : DefinitionWildcard<S>>(val context: ExecutionContext) {
abstract suspend fun collect(definition: D): S
}

@ -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<PackageState, PackageDefinition>(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
}
}
}

@ -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<ServiceState, ServiceDefinition>(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)
}
}
}

@ -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<S : AbstractState, C : DefinitionContextSkeleton<S>>(
override val key: DefinitionKey,
override val state: S
) : DefinitionSkeleton<S, C> {
override val id: String get() = state.id
override val require: MutableSet<DefinitionKey> = mutableSetOf()
override val before: MutableSet<DefinitionKey> = mutableSetOf()
override val after: MutableSet<DefinitionKey> = mutableSetOf()
}

@ -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<S : AbstractState, C : DefinitionContextSkeleton<S>, D : DefinitionSkeleton<S, C>>(
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<S, D>
abstract fun createEnforcer(context: ExecutionContext): AbstractEnforcer<S>
init {
FactoryRegister += this
}
}

@ -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|@"
}

@ -0,0 +1,3 @@
package me.eater.hefbrug.definition
open class DefinitionKeyAction(name: String, id: String, val action: String) : DefinitionKey(name, id)

@ -0,0 +1,16 @@
package me.eater.hefbrug.definition
import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton
import me.eater.hefbrug.state.AbstractState
interface DefinitionSkeleton<S : AbstractState, out C : DefinitionContextSkeleton<S>> {
val key: DefinitionKey
val state: S
val id: String
val require: MutableSet<DefinitionKey>
val before: MutableSet<DefinitionKey>
val after: MutableSet<DefinitionKey>
fun getContext(): C
}

@ -0,0 +1,5 @@
package me.eater.hefbrug.definition
import me.eater.hefbrug.dsl.context.DefinitionContextSkeleton
typealias DefinitionWildcard<S> = DefinitionSkeleton<S, DefinitionContextSkeleton<S>>

@ -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<PackageState, PackageContext>(factory.key(id), PackageState(id)) {
override fun getContext(): PackageContext = PackageContext(this)
class Factory : DefinitionFactory<PackageState, PackageContext, PackageDefinition>("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()
}
}

@ -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<ServiceState, ServiceContext>(factory.key(id), ServiceState(id)) {
override fun getContext(): ServiceContext = ServiceContext(this)
class Factory : DefinitionFactory<ServiceState, ServiceContext, ServiceDefinition>("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()
}
}

@ -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()
}

@ -0,0 +1,4 @@
package me.eater.hefbrug.dsl.annotation
@DslMarker
annotation class HefbrugDSL

@ -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<AssignContext.() -> Unit> {
val groups: MutableSet<String>
get() = node.groups
override fun select(selector: SelectorInterface, block: AssignContext.() -> Unit) {
if (selector.matches(node)) {
block(this)
}
}
}

@ -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))
}
}

@ -0,0 +1,8 @@
package me.eater.hefbrug.dsl.context
import java.util.*
interface ContextInterface {
val contextUUID: UUID
}

@ -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<S : AbstractState>(private val definition: DefinitionSkeleton<S, DefinitionContextSkeleton<S>>) :
DefinitionContextSkeleton<S> {
protected fun <T> proxy(reference: KMutableProperty0<T>): ReadWriteProperty<DefinitionContext<*>, T> =
ProxyDelegate(reference)
protected val state: S
get() = definition.state
override val after: MutableSet<DefinitionKey>
get() = definition.after
override val before: MutableSet<DefinitionKey>
get() = definition.before
override val require: MutableSet<DefinitionKey>
get() = definition.require
}

@ -0,0 +1,10 @@
package me.eater.hefbrug.dsl.context
import me.eater.hefbrug.definition.DefinitionKey
import me.eater.hefbrug.state.AbstractState
interface DefinitionContextSkeleton<S : AbstractState> {
val require: MutableSet<DefinitionKey>
val before: MutableSet<DefinitionKey>
val after: MutableSet<DefinitionKey>
}

@ -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<DefinitionKeyAction>
operator fun MutableSet<DefinitionKeyAction>.plusAssign(definitionKey: Array<out DefinitionKey>) {
this.addAll(definitionKey.map { DefinitionKeyAction(it.group, it.id, "default") })
}
}

@ -0,0 +1,7 @@
package me.eater.hefbrug.dsl.context
import me.eater.hefbrug.definition.DefinitionKeyAction
open class EmitterContext : Emitter {
override val notify: MutableSet<DefinitionKeyAction> = mutableSetOf()
}

@ -0,0 +1,5 @@
package me.eater.hefbrug.dsl.context
interface Listener {
val listen: MutableSet<Unit>
}

@ -0,0 +1,5 @@
package me.eater.hefbrug.dsl.context
class ListenerContext : Listener {
override val listen: MutableSet<Unit> = mutableSetOf()
}

@ -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<String, Any?>
) :
TargetedContext() {
val id: String
get() = module.id
fun <T> arg(name: String, default: T? = null) =
ArgumentDelegate<T>(arguments, name, default)
fun <T> arg(default: T? = null) =
ArgumentDelegate<T>(arguments, null, default)
}

@ -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<suspend TargetedContext.() -> 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)
}
}

@ -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)
}

@ -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<C> {
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)
}
}

@ -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<suspend TargetedContext.() -> Unit> by root,
SourceContextSkeleton {
fun include(vararg file: String) {
for (f in file) {
executor.run(location.resolve(f))
}
}
}

@ -0,0 +1,5 @@
package me.eater.hefbrug.dsl.context
interface SourceContextSkeleton : RootContextSkeleton {
}

@ -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)
}

@ -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 <S : AbstractState, C : DefinitionContextSkeleton<S>, D : DefinitionSkeleton<S, C>, F : DefinitionFactory<S, C, D>> runBlock(
context: TargetedContext,
factory: F,
id: Array<out String>,
block: suspend C.() -> Unit
) {
for (idx in id) {
block(
RuntimeRegister[context.runtimeUUID]
.getScope<TargetedScope>(context)
.getDefinition(idx, factory)
.getContext()
)
}
}
class KeyHelper(private val factory: DefinitionFactory<*, *, *>) {
operator fun get(id: String) = factory.key(id)
}
}

@ -0,0 +1,13 @@
package me.eater.hefbrug.dsl.context.extension_util
import me.eater.hefbrug.definition.DefinitionFactory
object FactoryRegister {
private val map = mutableMapOf<String, DefinitionFactory<*, *, *>>()
operator fun plusAssign(factory: DefinitionFactory<*, *, *>) {
map[factory.id] = factory
}
operator fun get(id: String) = map[id]!!
}

@ -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<UUID, Register>()
operator fun get(id: UUID) = registerMap[id]!!
fun register(register: Register) {
registerMap[register.id] = register
}
}
}

@ -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<ContextInterface, ScopeInterface>()
@Suppress("UNCHECKED_CAST")
fun <T : ScopeInterface> getScope(context: ContextInterface): T =
scopeMap[context]!! as T
fun registerScope(context: ContextInterface, scope: ScopeInterface) {
scopeMap[context] = scope
}
companion object {
private val runtimeRegisterMap = mutableMapOf<UUID, RuntimeRegister>()
operator fun get(runtimeUUID: UUID) =
runtimeRegisterMap.getOrPut(runtimeUUID, { RuntimeRegister() })
fun remove(runtimeUUID: UUID) {
runtimeRegisterMap.remove(runtimeUUID)
}
}
}

@ -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<PackageState>(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)
}

@ -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<ServiceState>(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
}
}

@ -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 <S : AbstractState, C : DefinitionContextSkeleton<S>, D : DefinitionSkeleton<S, C>> getDefinition(
id: String,
factory: DefinitionFactory<S, C, D>
): D =
parentScope.getDefinition(id, factory)
}

@ -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<DefinitionKey, DefinitionSkeleton<*, *>>()
val definitions by lazy { definitionsMut }
@Suppress("UNCHECKED_CAST")
override fun <S : AbstractState, C : DefinitionContextSkeleton<S>, D : DefinitionSkeleton<S, C>> getDefinition(
id: String,
factory: DefinitionFactory<S, C, D>
): D =
definitionsMut.getOrPut(factory.key(id)) {
factory.build(id)
} as D
}

@ -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 <S : AbstractState, C : DefinitionContextSkeleton<S>, D : DefinitionSkeleton<S, C>> getDefinition(
id: String,
factory: DefinitionFactory<S, C, D>
): D
}

@ -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<Pair<SelectorInterface, suspend TargetedContext.() -> Unit>> = mutableSetOf()
private val modules: MutableMap<String, Module> = mutableMapOf()
private val assigners: MutableSet<AssignContext.() -> 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<DefinitionKey, DefinitionSkeleton<*, *>> {
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
}
}

@ -0,0 +1,3 @@
package me.eater.hefbrug.dsl.scope
interface TargetedScope : ScopeInterface

@ -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<S: AbstractState>(val context: ExecutionContext) {
operator fun Set<String>.contains(property: KProperty<*>): Boolean {
return property.name in this
}
abstract suspend fun enforce(currentState: S, desiredState: S, changeSet: Set<String>)
}

@ -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<PackageState>(context) {
override suspend fun enforce(currentState: PackageState, desiredState: PackageState, changeSet: Set<String>) {
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)
}
}
}

@ -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<ServiceState>(context) {
override suspend fun enforce(currentState: ServiceState, desiredState: ServiceState, changeSet: Set<String>) {
val sm = context.getServiceManager()
sm.setState(
desiredState.name,
enabled = desiredState.enabled,
running = desiredState.running,
autostart = desiredState.autostart
)
}
}

@ -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!!
}
}

@ -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<DefinitionKey, DefinitionSkeleton<*, *>>) :
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<AbstractState, DefinitionContextSkeleton<AbstractState>>)
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))
}
}

@ -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<HefbrugScript>()
fun run(name: String) {
val location = Location.File(Paths.get(name).toRealPath().toString())
val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<HefbrugScript> {
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))
}
}

@ -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<DefinitionKey, DefinitionSkeleton<*, *>>): List<Set<DefinitionKey>> {
val dependencies = mutableMapOf<DefinitionKey, MutableSet<DefinitionKey>>()
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<DefinitionKey>()
val graphLayers = mutableListOf<Set<DefinitionKey>>()
while (todo.isNotEmpty()) {
val nextLayer = mutableSetOf<DefinitionKey>()
for (item in todo) {
if (dependencies[item]?.all { it in fulfilled } != false) {
nextLayer.add(item)
}
}
fulfilled += nextLayer
todo -= nextLayer
graphLayers.add(nextLayer)
}
return graphLayers
}
}

@ -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
)
}
})

@ -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()
})

@ -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
}

@ -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<String, AbstractCollector<*, *>>()
@Suppress("UNCHECKED_CAST")
suspend fun <S : AbstractState> collect(definition: DefinitionWildcard<S>): S {
val collector = collectors.getOrPut(
definition.key.group,
{ FactoryRegister[definition.key.group].createCollector(context) }) as AbstractCollector<S, DefinitionWildcard<S>>
return collector.collect(definition)
}
}

@ -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<String, AbstractEnforcer<*>>()
@Suppress("UNCHECKED_CAST")
suspend fun <S : AbstractState, D : DefinitionWildcard<S>> enforce(
definition: D,
currentState: S
) {
val enforcer = enforcers.getOrPut(
definition.key.group,
{ FactoryRegister[definition.key.group].createEnforcer(context) }) as AbstractEnforcer<S>
val diff = definition.state.diff(currentState)
if (diff.isNotEmpty())
info(
foundDifference(
definition.key,
context.node,
mutableMapOf<String, Pair<Any?, Any?>>().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)
}
}

@ -0,0 +1,5 @@
package me.eater.hefbrug.logging
interface LogFormat {
fun logFormat(): String
}

@ -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<String> = arrayOf("*")
}

@ -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 <R : Any?> runInTrace(block: () -> R): R = logger.runInTrace(block)
fun <R : Any?> 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)
}

@ -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<String, Any?>) : Message,
StringBuilderFormattable {
constructor(message: String, vararg items: Pair<String, Any?>) : this(message, items.toMap())
override fun getThrowable(): Throwable? = null
override fun getParameters(): Array<Any> {
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)
}
}

@ -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<String, Pair<Any?, Any?>>) =
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<String, Pair<Any?, Any?>>) : 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(", ")}]"
}
}
}

@ -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<suspend ModuleContext.() -> Unit> = mutableSetOf()
fun getContext(runtimeUUID: UUID, arguments: Map<String, Any?>): ModuleContext = ModuleContext(runtimeUUID, contextUUID, this, arguments)
fun addBlock(block: suspend ModuleContext.() -> Unit) = blocks.add(block)
}

@ -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<String>()
override fun toString() = "Node[$hostname,groups={${groups.joinToString(",")}}]"
override fun logFormat() = "@|green $this|@"
}

@ -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<out M> {
suspend fun isSupported(access: AccessSkeleton): Boolean
suspend fun getManager(context: ExecutionContext): M
abstract class Provider<M: Any>(val name: String, vararg manager: PlatformUtil<M>) : Logging {
private val managers: MutableSet<PlatformUtil<M>> = 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<M>) {
managers.add(packageManager)
}
}
}

@ -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<PackageManager>(
"package manager",
XbpsManager.Util
)
}

@ -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<XbpsManager> {
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)
}
}

@ -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<RunitManager> {
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)
}
}

@ -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<ServiceManager>(
"service manager",
RunitManager.Util
)
}

@ -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<PlatformUtil<UserManager>> = 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")
}
}
}

@ -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<UsermodManager> {
override suspend fun isSupported(access: AccessSkeleton): Boolean {
return access.execute("which", "usermod").exitCode == 0
}
override suspend fun getManager(context: ExecutionContext) = UsermodManager(context)
}
}

@ -0,0 +1,11 @@
package me.eater.hefbrug.selector
import me.eater.hefbrug.node.Node
class AllSelector(private val selectors: Collection<SelectorInterface>) : SelectorInterface {
override fun matches(node: Node) = selectors.all {
it.matches(node)
}
constructor(vararg selector: SelectorInterface) : this(selector.asList())
}

@ -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)
}

@ -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)
}

@ -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)
}
}
}

@ -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)
}

@ -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)
}

@ -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)
}

@ -0,0 +1,7 @@
package me.eater.hefbrug.selector
import me.eater.hefbrug.node.Node
class TrueSelector : SelectorInterface {
override fun matches(node: Node) = true
}

@ -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<String, PropertyDelegate<*>> = mutableMapOf()
protected fun <T> state(
default: T? = null,
required: Boolean = false,
writeOnce: Boolean = false
): PropertyDelegate<T> =
state<T>(required, writeOnce) { default }
protected fun <T> extState(
default: T? = null,
required: Boolean = false,
writeOnce: Boolean = false,
equals: ExtendedPropertyDelegate.DiffScope<T>.() -> Boolean
): ExtendedPropertyDelegate<T> =
extState<T>(required, writeOnce, { default }, equals)
protected fun <T> state(required: Boolean = false, writeOnce: Boolean = false, default: () -> T?) =
PropertyDelegate<T>(default, required, writeOnce)
protected fun <T> extState(
required: Boolean = false,
writeOnce: Boolean = false,
default: () -> T?,
equals: ExtendedPropertyDelegate.DiffScope<T>.() -> Boolean
) =
ExtendedPropertyDelegate<T>(default, required, writeOnce, equals)
@Suppress("UNCHECKED_CAST")
operator fun <T> get(property: KProperty<T>): PropertyDelegate<T> =
(properties[property.name] as? PropertyDelegate<T>)!!
@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<String> {
if (javaClass != currentState.javaClass) {
error("When diffing states both should be the same kind of state")
}
val changed = mutableSetOf<String>()
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
}
}

@ -0,0 +1,7 @@
package me.eater.hefbrug.state
enum class ExistenceStatus {
Allow,
Present,
Absent
}

@ -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<String> {
if (currentState is PackageState) {
return diff(currentState)
}
return super.diff(currentState)
}
fun diff(currentState: PackageState): Set<String> {
val diff = super.diff(currentState)
if ("upgrade" in diff && status == Allow && currentState.status == Absent) {
return diff - "upgrade"
}
return diff
}*/
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save