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