initial commit
This commit is contained in:
parent
d1af64aa8f
commit
4766ffe18e
38 changed files with 2654 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/.gradle/*
|
||||
/build/*
|
||||
/out/*
|
||||
/.idea/*
|
||||
*.log
|
52
.gitlab-ci.yml
Normal file
52
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,52 @@
|
|||
image: openjdk:15-jdk-slim
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- jar
|
||||
- publish
|
||||
|
||||
before_script:
|
||||
- echo "---------- Start CI ----------"
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||
- export repoUser="$repoUser"
|
||||
- export repoPassword="$repoPassword"
|
||||
- chmod +x gradlew
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- echo "---------- build stage ----------"
|
||||
- ./gradlew --build-cache assemble
|
||||
artifacts:
|
||||
paths:
|
||||
- build/libs/*.jar
|
||||
expire_in: 1 week
|
||||
|
||||
test:
|
||||
stage: test
|
||||
services:
|
||||
- docker:dind
|
||||
dependencies:
|
||||
- build
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
jar:
|
||||
stage: jar
|
||||
script:
|
||||
- echo "---------- jar ----------"
|
||||
- ./gradlew jar
|
||||
artifacts:
|
||||
paths:
|
||||
- build/libs/*.jar
|
||||
expire_in: 2 months
|
||||
|
||||
publish:
|
||||
stage: publish
|
||||
script:
|
||||
- echo "---------- publish ----------"
|
||||
- ./gradlew -PrepoUser="$repoUser" -PrepoPassword="$repoPassword" publish
|
||||
|
||||
after_script:
|
||||
- echo "---------- End CI ----------"
|
52
Readme.md
Normal file
52
Readme.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
# Provs-core
|
||||
|
||||
the core engine of the provs framework.
|
||||
|
||||
## Provs framework
|
||||
|
||||
Framework for automating shell- and other system-tasks for provisioning reasons or other purposes.
|
||||
|
||||
Can easily be run
|
||||
|
||||
* locally
|
||||
* remotely
|
||||
* in a docker container
|
||||
* or with any self-defined custom processor
|
||||
|
||||
Combines the
|
||||
* convenience and robustness of a modern programming language (Kotlin) with
|
||||
* power of being able to use shell commands and with
|
||||
* clear and detailed result summary of the built-in failure handling.
|
||||
|
||||
## Provs-core
|
||||
|
||||
Provs-core provides the core component with
|
||||
* execution engine
|
||||
* failure handling
|
||||
* multiple execution processors
|
||||
* execution summary and logging
|
||||
* support for secrets
|
||||
|
||||
## Usage
|
||||
|
||||
### Run hello world
|
||||
Locally:
|
||||
|
||||
`kotlinc -cp build/libs/provs-latest.jar -script scripts/HelloWorldLocal.kts`
|
||||
|
||||
Remotely:
|
||||
|
||||
`kotlinc -cp build/libs/provs-latest.jar -script scripts/HelloWorldRemote.kts`
|
||||
|
||||
### Other examples
|
||||
For a bunch of usage examples please have a look at provs-ubuntu-extensions.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
[Apache license 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and limitations under the License.
|
154
build.gradle
Normal file
154
build.gradle
Normal file
|
@ -0,0 +1,154 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.4.21'
|
||||
repositories { jcenter() }
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
group = 'io.provs'
|
||||
version = '0.8.5-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform {
|
||||
excludeTags('containertest')
|
||||
}
|
||||
}
|
||||
|
||||
compileJava.options.debugOptions.debugLevel = "source,lines,vars"
|
||||
compileTestJava.options.debugOptions.debugLevel = "source,lines,vars"
|
||||
|
||||
// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" // JVM dependency
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
implementation group: 'com.hierynomus', name: 'sshj', version: '0.30.0'
|
||||
|
||||
api "org.slf4j:slf4j-api:1.7.30"
|
||||
api "ch.qos.logback:logback-classic:1.2.3"
|
||||
api "ch.qos.logback:logback-core:1.2.3"
|
||||
|
||||
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
|
||||
testImplementation "io.mockk:mockk:1.9.3"
|
||||
}
|
||||
|
||||
|
||||
//create a single Jar with all dependencies excl. Kotlin libs
|
||||
task fatJar(type: Jar) {
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'io.provs.entry.EntryKt'
|
||||
}
|
||||
archivesBaseName = project.name + '-all'
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
with jar
|
||||
}
|
||||
|
||||
task fatJarLatest(type: Jar) {
|
||||
doFirst {
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
}
|
||||
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
|
||||
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'io.provs.entry.EntryKt'
|
||||
}
|
||||
with jar
|
||||
archiveFileName = 'provs-fat-latest.jar'
|
||||
}
|
||||
|
||||
//create a single Jar with all dependencies incl. Kotlin libs
|
||||
task uberJar(type: Jar) {
|
||||
|
||||
from sourceSets.main.output
|
||||
|
||||
dependsOn configurations.runtimeClasspath
|
||||
from {
|
||||
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
|
||||
} {
|
||||
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'io.provs.entry.EntryKt'
|
||||
}
|
||||
archiveClassifier = 'uber'
|
||||
}
|
||||
|
||||
|
||||
//create a single Jar with all dependencies incl. Kotlin libs - as ...-latest
|
||||
task uberJarLatest(type: Jar) {
|
||||
|
||||
from sourceSets.main.output
|
||||
|
||||
dependsOn configurations.runtimeClasspath
|
||||
from {
|
||||
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
|
||||
} {
|
||||
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'io.provs.entry.EntryKt'
|
||||
}
|
||||
archiveFileName = 'provs-latest.jar'
|
||||
}
|
||||
|
||||
|
||||
task sourceJar(type: Jar, dependsOn: classes) {
|
||||
from sourceSets.main.allSource
|
||||
archiveClassifier.set("sources")
|
||||
}
|
||||
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
if (System.getenv("CI_JOB_TOKEN") != null) {
|
||||
// see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html
|
||||
maven {
|
||||
url "https://gitlab.com/api/v4/projects/23127098/packages/maven"
|
||||
name "GitLab"
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = 'Job-Token'
|
||||
value = System.getenv("CI_JOB_TOKEN")
|
||||
}
|
||||
// project id: 23966425
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
gradle.properties
Normal file
4
gradle.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
kotlin.code.style=official
|
||||
|
||||
#avoid nexus error "Cannot upload checksum for snapshot-maven-metadata.xml. Remote repository doesn't support sha-512. Error: Could not PUT ..."
|
||||
systemProp.org.gradle.internal.publish.checksums.insecure=true
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
188
gradlew
vendored
Executable file
188
gradlew
vendored
Executable file
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## 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='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# 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" "$@"
|
100
gradlew.bat
vendored
Normal file
100
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@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
|
1
settings.gradle
Normal file
1
settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
rootProject.name = 'provs-core'
|
361
src/main/kotlin/io/provs/Prov.kt
Normal file
361
src/main/kotlin/io/provs/Prov.kt
Normal file
|
@ -0,0 +1,361 @@
|
|||
package io.provs
|
||||
|
||||
import io.provs.platforms.SHELL
|
||||
import io.provs.platforms.UbuntuProv
|
||||
import io.provs.platforms.WinProv
|
||||
import io.provs.processors.LocalProcessor
|
||||
import io.provs.processors.Processor
|
||||
|
||||
|
||||
enum class ResultMode { NONE, LAST, ALL, FAILEXIT }
|
||||
enum class OS { WINDOWS, LINUX }
|
||||
|
||||
|
||||
/**
|
||||
* This main class offers methods to execute shell commands either locally or remotely (via ssh) or in a docker
|
||||
* depending on the processor which is passed to the constructor.
|
||||
*/
|
||||
open class Prov protected constructor(private val processor: Processor, val name: String? = null) {
|
||||
|
||||
companion object Factory {
|
||||
|
||||
lateinit var prov: Prov
|
||||
|
||||
fun defaultInstance(platform: String? = null): Prov {
|
||||
return if (::prov.isInitialized) {
|
||||
prov
|
||||
} else {
|
||||
prov = newInstance(platform = platform, name = "default instance")
|
||||
prov
|
||||
}
|
||||
}
|
||||
|
||||
fun newInstance(processor: Processor = LocalProcessor(), platform: String? = null, name: String? = null): Prov {
|
||||
|
||||
val os = platform ?: System.getProperty("os.name")
|
||||
|
||||
return when {
|
||||
os.toUpperCase().contains(OS.LINUX.name) -> UbuntuProv(processor, name)
|
||||
os.toUpperCase().contains(OS.WINDOWS.name) -> WinProv(processor, name)
|
||||
else -> throw Exception("OS not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val internalResults = arrayListOf<InternalResult>()
|
||||
private var level = 0
|
||||
private var previousLevel = 0
|
||||
private var exit = false
|
||||
private var runInContainerWithName: String? = null
|
||||
|
||||
|
||||
|
||||
// task defining functions
|
||||
fun def(a: Prov.() -> ProvResult): ProvResult {
|
||||
return handle(ResultMode.ALL) { a() }
|
||||
}
|
||||
|
||||
fun requireLast(a: Prov.() -> ProvResult): ProvResult {
|
||||
return handle(ResultMode.LAST) { a() }
|
||||
}
|
||||
|
||||
fun optional(a: Prov.() -> ProvResult): ProvResult {
|
||||
return handle(ResultMode.NONE) { a() }
|
||||
}
|
||||
|
||||
fun requireAll(a: Prov.() -> ProvResult): ProvResult {
|
||||
return handle(ResultMode.ALL) { a() }
|
||||
}
|
||||
|
||||
fun exitOnFailure(a: Prov.() -> ProvResult): ProvResult {
|
||||
return handle(ResultMode.FAILEXIT) { a() }
|
||||
}
|
||||
|
||||
fun inContainer(containerName: String, a: Prov.() -> ProvResult): ProvResult {
|
||||
runInContainerWithName = containerName
|
||||
val res = handle(ResultMode.ALL) { a() }
|
||||
runInContainerWithName = null
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
// execute programs
|
||||
fun xec(vararg s: String): ProvResult {
|
||||
val cmd = runInContainerWithName?.let { cmdInContainer(it, *s) } ?: s
|
||||
val result = processor.x(*cmd)
|
||||
return ProvResult(
|
||||
success = (result.exitCode == 0),
|
||||
cmd = result.argsToString(),
|
||||
out = result.out,
|
||||
err = result.err
|
||||
)
|
||||
}
|
||||
|
||||
fun xecNoLog(vararg s: String): ProvResult {
|
||||
val cmd = runInContainerWithName?.let { cmdInContainer(it, *s) } ?: s
|
||||
val result = processor.xNoLog(*cmd)
|
||||
return ProvResult(
|
||||
success = (result.exitCode == 0),
|
||||
cmd = result.argsToString(),
|
||||
out = result.out,
|
||||
err = result.err
|
||||
)
|
||||
}
|
||||
|
||||
fun cmdInContainer(containerName: String, vararg args: String): Array<String> {
|
||||
return arrayOf(SHELL, "-c", "sudo docker exec $containerName " + buildCommand(*args))
|
||||
}
|
||||
private fun buildCommand(vararg args: String) : String {
|
||||
return if (args.size == 1)
|
||||
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
||||
else
|
||||
if (args.size == 3 && SHELL.equals(args[0]) && "-c".equals(args[1]))
|
||||
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
||||
else
|
||||
args.joinToString(separator = " ")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute commands using the shell
|
||||
* Be aware: Executing shell commands that incorporate unsanitized input from an untrusted source
|
||||
* makes a program vulnerable to shell injection, a serious security flaw which can result in arbitrary command execution.
|
||||
* Thus, the use of this method is strongly discouraged in cases where the command string is constructed from external input.
|
||||
*/
|
||||
open fun cmd(cmd: String, dir: String? = null): ProvResult {
|
||||
throw Exception("Not implemented")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Same as method cmd but without logging of the result/output, should be used e.g. if secrets are involved.
|
||||
* Attention: only result is NOT logged the executed command still is.
|
||||
*/
|
||||
open fun cmdNoLog(cmd: String, dir: String? = null): ProvResult {
|
||||
throw Exception("Not implemented")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Same as method cmd but without evaluating the result for the overall success.
|
||||
* Can be used e.g. for checks which might succeed or fail but where failure should not influence overall success
|
||||
*/
|
||||
open fun cmdNoEval(cmd: String, dir: String? = null): ProvResult {
|
||||
throw Exception("Not implemented")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes command cmd and returns true in case of success else false.
|
||||
* The success resp. failure does not count into the overall success.
|
||||
*/
|
||||
fun check(cmd: String, dir: String? = null): Boolean {
|
||||
return cmdNoEval(cmd, dir).success
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a secret by executing the given command.
|
||||
* Returns the result of the command as secret.
|
||||
*/
|
||||
fun getSecret(command: String): Secret? {
|
||||
val result = cmdNoLog(command)
|
||||
addResultToEval(ProvResult(result.success, err = result.err, exception = result.exception, exit = result.exit))
|
||||
return if (result.success && result.out != null) {
|
||||
addResultToEval(ProvResult(true, getCallingMethodName()))
|
||||
Secret(result.out)
|
||||
} else {
|
||||
addResultToEval(ProvResult(false, getCallingMethodName(), err = result.err, exception = result.exception))
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an ProvResult to the overall success evaluation.
|
||||
* Intended for use in methods which do not automatically add results.
|
||||
*/
|
||||
fun addResultToEval(result: ProvResult) = requireAll {
|
||||
result
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes multiple shell commands. Each command must be in its own line.
|
||||
* Multi-line commands within the script are not supported.
|
||||
* Empty lines and comments (all text behind # in a line) are supported, i.e. they are ignored.
|
||||
*/
|
||||
fun sh(script: String) = def {
|
||||
val lines = script.trimIndent().replace("\r\n", "\n").split("\n")
|
||||
val linesWithoutComments = lines.stream().map { it.split("#")[0] }
|
||||
val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() }
|
||||
|
||||
var success = true
|
||||
|
||||
for (cmd in linesNonEmpty) {
|
||||
if (success) {
|
||||
success = success && cmd(cmd).success
|
||||
}
|
||||
}
|
||||
ProvResult(success)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides result handling, e.g. gather results for result summary
|
||||
*/
|
||||
private fun handle(mode: ResultMode, a: Prov.() -> ProvResult): ProvResult {
|
||||
|
||||
// init
|
||||
if (level == 0) {
|
||||
internalResults.clear()
|
||||
previousLevel = -1
|
||||
exit = false
|
||||
ProgressBar.init()
|
||||
}
|
||||
|
||||
// pre-handling
|
||||
val resultIndex = internalResults.size
|
||||
val method = getCallingMethodName()
|
||||
internalResults.add(InternalResult(level, method, null))
|
||||
|
||||
previousLevel = level
|
||||
|
||||
level++
|
||||
|
||||
// call the actual function
|
||||
val res = if (!exit) {
|
||||
ProgressBar.progress()
|
||||
@Suppress("UNUSED_EXPRESSION") // false positive
|
||||
a()
|
||||
} else {
|
||||
ProvResult(false, out = "Exiting due to failure and mode FAILEXIT")
|
||||
}
|
||||
|
||||
level--
|
||||
|
||||
// post-handling
|
||||
val returnValue =
|
||||
if (mode == ResultMode.LAST) {
|
||||
if (internalResultIsLeaf(resultIndex) || method == "cmd")
|
||||
res.copy() else ProvResult(res.success)
|
||||
} else if (mode == ResultMode.ALL) {
|
||||
// leaf
|
||||
if (internalResultIsLeaf(resultIndex)) res.copy()
|
||||
// evaluate subcalls' results
|
||||
else ProvResult(cumulativeSuccessSublevel(resultIndex) ?: false)
|
||||
} else if (mode == ResultMode.NONE) {
|
||||
ProvResult(true)
|
||||
} else if (mode == ResultMode.FAILEXIT) {
|
||||
if (res.success) {
|
||||
return ProvResult(true)
|
||||
} else {
|
||||
exit = true
|
||||
return ProvResult(false)
|
||||
}
|
||||
} else {
|
||||
ProvResult(false, err = "mode unknown")
|
||||
}
|
||||
|
||||
previousLevel = level
|
||||
|
||||
internalResults[resultIndex].provResult = returnValue
|
||||
|
||||
if (level == 0) {
|
||||
ProgressBar.end()
|
||||
processor.close()
|
||||
printResults()
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
|
||||
private fun internalResultIsLeaf(resultIndex: Int) : Boolean {
|
||||
return !(resultIndex < internalResults.size - 1 && internalResults[resultIndex + 1].level > internalResults[resultIndex].level)
|
||||
}
|
||||
|
||||
|
||||
private fun cumulativeSuccessSublevel(resultIndex: Int) : Boolean? {
|
||||
val currentLevel = internalResults[resultIndex].level
|
||||
var res : Boolean? = null
|
||||
var i = resultIndex + 1
|
||||
while ( i < internalResults.size && internalResults[i].level > currentLevel) {
|
||||
if (internalResults[i].level == currentLevel + 1) {
|
||||
res =
|
||||
if (res == null) internalResults[i].provResult?.success else res && (internalResults[i].provResult?.success
|
||||
?: false)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
private data class InternalResult(val level: Int, val method: String?, var provResult: ProvResult?) {
|
||||
override fun toString() : String {
|
||||
val provResult = provResult
|
||||
if (provResult != null) {
|
||||
return prefix(level) + (if (provResult.success) "Success -- " else "FAILED -- ") +
|
||||
method + " " + (provResult.cmd ?: "") +
|
||||
(if (!provResult.success && provResult.err != null) " -- Error: " + provResult.err.escapeNewline() else "") }
|
||||
else
|
||||
return prefix(level) + " " + method + " " + "... in progress ... "
|
||||
|
||||
}
|
||||
|
||||
private fun prefix(level: Int): String {
|
||||
return "---".repeat(level) + "> "
|
||||
}
|
||||
}
|
||||
|
||||
val ANSI_RESET = "\u001B[0m"
|
||||
val ANSI_BRIGHT_RED = "\u001B[91m"
|
||||
val ANSI_BRIGHT_GREEN = "\u001B[92m"
|
||||
// uncomment if needed
|
||||
// val ANSI_BLACK = "\u001B[30m"
|
||||
// val ANSI_RED = "\u001B[31m"
|
||||
// val ANSI_GREEN = "\u001B[32m"
|
||||
// val ANSI_YELLOW = "\u001B[33m"
|
||||
// val ANSI_BLUE = "\u001B[34m"
|
||||
// val ANSI_PURPLE = "\u001B[35m"
|
||||
// val ANSI_CYAN = "\u001B[36m"
|
||||
// val ANSI_WHITE = "\u001B[37m"
|
||||
// val ANSI_GRAY = "\u001B[90m"
|
||||
|
||||
private fun printResults() {
|
||||
println("============================================== SUMMARY " + (if (name != null) "(" + name + ") " else "") +
|
||||
"============================================== ")
|
||||
for (result in internalResults) {
|
||||
println(
|
||||
result.toString().escapeNewline().
|
||||
replace("Success --", ANSI_BRIGHT_GREEN + "Success" + ANSI_RESET + " --")
|
||||
.replace("FAILED --", ANSI_BRIGHT_RED + "FAILED" + ANSI_RESET + " --")
|
||||
)
|
||||
}
|
||||
if (internalResults.size > 1) {
|
||||
println("----------------------------------------------------------------------------------------------------- ")
|
||||
println(
|
||||
"Overall " + internalResults.get(0).toString().take(10)
|
||||
.replace("Success", ANSI_BRIGHT_GREEN + "Success" + ANSI_RESET)
|
||||
.replace("FAILED", ANSI_BRIGHT_RED + "FAILED" + ANSI_RESET)
|
||||
)
|
||||
}
|
||||
println("============================================ SUMMARY END ============================================ " + newline())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private object ProgressBar {
|
||||
fun init() {
|
||||
print("Processing started ...\n")
|
||||
}
|
||||
|
||||
fun progress() {
|
||||
print(".")
|
||||
System.out.flush()
|
||||
}
|
||||
|
||||
fun end() {
|
||||
println("processing completed.")
|
||||
}
|
||||
}
|
30
src/main/kotlin/io/provs/Results.kt
Normal file
30
src/main/kotlin/io/provs/Results.kt
Normal file
|
@ -0,0 +1,30 @@
|
|||
package io.provs
|
||||
|
||||
|
||||
data class ProvResult(val success: Boolean,
|
||||
val cmd: String? = null,
|
||||
val out: String? = null,
|
||||
val err: String? = null,
|
||||
val exception: Exception? = null,
|
||||
val exit: String? = null) {
|
||||
|
||||
constructor(returnCode : Int) : this(returnCode == 0)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ProvResult:: ${if (success) "Succeeded" else "FAILED"} -- ${if (!cmd.isNullOrEmpty()) "Name: " +
|
||||
cmd.escapeNewline() + ", " else ""}${if (!out.isNullOrEmpty()) "Details: $out" else ""}" +
|
||||
(exception?.run { " Exception: " + toString() } ?: "")
|
||||
}
|
||||
|
||||
fun toShortString() : String {
|
||||
return "ProvResult:: ${if (success) "Succeeded" else "FAILED"} -- ${if (!success && (out != null)) "Details: $out" else ""}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused") // might be used by custom methods
|
||||
data class TypedResult<T>(val success: Boolean, val resultObject: T? = null) {
|
||||
override fun toString(): String {
|
||||
return "TypedResult:: ${if (success) "Succeeded" else "FAILED"} -- Result object: " + resultObject?.run { toString().escapeNewline() }
|
||||
}
|
||||
}
|
14
src/main/kotlin/io/provs/Secret.kt
Normal file
14
src/main/kotlin/io/provs/Secret.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package io.provs
|
||||
|
||||
|
||||
open class Secret(private val value: String) {
|
||||
override fun toString(): String {
|
||||
return "********"
|
||||
}
|
||||
fun plain() : String {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Password(plainPassword: String) : Secret(plainPassword)
|
107
src/main/kotlin/io/provs/Utils.kt
Normal file
107
src/main/kotlin/io/provs/Utils.kt
Normal file
|
@ -0,0 +1,107 @@
|
|||
package io.provs
|
||||
|
||||
import io.provs.docker.provideContainer
|
||||
import io.provs.processors.ContainerStartMode
|
||||
import io.provs.processors.ContainerUbuntuHostProcessor
|
||||
import io.provs.processors.RemoteProcessor
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Returns the name of the calling function but excluding some functions of the prov framework
|
||||
* in order to return the "real" calling function.
|
||||
* Note: names of inner functions (i.e. which are defined inside other functions) are not
|
||||
* supported in the sense that always the name of the outer function is returned instead.
|
||||
*/
|
||||
fun getCallingMethodName(): String? {
|
||||
val offsetVal = 1
|
||||
val exclude = arrayOf("def", "record", "invoke", "invoke0", "handle", "def\$default", "addResultToEval")
|
||||
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
|
||||
val suffixes = arrayOf("optional", "requireAll", "requireLast", "inContainer")
|
||||
|
||||
var suffix = ""
|
||||
val callingFrame = Thread.currentThread().stackTrace
|
||||
for (i in 0 until (callingFrame.size - 1)) {
|
||||
if (callingFrame[i].methodName == "getCallingMethodName") {
|
||||
var method = callingFrame[i + offsetVal].methodName
|
||||
var inc = 0
|
||||
while ((method in exclude) or (method in suffixes)) {
|
||||
if (method in suffixes && suffix == "") {
|
||||
suffix = method
|
||||
}
|
||||
inc++
|
||||
method = callingFrame[i + offsetVal + inc].methodName
|
||||
}
|
||||
return method + if (suffix.isBlank()) "" else " ($suffix)"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun String.escapeNewline(): String = this.replace("\r\n", "\\n").replace("\n", "\\n")
|
||||
fun String.escapeBackslash(): String = this.replace("\\", "\\\\")
|
||||
fun String.escapeDoubleQuote(): String = this.replace("\"", "\\\"")
|
||||
fun String.escapeSingleQuote(): String = this.replace("'", "\'")
|
||||
fun String.escapeSingleQuoteForShell(): String = this.replace("'", "'\"'\"'")
|
||||
fun String.escapeProcentForPrintf(): String = this.replace("%", "%%")
|
||||
|
||||
// see https://www.shellscript.sh/escape.html
|
||||
fun String.escapeAndEncloseByDoubleQuoteForShell(): String {
|
||||
return "\"" + this.escapeBackslash().replace("`", "\\`").escapeDoubleQuote().replace("$", "\\$") + "\""
|
||||
}
|
||||
|
||||
fun hostUserHome(): String = System.getProperty("user.home") + fileSeparator()
|
||||
fun newline(): String = System.getProperty("line.separator")
|
||||
fun fileSeparator(): String = File.separator
|
||||
|
||||
|
||||
/**
|
||||
* Returns default local Prov instance.
|
||||
*/
|
||||
@Suppress("unused") // used by other libraries resp. KotlinScript
|
||||
fun local(): Prov {
|
||||
return Prov.defaultInstance()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns Prov instance for remote host with remote user with provided password.
|
||||
* If password is null, connection is done by ssh-key.
|
||||
* Platform (Linux, Windows) must be provided if different from local platform.
|
||||
*/
|
||||
@Suppress("unused") // used by other libraries resp. KotlinScript
|
||||
fun remote(host: String, remoteUser: String, password: Secret? = null, platform: String? = null): Prov {
|
||||
return Prov.newInstance(RemoteProcessor(host, remoteUser, password), platform)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns Prov instance running in a local docker container with name containerName.
|
||||
* A potentially existing container with the same name is reused by default resp. if
|
||||
* parameter useExistingContainer is set to true.
|
||||
* If a new container needs to be created, on Linux systems the image _ubuntu_ is used.
|
||||
*/
|
||||
@Suppress("unused") // used by other libraries resp. KotlinScript
|
||||
fun docker(containerName: String = "provs_default", useExistingContainer: Boolean = true): Prov {
|
||||
|
||||
val os = System.getProperty("os.name")
|
||||
|
||||
if ("Linux".equals(os)) {
|
||||
val defaultDockerImage = "ubuntu"
|
||||
|
||||
local().provideContainer(containerName, defaultDockerImage)
|
||||
|
||||
return Prov.newInstance(
|
||||
ContainerUbuntuHostProcessor(
|
||||
containerName,
|
||||
defaultDockerImage,
|
||||
if (useExistingContainer)
|
||||
ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
||||
else
|
||||
ContainerStartMode.CREATE_NEW_KILL_EXISTING
|
||||
)
|
||||
)
|
||||
} else {
|
||||
throw RuntimeException("ERROR: method docker() is currently not supported for " + os)
|
||||
}
|
||||
}
|
87
src/main/kotlin/io/provs/docker/HostDocker.kt
Normal file
87
src/main/kotlin/io/provs/docker/HostDocker.kt
Normal file
|
@ -0,0 +1,87 @@
|
|||
package io.provs.docker
|
||||
|
||||
import io.provs.Prov
|
||||
import io.provs.ProvResult
|
||||
import io.provs.docker.images.DockerImage
|
||||
import io.provs.docker.platforms.*
|
||||
import io.provs.platforms.UbuntuProv
|
||||
import io.provs.processors.ContainerStartMode
|
||||
|
||||
|
||||
/**
|
||||
* Creates and runs a new container with name _containerName_ for image _imageName_ if not yet existing.
|
||||
* In case the container already exists, the parameter _startMode_ determines
|
||||
* if the running container is just kept (default behavior)
|
||||
* or if the running container is stopped and removed and a new container is created
|
||||
* or if the method results in a failure result.
|
||||
*/
|
||||
fun Prov.provideContainer(
|
||||
containerName: String,
|
||||
imageName: String = "ubuntu",
|
||||
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
||||
) : ProvResult {
|
||||
if (this is UbuntuProv) {
|
||||
return this.provideContainerPlatform(containerName, imageName, startMode)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.containerRuns(containerName: String) : Boolean {
|
||||
if (this is UbuntuProv) {
|
||||
return this.containerRunsPlatform(containerName)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.runContainer(
|
||||
containerName: String = "defaultProvContainer",
|
||||
imageName: String = "ubuntu"
|
||||
) : ProvResult {
|
||||
if (this is UbuntuProv) {
|
||||
return this.runContainerPlatform(containerName, imageName)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.containerSh(containerName: String, cmd: String) : ProvResult {
|
||||
if (this is UbuntuProv) {
|
||||
return this.containerShPlatform(containerName, cmd)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.dockerBuildImage(image: DockerImage, skipIfExisting: Boolean = true) : ProvResult {
|
||||
if (this is UbuntuProv) {
|
||||
return this.dockerBuildImagePlatform(image, skipIfExisting)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.dockerImageExists(imageName: String) : Boolean {
|
||||
if (this is UbuntuProv) {
|
||||
return this.dockerImageExistsPlatform(imageName)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.exitAndRmContainer(
|
||||
containerName: String
|
||||
) : ProvResult {
|
||||
if (this is UbuntuProv) {
|
||||
return this.exitAndRmContainerPlatform(containerName)
|
||||
} else {
|
||||
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||
}
|
||||
}
|
34
src/main/kotlin/io/provs/docker/images/DockerImages.kt
Normal file
34
src/main/kotlin/io/provs/docker/images/DockerImages.kt
Normal file
|
@ -0,0 +1,34 @@
|
|||
package io.provs.docker.images
|
||||
|
||||
|
||||
interface DockerImage {
|
||||
fun imageName() : String
|
||||
fun imageText() : String
|
||||
}
|
||||
|
||||
/**
|
||||
* Dockerfile based ubuntu image added by a non-root default user.
|
||||
*/
|
||||
@Suppress("unused") // used by other libraries
|
||||
class UbuntuPlusUser(private val userName: String = "testuser") : DockerImage {
|
||||
|
||||
override fun imageName(): String {
|
||||
return "ubuntu_plus_user"
|
||||
}
|
||||
|
||||
override fun imageText(): String {
|
||||
return """
|
||||
FROM ubuntu:18.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get -y install sudo
|
||||
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && adduser $userName sudo
|
||||
RUN echo "$userName ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$userName
|
||||
|
||||
USER $userName
|
||||
CMD /bin/bash
|
||||
WORKDIR /home/$userName
|
||||
"""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.provs.docker.platforms
|
||||
|
||||
import io.provs.*
|
||||
import io.provs.docker.containerRuns
|
||||
import io.provs.docker.dockerImageExists
|
||||
import io.provs.docker.exitAndRmContainer
|
||||
import io.provs.docker.images.DockerImage
|
||||
import io.provs.platforms.UbuntuProv
|
||||
import io.provs.processors.ContainerStartMode
|
||||
|
||||
|
||||
fun UbuntuProv.provideContainerPlatform(
|
||||
containerName: String,
|
||||
imageName: String = "ubuntu",
|
||||
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
||||
): ProvResult = requireLast {
|
||||
if (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING) {
|
||||
exitAndRmContainer(containerName)
|
||||
}
|
||||
if ((startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING) || (startMode == ContainerStartMode.CREATE_NEW_FAIL_IF_EXISTING)) {
|
||||
if (!cmd(
|
||||
"sudo docker run -dit --name=$containerName $imageName"
|
||||
).success
|
||||
) {
|
||||
throw RuntimeException("could not start docker")
|
||||
}
|
||||
} else if (startMode == ContainerStartMode.USE_RUNNING_ELSE_CREATE) {
|
||||
val r =
|
||||
cmd("sudo docker inspect -f '{{.State.Running}}' $containerName")
|
||||
if (!r.success || "false\n" == r.out) {
|
||||
cmd("sudo docker rm -f $containerName")
|
||||
cmd("sudo docker run -dit --name=$containerName $imageName")
|
||||
}
|
||||
}
|
||||
ProvResult(containerRuns(containerName))
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.containerRunsPlatform(containerName: String): Boolean {
|
||||
return cmdNoEval("sudo docker inspect -f '{{.State.Running}}' $containerName").out?.equals("true\n") ?: false
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.runContainerPlatform(
|
||||
containerName: String = "defaultProvContainer",
|
||||
imageName: String = "ubuntu"
|
||||
) = def {
|
||||
cmd("sudo docker run -dit --name=$containerName $imageName")
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.containerExecPlatform(containerName: String, cmd: String) = def {
|
||||
cmd("sudo docker exec $containerName $cmd")
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.containerShPlatform(containerName: String, cmd: String) = def {
|
||||
containerExecPlatform(containerName, "sh -c \"${cmd.escapeDoubleQuote()}\"")
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.dockerBuildImagePlatform(image: DockerImage, skipIfExisting: Boolean): ProvResult {
|
||||
|
||||
if (skipIfExisting && dockerImageExists(image.imageName())) {
|
||||
return ProvResult(true)
|
||||
}
|
||||
|
||||
val path = hostUserHome() + "tmp_docker_img" + fileSeparator()
|
||||
|
||||
if (!xec("test", "-d", path).success) {
|
||||
cmd("cd ${hostUserHome()} && mkdir tmp_docker_img")
|
||||
}
|
||||
|
||||
cmd("cd $path && printf '${image.imageText().escapeSingleQuote()}' > Dockerfile")
|
||||
|
||||
return cmd("cd $path && sudo docker build --tag ${image.imageName()} .")
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.dockerImageExistsPlatform(imageName: String): Boolean {
|
||||
return (cmd("sudo docker images $imageName -q").out != "")
|
||||
}
|
||||
|
||||
|
||||
fun UbuntuProv.exitAndRmContainerPlatform(
|
||||
containerName: String
|
||||
) = requireAll {
|
||||
if (containerRuns(containerName)) {
|
||||
cmd("sudo docker stop $containerName")
|
||||
}
|
||||
cmd("sudo docker rm $containerName")
|
||||
}
|
37
src/main/kotlin/io/provs/entry/Entry.kt
Normal file
37
src/main/kotlin/io/provs/entry/Entry.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
package io.provs.entry
|
||||
|
||||
/**
|
||||
* Calls a static method of a class.
|
||||
* Only methods are supported with either no parameters or with one vararg parameter of type String.
|
||||
* Methods with a vararg parameter must be called with at least one argument.
|
||||
*
|
||||
* @param args specify class and (optionally) method and parameters, in detail:
|
||||
* @param args[0] class to be called
|
||||
* @param args[1] (optional) static method of the class; if not specified, the "main" method is used
|
||||
* @param args[2...] String parameters that are passed to the method, only applicable if method has vararg parameter
|
||||
*/
|
||||
fun main(vararg args: String) {
|
||||
|
||||
if (args.isNotEmpty()) {
|
||||
val className = args[0]
|
||||
|
||||
val jClass = Class.forName(className)
|
||||
|
||||
val parameterTypeStringArray = arrayOf<Class<*>>(
|
||||
Array<String>::class.java
|
||||
)
|
||||
val method = if (args.size == 1) jClass.getMethod("main", *parameterTypeStringArray) else
|
||||
(if (args.size == 2 && args[1] != "main") jClass.getMethod(args[1])
|
||||
else jClass.getMethod(args[1], *parameterTypeStringArray))
|
||||
|
||||
if ((args.size == 1) || (args.size == 2 && args[1] == "main")) {
|
||||
method.invoke(null, emptyArray<String>())
|
||||
} else if (args.size == 2) {
|
||||
method.invoke(null)
|
||||
} else {
|
||||
method.invoke(null, args.drop(2).toTypedArray())
|
||||
}
|
||||
} else {
|
||||
println("Usage: <packageName.className> <static methodName>")
|
||||
}
|
||||
}
|
24
src/main/kotlin/io/provs/platforms/UbuntuProv.kt
Normal file
24
src/main/kotlin/io/provs/platforms/UbuntuProv.kt
Normal file
|
@ -0,0 +1,24 @@
|
|||
package io.provs.platforms
|
||||
|
||||
import io.provs.Prov
|
||||
import io.provs.ProvResult
|
||||
import io.provs.processors.LocalProcessor
|
||||
import io.provs.processors.Processor
|
||||
|
||||
const val SHELL = "/bin/bash" // could be changed to another shell like "sh", "/bin/csh" if required
|
||||
|
||||
|
||||
class UbuntuProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null) : Prov (processor, name) {
|
||||
|
||||
override fun cmd(cmd: String, dir: String?) : ProvResult = def {
|
||||
xec(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||
}
|
||||
|
||||
override fun cmdNoLog(cmd: String, dir: String?) : ProvResult {
|
||||
return xecNoLog(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||
}
|
||||
|
||||
override fun cmdNoEval(cmd: String, dir: String?) : ProvResult {
|
||||
return xec(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||
}
|
||||
}
|
25
src/main/kotlin/io/provs/platforms/WinProv.kt
Normal file
25
src/main/kotlin/io/provs/platforms/WinProv.kt
Normal file
|
@ -0,0 +1,25 @@
|
|||
package io.provs.platforms
|
||||
|
||||
import io.provs.Prov
|
||||
import io.provs.ProvResult
|
||||
import io.provs.processors.LocalProcessor
|
||||
import io.provs.processors.Processor
|
||||
|
||||
|
||||
class WinProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null) : Prov (processor, name) {
|
||||
|
||||
// todo put cmd.exe in variable SHELL
|
||||
|
||||
override fun cmd(cmd: String, dir: String?) : ProvResult = def {
|
||||
xec("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||
}
|
||||
|
||||
override fun cmdNoLog(cmd: String, dir: String?) : ProvResult = def {
|
||||
xecNoLog("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||
}
|
||||
|
||||
|
||||
override fun cmdNoEval(cmd: String, dir: String?) : ProvResult {
|
||||
return xec("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package io.provs.processors
|
||||
|
||||
import io.provs.Prov
|
||||
import io.provs.docker.containerSh
|
||||
import io.provs.docker.provideContainer
|
||||
import io.provs.escapeAndEncloseByDoubleQuoteForShell
|
||||
import io.provs.platforms.SHELL
|
||||
|
||||
enum class ContainerStartMode {
|
||||
USE_RUNNING_ELSE_CREATE,
|
||||
CREATE_NEW_KILL_EXISTING,
|
||||
CREATE_NEW_FAIL_IF_EXISTING
|
||||
}
|
||||
|
||||
enum class ContainerEndMode {
|
||||
EXIT_AND_REMOVE,
|
||||
KEEP_RUNNING
|
||||
}
|
||||
|
||||
open class ContainerUbuntuHostProcessor(
|
||||
private val containerName: String = "default_provs_container",
|
||||
@Suppress("unused") // suppress false positive warning
|
||||
private val dockerImage: String = "ubuntu",
|
||||
@Suppress("unused") // suppress false positive warning
|
||||
private val startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||
private val endMode: ContainerEndMode = ContainerEndMode.KEEP_RUNNING
|
||||
) : Processor {
|
||||
private var localExecution = LocalProcessor()
|
||||
private var a = Prov.newInstance(name = "ContainerUbuntuHostProcessor")
|
||||
|
||||
init {
|
||||
val r = a.provideContainer(containerName, dockerImage, startMode)
|
||||
if (!r.success)
|
||||
throw RuntimeException("Could not start docker image: " + r.toShortString(), r.exception)
|
||||
}
|
||||
|
||||
override fun x(vararg args: String): ProcessResult {
|
||||
return localExecution.x("sh", "-c", "sudo docker exec $containerName " + buildCommand(*args))
|
||||
}
|
||||
|
||||
override fun xNoLog(vararg args: String): ProcessResult {
|
||||
return localExecution.xNoLog("sh", "-c", "sudo docker exec $containerName " + buildCommand(*args))
|
||||
}
|
||||
|
||||
fun installSudo(): ContainerUbuntuHostProcessor {
|
||||
a.containerSh(containerName, "apt-get update")
|
||||
a.containerSh(containerName, "apt-get -y install sudo")
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAndSwitchToUser(user: String = "testuser"): ContainerUbuntuHostProcessor {
|
||||
|
||||
a.containerSh(containerName,"sudo useradd -m $user && echo '$user:$user' | chpasswd && adduser $user sudo")
|
||||
a.containerSh(containerName,"echo '$user ALL=(ALL:ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/$user")
|
||||
a.containerSh(containerName,"sudo su $user")
|
||||
a.containerSh(containerName,"cd /home/$user")
|
||||
a.containerSh(containerName,"mkdir $user && cd $user")
|
||||
return this
|
||||
}
|
||||
|
||||
fun exitAndRm() {
|
||||
localExecution.x(SHELL, "-c", "sudo docker stop $containerName")
|
||||
localExecution.x(SHELL, "-c", "sudo docker rm $containerName")
|
||||
}
|
||||
|
||||
private fun quoteString(s: String): String {
|
||||
return s.escapeAndEncloseByDoubleQuoteForShell()
|
||||
}
|
||||
|
||||
private fun buildCommand(vararg args: String) : String {
|
||||
return if (args.size == 1) quoteString(args[0]) else
|
||||
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) SHELL + " -c " + quoteString(args[2])
|
||||
else args.joinToString(separator = " ")
|
||||
}
|
||||
|
||||
protected fun finalize() {
|
||||
if (endMode == ContainerEndMode.EXIT_AND_REMOVE) {
|
||||
exitAndRm()
|
||||
}
|
||||
}
|
||||
}
|
77
src/main/kotlin/io/provs/processors/LocalProcessor.kt
Normal file
77
src/main/kotlin/io/provs/processors/LocalProcessor.kt
Normal file
|
@ -0,0 +1,77 @@
|
|||
package io.provs.processors
|
||||
|
||||
import io.provs.escapeNewline
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
|
||||
private fun getOsName(): String {
|
||||
return System.getProperty("os.name")
|
||||
}
|
||||
|
||||
open class LocalProcessor : Processor {
|
||||
|
||||
companion object {
|
||||
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
|
||||
|
||||
var charset = if (getOsName().contains("Windows")) Charset.forName("Windows-1252") else Charset.defaultCharset()
|
||||
init {
|
||||
log.info("os.name: " + getOsName())
|
||||
log.info("user.home: " + System.getProperty("user.home"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun workingDir() : String
|
||||
{
|
||||
return System.getProperty("user.home") ?: File.separator
|
||||
}
|
||||
|
||||
override fun x(vararg args: String): ProcessResult {
|
||||
return execute(true, *args)
|
||||
}
|
||||
|
||||
|
||||
override fun xNoLog(vararg args: String): ProcessResult {
|
||||
return execute(false, *args)
|
||||
}
|
||||
|
||||
private fun execute(logging: Boolean, vararg args: String): ProcessResult {
|
||||
try {
|
||||
var prefix = "******************** Prov: "
|
||||
if (logging) {
|
||||
for (arg in args) {
|
||||
prefix += " \"${arg.escapeNewline()}\""
|
||||
}
|
||||
} else {
|
||||
prefix += "\"xxxxxxxx\""
|
||||
}
|
||||
log.info(prefix)
|
||||
|
||||
val proc = ProcessBuilder(args.toList())
|
||||
.directory(File(workingDir()))
|
||||
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||
.redirectError(ProcessBuilder.Redirect.PIPE)
|
||||
.start()
|
||||
|
||||
val c = proc.waitFor()
|
||||
|
||||
val r = ProcessResult(
|
||||
c,
|
||||
proc.inputStream.bufferedReader(charset).readText(),
|
||||
proc.errorStream.bufferedReader(charset).readText(),
|
||||
args = args
|
||||
)
|
||||
if (logging) {
|
||||
log.info(r.toString())
|
||||
}
|
||||
return r
|
||||
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
return ProcessResult(-1, ex = e)
|
||||
}
|
||||
}
|
||||
}
|
19
src/main/kotlin/io/provs/processors/PrintOnlyProcessor.kt
Normal file
19
src/main/kotlin/io/provs/processors/PrintOnlyProcessor.kt
Normal file
|
@ -0,0 +1,19 @@
|
|||
package io.provs.processors
|
||||
|
||||
|
||||
class PrintOnlyProcessor : Processor {
|
||||
|
||||
override fun x(vararg args: String): ProcessResult
|
||||
{
|
||||
print("PrintOnlyProcessor >>> ")
|
||||
for (n in args) print("\"$n\" ")
|
||||
println()
|
||||
return ProcessResult(0, args = args)
|
||||
}
|
||||
|
||||
override fun xNoLog(vararg args: String): ProcessResult
|
||||
{
|
||||
print("PrintOnlyProcessor >>> ********")
|
||||
return ProcessResult(0, args = args)
|
||||
}
|
||||
}
|
54
src/main/kotlin/io/provs/processors/Processor.kt
Normal file
54
src/main/kotlin/io/provs/processors/Processor.kt
Normal file
|
@ -0,0 +1,54 @@
|
|||
package io.provs.processors
|
||||
|
||||
|
||||
interface Processor {
|
||||
fun x(vararg args: String): ProcessResult
|
||||
fun xNoLog(vararg args: String): ProcessResult
|
||||
fun close() {}
|
||||
}
|
||||
|
||||
|
||||
data class ProcessResult(val exitCode: Int, val out: String? = null, val err: String? = null, val ex: Exception? = null, val args: Array<out String> = emptyArray()) {
|
||||
|
||||
private fun success(): Boolean {
|
||||
return (exitCode == 0)
|
||||
}
|
||||
|
||||
fun argsToString() : String {
|
||||
return args.joinToString(
|
||||
separator = ", ",
|
||||
prefix = "[",
|
||||
postfix = "]",
|
||||
limit = 4,
|
||||
truncated = " ..."
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "--->>> ProcessResult: ${if (success()) "Succeeded" else "FAILED"} -- Code: $exitCode, ${if (!out.isNullOrEmpty()) "Out: $out, " else ""}${if (!err.isNullOrEmpty()) "Err: $err" else ""}" + argsToString()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ProcessResult
|
||||
|
||||
if (exitCode != other.exitCode) return false
|
||||
if (out != other.out) return false
|
||||
if (err != other.err) return false
|
||||
if (ex != other.ex) return false
|
||||
if (!args.contentEquals(other.args)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = exitCode
|
||||
result = 31 * result + (out?.hashCode() ?: 0)
|
||||
result = 31 * result + (err?.hashCode() ?: 0)
|
||||
result = 31 * result + ex.hashCode()
|
||||
result = 31 * result + args.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
127
src/main/kotlin/io/provs/processors/RemoteUbuntuProcessor.kt
Normal file
127
src/main/kotlin/io/provs/processors/RemoteUbuntuProcessor.kt
Normal file
|
@ -0,0 +1,127 @@
|
|||
package io.provs.processors
|
||||
|
||||
import io.provs.Secret
|
||||
import io.provs.escapeAndEncloseByDoubleQuoteForShell
|
||||
import io.provs.escapeNewline
|
||||
import io.provs.platforms.SHELL
|
||||
import net.schmizz.sshj.SSHClient
|
||||
import net.schmizz.sshj.connection.channel.direct.Session
|
||||
import net.schmizz.sshj.connection.channel.direct.Session.Command
|
||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class RemoteProcessor(ip: String, user: String, password: Secret? = null) : Processor {
|
||||
|
||||
companion object {
|
||||
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
|
||||
}
|
||||
|
||||
private val ssh = SSHClient()
|
||||
|
||||
init {
|
||||
try {
|
||||
log.info("Connecting to $ip with user: $user with " + if (password != null) "password" else "ssh-key")
|
||||
|
||||
ssh.loadKnownHosts()
|
||||
|
||||
// todo: replace PromiscuousVerifier by more secure solution
|
||||
ssh.addHostKeyVerifier(PromiscuousVerifier())
|
||||
ssh.connect(ip)
|
||||
|
||||
if (password != null) {
|
||||
ssh.authPassword(user, password.plain())
|
||||
} else {
|
||||
val base = System.getProperty("user.home") + File.separator + ".ssh" + File.separator
|
||||
ssh.authPublickey(user, base + "id_rsa", base + "id_dsa", base + "id_ed25519", base + "id_ecdsa")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
ssh.disconnect()
|
||||
} finally {
|
||||
log.error("Got exception when initializing ssh: " + e.message)
|
||||
throw RuntimeException("Error when initializing ssh", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun x(vararg args: String): ProcessResult {
|
||||
return execute(true, *args)
|
||||
}
|
||||
|
||||
override fun xNoLog(vararg args: String): ProcessResult {
|
||||
return execute(false, *args)
|
||||
}
|
||||
|
||||
private fun execute(logging: Boolean, vararg args: String): ProcessResult {
|
||||
var prefix = "******************** Prov: "
|
||||
if (logging) {
|
||||
for (arg in args) {
|
||||
prefix += " \"${arg.escapeNewline()}\""
|
||||
}
|
||||
} else {
|
||||
prefix += "\"xxxxxxxx\""
|
||||
}
|
||||
log.info(prefix)
|
||||
|
||||
val cmdString: String =
|
||||
if (args.size == 1)
|
||||
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
||||
else
|
||||
if (args.size == 3 && SHELL == args[0] && "-c" == args[1])
|
||||
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
||||
else
|
||||
args.joinToString(separator = " ")
|
||||
|
||||
|
||||
var session: Session? = null
|
||||
|
||||
try {
|
||||
session = ssh.startSession()
|
||||
|
||||
val cmd: Command = session!!.exec(cmdString)
|
||||
val out = BufferedReader(InputStreamReader(cmd.inputStream)).use { it.readText() }
|
||||
val err = BufferedReader(InputStreamReader(cmd.errorStream)).use { it.readText() }
|
||||
cmd.join(100, TimeUnit.SECONDS)
|
||||
|
||||
val cmdRes = ProcessResult(cmd.exitStatus, out, err, args = args)
|
||||
if (logging) {
|
||||
log.info(cmdRes.toString())
|
||||
}
|
||||
session.close()
|
||||
|
||||
return cmdRes
|
||||
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
session?.close()
|
||||
} finally {
|
||||
// nothing to do
|
||||
}
|
||||
return ProcessResult(
|
||||
-1,
|
||||
err = "Error when opening or executing remote ssh session (Pls check host, user, password resp. ssh key) - ",
|
||||
ex = e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
log.info("Disconnecting ssh.")
|
||||
ssh.disconnect()
|
||||
} catch (e: IOException) {
|
||||
// No prov required
|
||||
}
|
||||
}
|
||||
|
||||
protected fun finalize() {
|
||||
close()
|
||||
}
|
||||
}
|
34
src/main/resources/logback.xml
Normal file
34
src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<configuration>
|
||||
|
||||
<timestamp key="byTime" datePattern="yyyyMMdd'T'HHmmss"/>
|
||||
|
||||
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{35}) - %msg %n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>./logs/provs-${byTime}.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>./logs/provs-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
<maxHistory>3</maxHistory>
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<append>true</append>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDERR" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
45
src/test/kotlin/io/provs/ContainerProcessorTest.kt
Normal file
45
src/test/kotlin/io/provs/ContainerProcessorTest.kt
Normal file
|
@ -0,0 +1,45 @@
|
|||
package io.provs
|
||||
|
||||
import io.provs.processors.ContainerStartMode
|
||||
import io.provs.processors.ContainerUbuntuHostProcessor
|
||||
import io.provs.testconfig.tags.CONTAINERTEST
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
internal class ContainerProcessorTest {
|
||||
|
||||
@Test
|
||||
@Tag(CONTAINERTEST)
|
||||
fun cmd_works_with_echo() {
|
||||
// given
|
||||
val prov = Prov.newInstance(ContainerUbuntuHostProcessor("provs_test", startMode = ContainerStartMode.CREATE_NEW_KILL_EXISTING))
|
||||
val text = "abc123!§$%&/#äöü"
|
||||
|
||||
// when
|
||||
val res = prov.cmd("echo '${text}'")
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
assertEquals(text + newline(), res.out)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Tag(CONTAINERTEST)
|
||||
fun cmdNoLog_works_with_echo() {
|
||||
// given
|
||||
val prov = Prov.newInstance(ContainerUbuntuHostProcessor("provs_test", startMode = ContainerStartMode.CREATE_NEW_KILL_EXISTING))
|
||||
val text = "abc123!§$%&/#äöü"
|
||||
|
||||
// when
|
||||
val res = prov.cmdNoLog("echo '${text}'")
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
assertEquals(text + newline(), res.out)
|
||||
}
|
||||
}
|
91
src/test/kotlin/io/provs/LocalProcessorTest.kt
Normal file
91
src/test/kotlin/io/provs/LocalProcessorTest.kt
Normal file
|
@ -0,0 +1,91 @@
|
|||
package io.provs
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
|
||||
|
||||
internal class LocalProcessorTest {
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
fun cmd_with_printf_on_Linux() {
|
||||
// given
|
||||
val prov = Prov.newInstance()
|
||||
val text = "abc123!§\\\$%%&/\"\\äöü'"
|
||||
|
||||
// when
|
||||
val res = prov.cmd("printf '${text.replace("%", "%%").escapeSingleQuoteForShell()}'")
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
assert(res.out == text)//"abc123!§\\$%&/\"\\äöü'")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
fun cmd_with_nested_shell_and_printf_on_Linux() {
|
||||
// given
|
||||
val prov = Prov.newInstance()
|
||||
val text = "abc123!§\\$%%&/\"\\äöü'"
|
||||
|
||||
// when
|
||||
val res = prov.cmd("sh -c " + ("sh -c " + ("printf ${text.escapeProcentForPrintf().escapeAndEncloseByDoubleQuoteForShell()}").escapeAndEncloseByDoubleQuoteForShell()).escapeAndEncloseByDoubleQuoteForShell())
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
assertEquals(text, res.out)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
fun cmd_with_echo_on_Windows() {
|
||||
// given
|
||||
val prov = Prov.newInstance()
|
||||
val text = "abc123!\"#"
|
||||
|
||||
// when
|
||||
val res = prov.cmd("echo $text")
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
assertEquals( text + "\r\n", res.out)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
fun cmdNoLog_linux() {
|
||||
// given
|
||||
val prov = Prov.newInstance()
|
||||
val text = "abc123!#"
|
||||
val osSpecificText = if (OS.WINDOWS.isCurrentOs) text else "'$text'"
|
||||
|
||||
|
||||
// when
|
||||
val res = prov.cmdNoLog("echo $osSpecificText")
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
assertEquals( text + System.lineSeparator(), res.out)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun cmd_forUnkownCommand_resultWithError() {
|
||||
// given
|
||||
val prov = Prov.newInstance()
|
||||
|
||||
// when
|
||||
val res = prov.cmd("iamanunknowncmd")
|
||||
|
||||
// then
|
||||
assert(!res.success)
|
||||
assert(res.out.isNullOrEmpty())
|
||||
assert(!res.err.isNullOrEmpty())
|
||||
}
|
||||
}
|
458
src/test/kotlin/io/provs/ProvTest.kt
Normal file
458
src/test/kotlin/io/provs/ProvTest.kt
Normal file
|
@ -0,0 +1,458 @@
|
|||
package io.provs
|
||||
|
||||
import io.provs.docker.provideContainer
|
||||
import io.provs.testconfig.tags.CONTAINERTEST
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
|
||||
|
||||
internal class ProvTest {
|
||||
|
||||
private fun Prov.def_returnungFalse() = def {
|
||||
ProvResult(false)
|
||||
}
|
||||
|
||||
private fun Prov.def_returningTrue() = def {
|
||||
ProvResult(true)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
fun cmd_onLinux() {
|
||||
// when
|
||||
val res = Prov.newInstance(name = "testing").cmd("echo --testing--").success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
@Tag(CONTAINERTEST)
|
||||
fun sh_onLinux() {
|
||||
// given
|
||||
val script = """
|
||||
# test some script commands
|
||||
|
||||
ping -c1 nu.nl
|
||||
echo something
|
||||
ping -c1 github.com
|
||||
"""
|
||||
|
||||
// when
|
||||
val res = Prov.newInstance(name = "testing").sh(script).success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
fun cmd_onWindows() {
|
||||
// when
|
||||
val res = Prov.newInstance(name = "testing").cmd("echo --testing--").success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
fun sh_onWindows() {
|
||||
// given
|
||||
val script = """
|
||||
# test some script commands
|
||||
|
||||
ping -n 1 nu.nl
|
||||
echo something
|
||||
ping -n 1 github.com
|
||||
"""
|
||||
|
||||
// when
|
||||
val res = Prov.newInstance(name = "testing").sh(script).success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_modeOptional_result_true() {
|
||||
// given
|
||||
fun Prov.tst_def() = optional {
|
||||
def_returnungFalse()
|
||||
def_returningTrue()
|
||||
def_returnungFalse()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def().success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_modeLast_result_true() {
|
||||
// given
|
||||
fun Prov.tst_def() = requireLast {
|
||||
def_returnungFalse()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def().success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_modeLast_result_false() {
|
||||
// given
|
||||
fun Prov.tst_def() = requireLast {
|
||||
def_returningTrue()
|
||||
def_returnungFalse()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def().success
|
||||
|
||||
// then
|
||||
assert(!res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_mode_ALL_result_true() {
|
||||
// given
|
||||
fun Prov.tst_def_all_true_mode_ALL() = requireAll {
|
||||
def_returningTrue()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def_all_true_mode_ALL().success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
// given
|
||||
fun Prov.tst_def_one_false_mode_ALL() = requireAll {
|
||||
def_returningTrue()
|
||||
def_returnungFalse()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_modeALL_resultFalse() {
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def_one_false_mode_ALL().success
|
||||
|
||||
// then
|
||||
assert(!res)
|
||||
}
|
||||
|
||||
// given
|
||||
fun Prov.tst_def_one_false_mode_ALL_nested() = requireAll {
|
||||
def_returningTrue()
|
||||
tst_def_one_false_mode_ALL()
|
||||
def_returningTrue()
|
||||
tst_ALL_returningTrue()
|
||||
}
|
||||
|
||||
// given
|
||||
fun Prov.tst_ALL_returningTrue() = requireAll {
|
||||
ProvResult(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_modeALLnested_resultFalse() {
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def_one_false_mode_ALL_nested().success
|
||||
|
||||
// then
|
||||
assert(!res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_mode_ALL_LAST_NONE_nested() {
|
||||
// given
|
||||
fun Prov.tst_def_last() = def {
|
||||
def_returningTrue()
|
||||
def_returnungFalse()
|
||||
}
|
||||
|
||||
fun Prov.tst_def_one_false_mode_ALL() = requireAll {
|
||||
tst_def_last()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def_one_false_mode_ALL().success
|
||||
|
||||
// then
|
||||
assert(!res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_mode_FAILEXIT_nested_false() {
|
||||
// given
|
||||
fun Prov.tst_def_failexit_inner() = exitOnFailure {
|
||||
def_returningTrue()
|
||||
def_returnungFalse()
|
||||
}
|
||||
|
||||
fun Prov.tst_def_failexit_outer() = exitOnFailure {
|
||||
tst_def_failexit_inner()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def_failexit_outer().success
|
||||
|
||||
// then
|
||||
assert(!res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_mode_FAILEXIT_nested_true() {
|
||||
// given
|
||||
fun Prov.tst_def_failexit_inner() = exitOnFailure {
|
||||
def_returningTrue()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
fun Prov.tst_def_failexit_outer() = exitOnFailure {
|
||||
tst_def_failexit_inner()
|
||||
def_returningTrue()
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_def_failexit_outer().success
|
||||
|
||||
// then
|
||||
assert(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun def_mode_multiple_nested() {
|
||||
// given
|
||||
fun Prov.tst_nested() = def {
|
||||
requireAll {
|
||||
def_returningTrue()
|
||||
def {
|
||||
def_returnungFalse()
|
||||
def_returningTrue()
|
||||
}
|
||||
def_returnungFalse()
|
||||
def_returningTrue()
|
||||
optional {
|
||||
def_returnungFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when
|
||||
val res = Prov.defaultInstance().tst_nested().success
|
||||
|
||||
// then
|
||||
assert(!res)
|
||||
}
|
||||
|
||||
|
||||
// given
|
||||
fun Prov.checkPrereq_evaluateToFailure() = requireLast {
|
||||
ProvResult(false, err = "This is a test error.")
|
||||
}
|
||||
|
||||
fun Prov.methodThatProvidesSomeOutput() = requireLast {
|
||||
|
||||
if (!checkPrereq_evaluateToFailure().success) {
|
||||
sh(
|
||||
"""
|
||||
echo -Start test-
|
||||
echo Some output
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
sh("echo -End test-")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runProv_printsCorrectOutput() {
|
||||
|
||||
// given
|
||||
val outContent = ByteArrayOutputStream()
|
||||
val errContent = ByteArrayOutputStream()
|
||||
val originalOut = System.out
|
||||
val originalErr = System.err
|
||||
|
||||
System.setOut(PrintStream(outContent))
|
||||
System.setErr(PrintStream(errContent))
|
||||
|
||||
// when
|
||||
local().methodThatProvidesSomeOutput()
|
||||
|
||||
// then
|
||||
System.setOut(originalOut)
|
||||
System.setErr(originalErr)
|
||||
|
||||
println(outContent.toString())
|
||||
|
||||
// todo : simplify next lines
|
||||
val expectedOutput = if (OS.WINDOWS.isCurrentOs) "\n" +
|
||||
"============================================== SUMMARY (default Instance) ============================================== \n" +
|
||||
"> Success -- methodThatProvidesSomeOutput (requireLast) \n" +
|
||||
"---> FAILED -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
||||
"---> Success -- sh \n" +
|
||||
"------> Success -- cmd [cmd.exe, /c, echo -Start test-]\n" +
|
||||
"------> Success -- cmd [cmd.exe, /c, echo Some output]\n" +
|
||||
"---> Success -- sh \n" +
|
||||
"------> Success -- cmd [cmd.exe, /c, echo -End test-]\n" +
|
||||
"============================================ SUMMARY END ============================================ \n"
|
||||
else if (OS.LINUX.isCurrentOs()) {
|
||||
"Processing started ...\n" +
|
||||
".......processing completed.\n" +
|
||||
"============================================== SUMMARY (default instance) ============================================== \n" +
|
||||
"> \u001B[92mSuccess\u001B[0m -- methodThatProvidesSomeOutput (requireLast) \n" +
|
||||
"---> \u001B[91mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
||||
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -Start test-]\n" +
|
||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo Some output]\n" +
|
||||
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
||||
"----------------------------------------------------------------------------------------------------- \n" +
|
||||
"Overall > \u001B[92mSuccess\u001B[0m\n" +
|
||||
"============================================ SUMMARY END ============================================ \n" +
|
||||
"\n"
|
||||
} else {
|
||||
"OS " + System.getProperty("os.name") + " not yet supported"
|
||||
}
|
||||
|
||||
assertEquals(expectedOutput, outContent.toString().replace("\r", ""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_returnsTrue() {
|
||||
// when
|
||||
val res = local().check("echo 123")
|
||||
|
||||
// then
|
||||
assertTrue(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_returnsFalse() {
|
||||
// when
|
||||
val res = local().check("cmddoesnotexist")
|
||||
|
||||
// then
|
||||
assertFalse(res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSecret_returnsSecret() {
|
||||
// when
|
||||
val res = local().getSecret("echo 123")
|
||||
|
||||
// then
|
||||
assertEquals("123", res?.plain()?.trim())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addResultToEval_success() {
|
||||
// given
|
||||
fun Prov.inner() {
|
||||
addResultToEval(ProvResult(true))
|
||||
}
|
||||
|
||||
fun Prov.outer() = requireAll {
|
||||
inner()
|
||||
ProvResult(true)
|
||||
}
|
||||
|
||||
// when
|
||||
val res = local().outer()
|
||||
|
||||
//then
|
||||
assertEquals(ProvResult(true), res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addResultToEval_failure() {
|
||||
// given
|
||||
fun Prov.inner() {
|
||||
addResultToEval(ProvResult(false))
|
||||
}
|
||||
|
||||
fun Prov.outer() = requireAll {
|
||||
inner()
|
||||
ProvResult(true)
|
||||
}
|
||||
|
||||
// when
|
||||
val res = local().outer()
|
||||
|
||||
//then
|
||||
assertEquals(ProvResult(false), res)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
@Tag(CONTAINERTEST)
|
||||
fun inContainer_locally() {
|
||||
// given
|
||||
val containerName = "provs_testing"
|
||||
local().provideContainer(containerName, "ubuntu")
|
||||
|
||||
fun Prov.inner() = def {
|
||||
cmd("echo in container")
|
||||
}
|
||||
|
||||
// then
|
||||
fun Prov.outer() = def {
|
||||
inContainer(containerName) {
|
||||
inner()
|
||||
cmd("echo testfile > testfile.txt")
|
||||
}
|
||||
}
|
||||
|
||||
val res = local().def { outer() }
|
||||
|
||||
// then
|
||||
assertEquals(true, res.success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
@Disabled // run manually after updating host and remoteUser
|
||||
fun inContainer_remotely() {
|
||||
// given
|
||||
val host = "192.168.56.135"
|
||||
val remoteUser = "az"
|
||||
|
||||
fun Prov.inner() = def {
|
||||
cmd("echo 'in testfile' > testfile.txt")
|
||||
}
|
||||
|
||||
// then
|
||||
val res = remote(host, remoteUser).def {
|
||||
inner() // executed on the remote host
|
||||
inContainer("prov_default") {
|
||||
inner() // executed in the container on the remote host
|
||||
}
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(true, res.success)
|
||||
}
|
||||
}
|
28
src/test/kotlin/io/provs/UtilsTest.kt
Normal file
28
src/test/kotlin/io/provs/UtilsTest.kt
Normal file
|
@ -0,0 +1,28 @@
|
|||
package io.provs
|
||||
|
||||
import io.provs.testconfig.tags.CONTAINERTEST
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class UtilsTest {
|
||||
|
||||
@Test
|
||||
fun test_getCallingMethodName() {
|
||||
// when
|
||||
val s = getCallingMethodName()
|
||||
|
||||
// then
|
||||
Assertions.assertEquals("test_getCallingMethodName", s)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag(CONTAINERTEST)
|
||||
fun test_docker() {
|
||||
// when
|
||||
val res = docker().cmd("echo")
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(true, res.success)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package io.provs.docker.platforms
|
||||
|
||||
import io.provs.ProvResult
|
||||
import io.provs.docker.containerRuns
|
||||
import io.provs.docker.exitAndRmContainer
|
||||
import io.provs.docker.runContainer
|
||||
import io.provs.local
|
||||
import io.provs.testconfig.tags.CONTAINERTEST
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
|
||||
internal class UbuntuHostDockerKtTest {
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
@Tag(CONTAINERTEST)
|
||||
fun runAndCheckAndExitContainer() {
|
||||
// when
|
||||
val containerName = "testContainer"
|
||||
val result = local().requireAll {
|
||||
runContainer(containerName)
|
||||
addResultToEval(ProvResult(containerRuns(containerName)))
|
||||
|
||||
exitAndRmContainer(containerName)
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(ProvResult(true), result)
|
||||
}
|
||||
}
|
18
src/test/kotlin/io/provs/entry/EntryTest.kt
Normal file
18
src/test/kotlin/io/provs/entry/EntryTest.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
package io.provs.entry
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
fun test() {
|
||||
println("test is fun")
|
||||
}
|
||||
|
||||
|
||||
internal class EntryKtTest {
|
||||
|
||||
@Test
|
||||
fun test_main_no_arg() {
|
||||
main("io.provs.entry.EntryTestKt", "test")
|
||||
}
|
||||
}
|
88
src/test/kotlin/io/provs/platformTest/UbuntuProvTests.kt
Normal file
88
src/test/kotlin/io/provs/platformTest/UbuntuProvTests.kt
Normal file
|
@ -0,0 +1,88 @@
|
|||
package io.provs.platformTest
|
||||
|
||||
import io.provs.Prov
|
||||
import io.provs.testconfig.tags.CONTAINERTEST
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
|
||||
internal class UbuntuProvTests {
|
||||
|
||||
private val prov = Prov.defaultInstance()
|
||||
|
||||
private fun ping(url: String) = prov.def {
|
||||
xec("ping", "-c", "4", url)
|
||||
}
|
||||
|
||||
private fun outerPing() = prov.def {
|
||||
ping("nu.nl")
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
@Tag(CONTAINERTEST)
|
||||
fun that_ping_works() {
|
||||
// when
|
||||
val res = outerPing()
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
fun that_cmd_works() {
|
||||
// given
|
||||
val a = Prov.defaultInstance()
|
||||
|
||||
// when
|
||||
val res1 = a.cmd("pwd")
|
||||
val dir = res1.out?.trim()
|
||||
val res2 = a.cmd("echo abc", dir)
|
||||
|
||||
// then
|
||||
assert(res1.success)
|
||||
assert(res2.success)
|
||||
assert(res2.out?.trim() == "abc")
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
fun that_nested_shells_work() {
|
||||
// given
|
||||
val a = Prov.defaultInstance()
|
||||
|
||||
// when
|
||||
val res1 = a.cmd("pwd")
|
||||
val dir = res1.out?.trim()
|
||||
val res2 = a.cmd("echo abc", dir)
|
||||
|
||||
// then
|
||||
assert(res1.success)
|
||||
assert(res2.success)
|
||||
assert(res2.out?.trim() == "abc")
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
@Tag(CONTAINERTEST)
|
||||
fun that_xec_works() {
|
||||
// given
|
||||
val a = Prov.defaultInstance()
|
||||
|
||||
// when
|
||||
val res1 = a.xec("/usr/bin/printf", "hi")
|
||||
val res2 = a.xec("/bin/ping", "-c", "2", "github.com")
|
||||
val res3 = a.xec("/bin/bash", "-c", "echo echoed")
|
||||
|
||||
// then
|
||||
assert(res1.success)
|
||||
assert(res1.out?.trim() == "hi")
|
||||
assert(res2.success)
|
||||
assert(res3.success)
|
||||
assert(res3.out?.trim() == "echoed")
|
||||
}
|
||||
|
||||
}
|
||||
|
37
src/test/kotlin/io/provs/platformTest/UbuntuProvUnitTest.kt
Normal file
37
src/test/kotlin/io/provs/platformTest/UbuntuProvUnitTest.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
package io.provs.platformTest
|
||||
|
||||
import io.provs.getCallingMethodName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
internal class UbuntuProvUnitTest {
|
||||
|
||||
// @Test
|
||||
// fun that_cond_executes_true_case() {
|
||||
// // given
|
||||
// val x = mockk<LocalProcessor>()
|
||||
// every { x.x(*anyVararg()) } returns ProcessResult(0)
|
||||
//
|
||||
// val a = Prov.newInstance(x,"Linux")
|
||||
//
|
||||
// // when
|
||||
// a.cond( { true }, { xec("doit") })
|
||||
// a.cond( { false }, { xec("dont") })
|
||||
//
|
||||
// // then
|
||||
// verify { x.x("doit") }
|
||||
// verify(exactly = 0) { x.x("dont") }
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun that_callingStack_works() {
|
||||
|
||||
// when
|
||||
val s = getCallingMethodName()
|
||||
|
||||
// then
|
||||
assert(s == "that_callingStack_works")
|
||||
}
|
||||
}
|
46
src/test/kotlin/io/provs/platformTest/WinProvTests.kt
Normal file
46
src/test/kotlin/io/provs/platformTest/WinProvTests.kt
Normal file
|
@ -0,0 +1,46 @@
|
|||
package io.provs.platformTest
|
||||
|
||||
import io.provs.Prov
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
|
||||
internal class WinProvTests {
|
||||
|
||||
private val prov = Prov.defaultInstance()
|
||||
|
||||
private fun ping(url: String) = prov.def {
|
||||
cmd("ping $url")
|
||||
}
|
||||
|
||||
private fun outerPing() = prov.def { ping("nu.nl") }
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
fun def_definesPing_function() {
|
||||
// when
|
||||
val res = outerPing()
|
||||
|
||||
// then
|
||||
assert(res.success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
fun cmd_executesCommand() {
|
||||
// given
|
||||
val a = Prov.defaultInstance()
|
||||
|
||||
// when
|
||||
val res1 = a.cmd("echo %cd%")
|
||||
val dir = res1.out?.trim()
|
||||
val res2 = a.cmd("echo abc", dir)
|
||||
|
||||
// then
|
||||
assert(res1.success)
|
||||
assert(res1.success)
|
||||
assertEquals( "abc", res2.out?.trim())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package io.provs.processors
|
||||
|
||||
import io.provs.platforms.SHELL
|
||||
import io.provs.testconfig.tags.CONTAINERTEST
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS.LINUX
|
||||
|
||||
|
||||
internal class ContainerUbuntuHostProcessorTest {
|
||||
|
||||
@Test
|
||||
@EnabledOnOs(LINUX)
|
||||
@Tag(CONTAINERTEST)
|
||||
fun test() {
|
||||
if (System.getProperty("os.name") == "Linux") {
|
||||
val processor = ContainerUbuntuHostProcessor("UbuntuHostContainerExecution", "ubuntu", ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
||||
processor.installSudo()
|
||||
processor.x(SHELL, "-c", "'cd /home && mkdir blabla'")
|
||||
processor.exitAndRm()
|
||||
}
|
||||
}
|
||||
}
|
3
src/test/kotlin/io/provs/testconfig/tags/Tags.kt
Normal file
3
src/test/kotlin/io/provs/testconfig/tags/Tags.kt
Normal file
|
@ -0,0 +1,3 @@
|
|||
package io.provs.testconfig.tags
|
||||
|
||||
const val CONTAINERTEST = "containertest"
|
19
src/test/resources/logback-test.xml
Normal file
19
src/test/resources/logback-test.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<configuration>
|
||||
|
||||
<timestamp key="byTime" datePattern="yyyyMMdd'T'HHmmss"/>
|
||||
|
||||
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{35}) - %msg %n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="net.schmizz" level="ERROR"/>
|
||||
|
||||
<root level="ALL">
|
||||
<appender-ref ref="STDERR" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
Loading…
Reference in a new issue