diff --git a/README.md b/README.md index b9fc282..093237d 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ The following properties are configurable: * ```storageEngine```: The name of the storage engine to use. Can be **'wiredTiger'** or **'mmapv1'** for MongoDB Community Edition (default is **'wiredTiger'** for Mongo 3.2 and later; otherwise it is **'mmapv1'**). Alternative distributions might support additional engines * ```storageLocation```: The directory location from where embedded Mongo will run, such as ```/tmp/storage``` (defaults to a java temp directory) * ```syncDelay```: The interval in seconds between fsync operations where mongod flushes its working memory to disk. See [syncdelay parameter](https://docs.mongodb.com/manual/reference/parameters/#param.syncdelay) for more information +* ```replicaSet```: Initializes a replica set with the specified name after mongod started (defaults to **null**) ### Tasks ### diff --git a/build.gradle b/build.gradle index dd89e37..260e6bb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import org.gradle.api.tasks.wrapper.Wrapper.DistributionType - buildscript { repositories { maven { @@ -9,7 +7,7 @@ buildscript { } plugins { - id 'com.gradle.plugin-publish' version '1.0.0' + id 'com.gradle.plugin-publish' version '1.2.1' id 'maven-publish' id 'groovy' id 'idea' @@ -19,34 +17,40 @@ plugins { } group = 'com.sourcemuse.gradle.plugin' -version = '2.0.1-SNAPSHOT' +version = '2.1.2-SNAPSHOT' -sourceCompatibility = 17 -targetCompatibility = 17 +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} repositories { mavenCentral() } dependencies { - implementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:3.4.6' - implementation 'org.mongodb:mongo-java-driver:3.12.11' + implementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:4.21.0' + // Using 4.7 for maximal compatibility back to mongo 2.6 see https://www.mongodb.com/docs/drivers/java/sync/current/compatibility/ + // On Ubuntu 22.04 some tests with mongo versions before 3.6 require libssl1.0.0 + // you can get it from https://old-releases.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.0.0_1.0.2g-1ubuntu13_amd64.deb + implementation 'org.mongodb:mongodb-driver-sync:4.7.2' - testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0', { + testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0', { exclude module: 'groovy-all' } testImplementation 'org.littleshoot:littleproxy:1.1.2' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' } -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from 'build/docs/javadoc' +tasks.register("javadocJar", Jar) { + dependsOn javadoc + archiveClassifier = 'javadoc' + from 'build/docs/javadoc' } -task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource +tasks.register("sourcesJar", Jar) { + archiveClassifier = 'sources' + from sourceSets.main.allSource } artifacts { @@ -64,7 +68,7 @@ publishing { name = 'Gradle Mongo Plugin' description = 'Gradle plugin for managing a local instance of MongoDb' url = 'https://github.com/sourcemuse/GradleMongoPlugin' - + licenses { license { name = 'The Apache License, Version 2.0' @@ -89,7 +93,7 @@ publishing { } } } - } + } repositories { maven { name = 'ossSonatype' @@ -106,18 +110,15 @@ signing { sign publishing.publications.mavenJava } -pluginBundle { - website = 'https://github.com/sourcemuse/GradleMongoPlugin' - vcsUrl = 'https://github.com/sourcemuse/GradleMongoPlugin' - tags = ['mongo', 'mongodb'] -} - gradlePlugin { + website = 'https://github.com/sourcemuse/GradleMongoPlugin' + vcsUrl = 'https://github.com/sourcemuse/GradleMongoPlugin' plugins { mongoPlugin { id = 'com.sourcemuse.mongo' displayName = 'Gradle Mongo plugin' description = 'Gradle plugin for running a managed instance of Mongo.' + tags = ['mongo', 'mongodb'] implementationClass = 'com.sourcemuse.gradle.plugin.flapdoodle.gradle.GradleMongoPlugin' } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738c..943f0cb 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 92f06b5..8b580b9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708f..65dcd68 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 +# +# https://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 -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # 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 +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # 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"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # 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 - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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 @@ -89,84 +140,105 @@ 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 +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac 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 +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# 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\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg 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")" +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d593..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@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 https://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 @@ -9,19 +25,23 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @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" +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 +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,38 +65,26 @@ 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% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 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 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/GradleMongoPluginExtension.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/GradleMongoPluginExtension.groovy index afe8f2d..904d0c6 100644 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/GradleMongoPluginExtension.groovy +++ b/src/main/groovy/com/sourcemuse/gradle/plugin/GradleMongoPluginExtension.groovy @@ -29,6 +29,7 @@ class GradleMongoPluginExtension { Map args = [:] Map params = [:] Integer syncDelay = null + String replicaSet = null void setDownloadUrl(String url) { try { diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleProcessLogger.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleProcessLogger.groovy deleted file mode 100644 index 71ffba2..0000000 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleProcessLogger.groovy +++ /dev/null @@ -1,34 +0,0 @@ -package com.sourcemuse.gradle.plugin.flapdoodle.adapters - -import de.flapdoodle.embed.process.distribution.Version -import de.flapdoodle.embed.process.io.progress.ProgressListener - - -class CustomFlapdoodleProcessLogger implements ProgressListener { - private final Version version - - CustomFlapdoodleProcessLogger(Version version) { - this.version = version - } - - @Override - void progress(String label, int percent) { - } - - @Override - void done(String label) { - } - - @Override - void start(String label) { - if (label.contains('Download')) { - println "Downloading Mongo ${version.asInDownloadPath()} distribution..." - } else if (label.contains('Extract')) { - println 'Extracting Mongo binaries...' - } - } - - @Override - void info(String label, String message) { - } -} diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleRuntimeConfig.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleRuntimeConfig.groovy deleted file mode 100644 index 3bc2e7e..0000000 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleRuntimeConfig.groovy +++ /dev/null @@ -1,72 +0,0 @@ -package com.sourcemuse.gradle.plugin.flapdoodle.adapters -import de.flapdoodle.embed.mongo.config.Defaults.DownloadConfigDefaults -import de.flapdoodle.embed.mongo.config.Defaults.RuntimeConfigDefaults -import de.flapdoodle.embed.mongo.packageresolver.Command -import de.flapdoodle.embed.process.config.ImmutableRuntimeConfig -import de.flapdoodle.embed.process.config.store.HttpProxyFactory -import de.flapdoodle.embed.process.config.store.ImmutableDownloadConfig -import de.flapdoodle.embed.process.distribution.Distribution -import de.flapdoodle.embed.process.distribution.Version -import de.flapdoodle.embed.process.io.directories.FixedPath -import de.flapdoodle.embed.process.runtime.CommandLinePostProcessor -import de.flapdoodle.embed.process.store.ArtifactStore -import de.flapdoodle.embed.process.store.Downloader - -class CustomFlapdoodleRuntimeConfig extends RuntimeConfigDefaults { - private final Version version - private final String mongodVerbosity - private final String downloadUrl - private final String proxyHost - private final int proxyPort - private final String artifactStorePath - - CustomFlapdoodleRuntimeConfig(Version version, - String mongodVerbosity, - String downloadUrl, - String proxyHost, - int proxyPort, - String artifactStorePath) { - this.version = version - this.mongodVerbosity = mongodVerbosity - this.downloadUrl = downloadUrl - this.proxyHost = proxyHost - this.proxyPort = proxyPort - this.artifactStorePath = artifactStorePath - } - - ImmutableRuntimeConfig.Builder defaults(Command command) { - ImmutableRuntimeConfig.Builder runtimeConfigBuilder = super.defaults(command) - - ImmutableDownloadConfig.Builder downloadConfigBuilder = new DownloadConfigDefaults().defaultsForCommand(command) - downloadConfigBuilder.progressListener(new CustomFlapdoodleProcessLogger(version)) - - if (downloadUrl) { - downloadConfigBuilder.downloadPath(downloadUrl) - } - - if (proxyHost) { - downloadConfigBuilder.proxyFactory(new HttpProxyFactory(proxyHost, proxyPort)) - } - - if (artifactStorePath) { - downloadConfigBuilder.artifactStorePath(new FixedPath(artifactStorePath)) - } - - runtimeConfigBuilder.commandLinePostProcessor(new CommandLinePostProcessor() { - @Override - List process(Distribution distribution, List args) { - if (mongodVerbosity) args.add(mongodVerbosity) - return args - } - }) - - runtimeConfigBuilder.artifactStore(ArtifactStore.builder() - .downloadConfig(downloadConfigBuilder.build()) - .downloader(Downloader.platformDefault()) - .tempDirFactory(downloadConfigBuilder.artifactStorePath) - .executableNaming(downloadConfigBuilder.fileNaming) - .build()) - - runtimeConfigBuilder - } -} diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/ProcessOutputFactory.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/ProcessOutputFactory.groovy index 56f04f5..1854638 100644 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/ProcessOutputFactory.groovy +++ b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/ProcessOutputFactory.groovy @@ -1,64 +1,74 @@ package com.sourcemuse.gradle.plugin.flapdoodle.adapters -import static com.sourcemuse.gradle.plugin.LogDestination.CONSOLE -import static com.sourcemuse.gradle.plugin.LogDestination.FILE -import static com.sourcemuse.gradle.plugin.LogDestination.NONE - import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension import com.sourcemuse.gradle.plugin.LogDestination -import de.flapdoodle.embed.mongo.packageresolver.Command -import de.flapdoodle.embed.mongo.config.MongodProcessOutputConfig -import de.flapdoodle.embed.process.config.process.ProcessOutput import de.flapdoodle.embed.process.io.NamedOutputStreamProcessor -import de.flapdoodle.embed.process.io.NullProcessor -import de.flapdoodle.embed.process.io.Processors -import de.flapdoodle.embed.process.io.Slf4jLevel +import de.flapdoodle.embed.process.io.ProcessOutput +import de.flapdoodle.reverse.State +import de.flapdoodle.reverse.StateID +import de.flapdoodle.reverse.StateLookup +import de.flapdoodle.reverse.Transition import groovy.transform.TupleConstructor import org.gradle.api.GradleScriptException import org.gradle.api.Project -@TupleConstructor -class ProcessOutputFactory { - Project project - - ProcessOutput getProcessOutput(GradleMongoPluginExtension pluginExtension) { - def logDestination = pluginExtension.logging.toUpperCase() as LogDestination +import static com.sourcemuse.gradle.plugin.LogDestination.CONSOLE +import static com.sourcemuse.gradle.plugin.LogDestination.FILE +import static com.sourcemuse.gradle.plugin.LogDestination.NONE - if (logDestination == CONSOLE) { - return MongodProcessOutputConfig.getDefaultInstance(Command.MongoD) - } +@TupleConstructor +class ProcessOutputFactory implements Transition { + Project project + GradleMongoPluginExtension pluginExtension - if (logDestination == FILE) { - def logFile = new File(pluginExtension.logFilePath) - def logFilePath = logFile.isAbsolute() ? logFile.absolutePath : - createRelativeFilePathFromBuildDir(logFile) + ProcessOutput getProcessOutput() { + def logDestination = pluginExtension.logging.toUpperCase() as LogDestination - def fileOutputStreamProcessor = new FileOutputStreamProcessor(logFilePath) + if (logDestination == CONSOLE) { + return ProcessOutput.namedConsole("mongod") + } - return ProcessOutput.builder() - .output(new NamedOutputStreamProcessor('[mongod output]', fileOutputStreamProcessor)) - .error(new NamedOutputStreamProcessor('[mongod error]', fileOutputStreamProcessor)) - .commands(new NamedOutputStreamProcessor('[mongod commands]', fileOutputStreamProcessor)) - .build() - } + if (logDestination == FILE) { + def logFile = new File(pluginExtension.logFilePath) + def logFilePath = logFile.isAbsolute() ? logFile.absolutePath : + createRelativeFilePathFromBuildDir(logFile) - if (logDestination == NONE) { - def nullProcessor = new NullProcessor() - return ProcessOutput.builder() - .output(nullProcessor) - .error(nullProcessor) - .commands(nullProcessor) - .build() - } + def fileOutputStreamProcessor = new FileOutputStreamProcessor(logFilePath) - throw new GradleScriptException( - "Unrecognized 'logging' option: ${pluginExtension.logging}. " + - "Choose one of ${LogDestination.values().collect { it.toString().toLowerCase() }.join(', ')}.", - new IllegalArgumentException() - ) + return ProcessOutput.builder() + .output(new NamedOutputStreamProcessor('[mongod output]', fileOutputStreamProcessor)) + .error(new NamedOutputStreamProcessor('[mongod error]', fileOutputStreamProcessor)) + .commands(new NamedOutputStreamProcessor('[mongod commands]', fileOutputStreamProcessor)) + .build() } - private String createRelativeFilePathFromBuildDir(File logFile) { - project.buildDir.absolutePath + File.separatorChar + logFile.path + if (logDestination == NONE) { + return ProcessOutput.silent() } + + throw new GradleScriptException( + "Unrecognized 'logging' option: ${pluginExtension.logging}. " + + "Choose one of ${LogDestination.values().collect { it.toString().toLowerCase() }.join(', ')}.", + new IllegalArgumentException() + ) + } + + private String createRelativeFilePathFromBuildDir(File logFile) { + project.buildDir.absolutePath + File.separatorChar + logFile.path + } + + @Override + StateID destination() { + return StateID.of(ProcessOutput.class) + } + + @Override + Set> sources() { + return Set.of() + } + + @Override + State result(StateLookup lookup) { + return State.of(getProcessOutput()) + } } diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/StorageFactory.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/StorageFactory.groovy deleted file mode 100644 index 05f70e3..0000000 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/StorageFactory.groovy +++ /dev/null @@ -1,14 +0,0 @@ -package com.sourcemuse.gradle.plugin.flapdoodle.adapters - -import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension -import de.flapdoodle.embed.mongo.config.Storage - -class StorageFactory { - Storage getStorage(GradleMongoPluginExtension extension) { - if(extension.storageLocation){ - new Storage(extension.storageLocation, null, 0) - } else { - new Storage() - } - } -} diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/VersionFactory.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/VersionFactory.groovy index 27cae1f..9f11617 100644 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/VersionFactory.groovy +++ b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/VersionFactory.groovy @@ -1,69 +1,39 @@ package com.sourcemuse.gradle.plugin.flapdoodle.adapters -import static de.flapdoodle.embed.mongo.distribution.Version.Main.DEVELOPMENT -import static de.flapdoodle.embed.mongo.distribution.Version.Main.PRODUCTION - import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension import de.flapdoodle.embed.mongo.distribution.IFeatureAwareVersion -import de.flapdoodle.embed.process.distribution.Version.GenericVersion -import de.flapdoodle.embed.mongo.packageresolver.Feature import de.flapdoodle.embed.mongo.distribution.Version import de.flapdoodle.embed.mongo.distribution.Versions -import de.flapdoodle.embed.mongo.distribution.Versions.GenericFeatureAwareVersion +import de.flapdoodle.embed.process.distribution.ImmutableGenericVersion +import static de.flapdoodle.embed.mongo.distribution.Version.Main.DEVELOPMENT +import static de.flapdoodle.embed.mongo.distribution.Version.Main.PRODUCTION class VersionFactory { - static final String LATEST_VERSION = '-LATEST' + static final String LATEST_VERSION = '-LATEST' - IFeatureAwareVersion getVersion(GradleMongoPluginExtension pluginExtension) { + static IFeatureAwareVersion getVersion(GradleMongoPluginExtension pluginExtension) { def suppliedVersion = pluginExtension.mongoVersion - if (versionIsDevOrProd(suppliedVersion)) { - return suppliedVersion as Version.Main + if (suppliedVersion == "DEVELOPMENT") { + return DEVELOPMENT + } else if (suppliedVersion == "PRODUCTION") { + return PRODUCTION + } else if (suppliedVersion == "latest") { + return Version.LATEST_NIGHTLY } - return parseVersionNumber(suppliedVersion) - } - - private static boolean versionIsDevOrProd(String suppliedVersion) { - try { - (suppliedVersion as Version.Main) in [DEVELOPMENT, PRODUCTION] - } catch (ignored) {} - } - - private static IFeatureAwareVersion parseVersionNumber(String suppliedVersion) { - String mongoVersion = convertToFlapdoodleVersion(suppliedVersion) - - if (mongoVersion.endsWith(LATEST_VERSION)) { - mongoVersion = mongoVersion.substring(0, mongoVersion.length() - LATEST_VERSION.length()) - - if (versionMatchesMainBranchVersion(mongoVersion)) { - return mongoVersion as Version.Main + if (suppliedVersion.endsWith(LATEST_VERSION)) { + def mainVersion = "V" + suppliedVersion.substring(0, suppliedVersion.length() - LATEST_VERSION.length()).replace(".", "_") + for (def v in Version.Main.values()) { + if (v.name() == mainVersion) { + return v } - } else if (versionMatchesSpecificVersion(mongoVersion)) { - return mongoVersion as Version - } else { - // we'll just assume that the version is newer and supports all features - return new GenericFeatureAwareVersion(GenericVersion.of(suppliedVersion)) + } } - } - - private static boolean versionMatchesMainBranchVersion(String mongoVersion) { - try { - mongoVersion in Version.Main.values().collect { it.toString() } - } catch (ignored) {} - } - - private static String convertToFlapdoodleVersion(String suppliedVersion) { - def mongoVersion = 'V' + suppliedVersion - mongoVersion = mongoVersion.replace('.', '_') - mongoVersion - } - private static Version versionMatchesSpecificVersion(String mongoVersion) { - try { - mongoVersion as Version - } catch (ignored) {} + // for some reason with gradle 8.8 and jdk 17 groovy is unable to use the static interface method Version.of + return new Versions.GenericFeatureAwareVersion(new ImmutableGenericVersion(suppliedVersion)) } } diff --git a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/gradle/GradleMongoPlugin.groovy b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/gradle/GradleMongoPlugin.groovy index f0d90aa..a83f3b3 100644 --- a/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/gradle/GradleMongoPlugin.groovy +++ b/src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/gradle/GradleMongoPlugin.groovy @@ -1,10 +1,27 @@ package com.sourcemuse.gradle.plugin.flapdoodle.gradle -import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.ManageProcessInstruction.CONTINUE_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS -import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.ManageProcessInstruction.STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS - -import java.util.concurrent.atomic.AtomicInteger - +import com.mongodb.MongoCommandException +import com.mongodb.client.MongoClients +import com.mongodb.client.MongoDatabase +import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension +import com.sourcemuse.gradle.plugin.flapdoodle.adapters.ProcessOutputFactory +import com.sourcemuse.gradle.plugin.flapdoodle.adapters.VersionFactory +import de.flapdoodle.embed.mongo.commands.ImmutableMongodArguments +import de.flapdoodle.embed.mongo.commands.MongodArguments +import de.flapdoodle.embed.mongo.config.Net +import de.flapdoodle.embed.mongo.transitions.ImmutableMongod +import de.flapdoodle.embed.mongo.transitions.Mongod +import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess +import de.flapdoodle.embed.mongo.types.DatabaseDir +import de.flapdoodle.embed.mongo.types.DistributionBaseUrl +import de.flapdoodle.embed.process.config.DownloadConfig +import de.flapdoodle.embed.process.io.directories.PersistentDir +import de.flapdoodle.embed.process.runtime.Network +import de.flapdoodle.embed.process.transitions.DownloadPackage +import de.flapdoodle.embed.process.types.ProcessConfig +import de.flapdoodle.net.ProxyFactory +import de.flapdoodle.reverse.Transition +import de.flapdoodle.reverse.transitions.Start import org.bson.Document import org.gradle.BuildListener import org.gradle.BuildResult @@ -15,21 +32,13 @@ import org.gradle.api.initialization.Settings import org.gradle.api.invocation.Gradle import org.gradle.api.tasks.TaskState -import com.mongodb.MongoClient -import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension -import com.sourcemuse.gradle.plugin.flapdoodle.adapters.CustomFlapdoodleRuntimeConfig -import com.sourcemuse.gradle.plugin.flapdoodle.adapters.ProcessOutputFactory -import com.sourcemuse.gradle.plugin.flapdoodle.adapters.StorageFactory -import com.sourcemuse.gradle.plugin.flapdoodle.adapters.VersionFactory +import java.nio.file.Files +import java.nio.file.Path +import java.time.Instant +import java.util.concurrent.atomic.AtomicInteger -import de.flapdoodle.embed.mongo.MongodProcess -import de.flapdoodle.embed.mongo.MongodStarter -import de.flapdoodle.embed.mongo.config.ImmutableMongoCmdOptions -import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig -import de.flapdoodle.embed.mongo.config.Net -import de.flapdoodle.embed.mongo.packageresolver.Command -import de.flapdoodle.embed.mongo.runtime.Mongod -import de.flapdoodle.embed.process.runtime.Network +import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.ManageProcessInstruction.CONTINUE_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS +import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.ManageProcessInstruction.STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS class GradleMongoPlugin implements Plugin { @@ -53,13 +62,14 @@ class GradleMongoPlugin implements Plugin { private static void configureTaskProperties(Project project) { project.extensions.create(PLUGIN_EXTENSION_NAME, GradleMongoPluginExtension) - project.getRootProject().extensions.extraProperties.set("mongoPortToProcessMap", new HashMap()) + project.getRootProject().extensions.extraProperties.set("mongoPortToProcessMap", new HashMap()) + project.getRootProject().extensions.extraProperties.set("mongoPortToTempStorage", new HashMap()) } private static void addStartManagedMongoDbTask(Project project) { project.task(group: TASK_GROUP_NAME, description: 'Starts a local MongoDb instance which will stop when the build process completes', 'startManagedMongoDb').doFirst { def mongoStartedByTask = startMongoDb(project, STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS) - + if (mongoStartedByTask) { ensureMongoDbStopsEvenIfGradleDaemonIsRunning(project) } @@ -77,7 +87,7 @@ class GradleMongoPlugin implements Plugin { if (!stopMongoDbTaskPresent) { def lastTask = project.gradle.taskGraph.allTasks[-1] - + project.gradle.addBuildListener(new BuildListener() { @Override void buildFinished(BuildResult buildResult) { @@ -87,13 +97,13 @@ class GradleMongoPlugin implements Plugin { } } } - + @Override void projectsEvaluated(Gradle gradle) {} - + @Override void projectsLoaded(Gradle gradle) {} - + @Override void settingsEvaluated(Settings gradle) {} }) @@ -105,52 +115,76 @@ class GradleMongoPlugin implements Plugin { return startMongoDb(pluginExtension, project, manageProcessInstruction) } - private - static boolean startMongoDb(GradleMongoPluginExtension pluginExtension, Project project, ManageProcessInstruction manageProcessInstruction) { + private static boolean startMongoDb(GradleMongoPluginExtension pluginExtension, Project project, ManageProcessInstruction manageProcessInstruction) { if (mongoInstanceAlreadyRunning(pluginExtension.bindIp, pluginExtension.port)) { println "Mongo instance already running at ${pluginExtension.bindIp}:${pluginExtension.port}. Reusing." return false } - def processOutput = new ProcessOutputFactory(project).getProcessOutput(pluginExtension) def version = new VersionFactory().getVersion(pluginExtension) - def storage = new StorageFactory().getStorage(pluginExtension) - - def configBuilder = ImmutableMongodConfig.builder() - .cmdOptions(createMongoCommandOptions(pluginExtension)) - .version(version) - .replication(storage) - .net(new Net(pluginExtension.bindIp, pluginExtension.port, Network.localhostIsIPv6())) - - pluginExtension.args.each { k, v -> - if (!v) - configBuilder.putArgs("--${k}", null) - else - configBuilder.putArgs("--${k}", v) - } - pluginExtension.params.each { k, v -> configBuilder.putParams(k, v) } - - def mongodConfig = configBuilder.build() + ImmutableMongod.Builder builder = Mongod.builder() + builder.processOutput(new ProcessOutputFactory(project, pluginExtension)) + builder.net( + Start.to(Net.class) + .initializedWith(Net.of(pluginExtension.bindIp, pluginExtension.port, Network.localhostIsIPv6())) + ) + + builder.mongodArguments(createMongoCommandOptions(pluginExtension)) + + builder.processConfig( + Start.to(ProcessConfig.class) + .initializedWith(ProcessConfig.defaults().withDaemonProcess(manageProcessInstruction == STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS)) + ) + + Path storageLocation + if (pluginExtension.storageLocation == GradleMongoPluginExtension.EPHEMERAL_TEMPORARY_FOLDER) { + storageLocation = Files.createTempDirectory("$PLUGIN_EXTENSION_NAME-") + storageLocation.toFile().deleteOnExit() + project.rootProject.mongoPortToTempStorage.put(pluginExtension.port, storageLocation) + } else { + storageLocation = Path.of(pluginExtension.storageLocation) + } + builder.databaseDir( + Start.to(DatabaseDir.class) + .initializedWith(DatabaseDir.of(storageLocation)) + ) + + if (pluginExtension.artifactStorePath) { + builder.persistentBaseDir( + Start.to(PersistentDir.class) + .initializedWith(PersistentDir.of(Path.of(pluginExtension.artifactStorePath))) + ) + } - def runtimeConfig = new CustomFlapdoodleRuntimeConfig(version, - pluginExtension.mongodVerbosity, - pluginExtension.downloadUrl, - pluginExtension.proxyHost, - pluginExtension.proxyPort, - pluginExtension.artifactStorePath) - .defaults(Command.MongoD) - .processOutput(processOutput) - .isDaemonProcess(manageProcessInstruction == STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS) - .build() + if (pluginExtension.downloadUrl) { + builder.distributionBaseUrl( + Start.to(DistributionBaseUrl.class) + .initializedWith(DistributionBaseUrl.of(pluginExtension.downloadUrl)) + ) + } - def runtime = MongodStarter.getInstance(runtimeConfig) + if (pluginExtension.proxyHost) { + builder.downloadPackage( + DownloadPackage.withDefaults() + .withDownloadConfig( + DownloadConfig.defaults() + .withProxyFactory(ProxyFactory.of(pluginExtension.proxyHost, pluginExtension.proxyPort)) + ) + ) + } - def mongodExecutable = runtime.prepare(mongodConfig) println "Starting Mongod ${version.asInDownloadPath()} on port ${pluginExtension.port}..." - def mongoProc = mongodExecutable.start() + RunningMongodProcess running = builder.build().start(version).current() println 'Mongod started.' - project.rootProject.mongoPortToProcessMap.put(pluginExtension.port, mongoProc) + project.rootProject.mongoPortToProcessMap.put(pluginExtension.port, running) + + if (pluginExtension.replicaSet != null) { + println 'Initializing replica set...' + setupReplicaSet(pluginExtension.bindIp, pluginExtension.port) + println 'Mongod is writable' + } + return true } @@ -160,8 +194,9 @@ class GradleMongoPlugin implements Plugin { } try { - def mongoClient = new MongoClient(bindIp, port) - mongoClient.getDatabase('test').runCommand(new Document(buildinfo: 1)) + MongoClients.create("mongodb://${bindIp}:${port}").withCloseable { + it.getDatabase('test').runCommand(new Document(buildinfo: 1)) + } } catch (Throwable ignored) { return false } @@ -183,19 +218,75 @@ class GradleMongoPlugin implements Plugin { } } - private static ImmutableMongoCmdOptions createMongoCommandOptions(GradleMongoPluginExtension pluginExtension) { - def mongoCommandOptionsBuilder = ImmutableMongoCmdOptions.builder() - .useNoJournal(!pluginExtension.journalingEnabled) - .storageEngine(pluginExtension.storageEngine) - .auth(pluginExtension.auth) + private static void setupReplicaSet(String host, int port) { + MongoClients.create("mongodb://${host}:${port}").withCloseable { + MongoDatabase adminDB = it.getDatabase("admin") + + try { + adminDB.runCommand(new Document("replSetInitiate", new Document())) + } catch (MongoCommandException e) { + if (e.getErrorCode() != 23) { // ignore AlreadyInitialized errors + throw e + } + } + + // wait until replica set (this node) is writable + Instant stop = Instant.now().plusSeconds(10) + while (!adminDB.runCommand(new Document("isMaster", 1)).getBoolean("ismaster") && Instant.now().isBefore(stop)) { + try { + //noinspection BusyWait + Thread.sleep(100) + } catch (InterruptedException e) { + throw new RuntimeException(e) + } + } + + if (Instant.now().isAfter(stop)) { + throw new RuntimeException("Current node is not master/primary after 10s") + } + } + } + + private static Transition createMongoCommandOptions(GradleMongoPluginExtension pluginExtension) { + ImmutableMongodArguments.Builder builder = MongodArguments.builder() + + if (pluginExtension.mongodVerbosity) { + // is already sanitized by GradleMongoPluginExtension#parseMongodVerbosity + builder.putArgs(pluginExtension.mongodVerbosity, "") + } + + builder.useNoJournal(!pluginExtension.journalingEnabled) + builder.storageEngine(pluginExtension.storageEngine) + builder.auth(pluginExtension.auth) if (pluginExtension.syncDelay != null){ - mongoCommandOptionsBuilder.syncDelay(pluginExtension.syncDelay) + builder.syncDelay(pluginExtension.syncDelay) } else { - mongoCommandOptionsBuilder.useDefaultSyncDelay(true) + builder.useDefaultSyncDelay(true) + } + + if (pluginExtension.replicaSet != null) { + builder.putArgs("--replSet", pluginExtension.replicaSet) } - mongoCommandOptionsBuilder.build() + if (pluginExtension.args != null && pluginExtension.args.size() > 0) { + pluginExtension.args.each {k, v -> + // automatically add - or -- to stay compatible with old plugin versions + if (!k.startsWith("-")) { + if (k.length() == 1) { + k = "-" + k + } else { + k = "--" + k + } + } + builder.putArgs(k, v) + } + } + if (pluginExtension.params != null && pluginExtension.params.size() > 0) { + builder.putAllParams(pluginExtension.params) + } + + return Start.to(MongodArguments.class).initializedWith(builder.build()) } private static void addStopMongoDbTask(Project project) { @@ -206,11 +297,12 @@ class GradleMongoPlugin implements Plugin { private static void stopMongoDb(Project project) { def port = project."$PLUGIN_EXTENSION_NAME".port as Integer - def proc = project.rootProject.mongoPortToProcessMap.remove(port) as MongodProcess - stopMongoDb(port, proc) + def proc = project.rootProject.mongoPortToProcessMap.remove(port) as RunningMongodProcess + def tempStorage = project.rootProject.mongoPortToTempStorage.remove(port) as Path + stopMongoDb(port, proc, tempStorage) } - private static void stopMongoDb(int port, MongodProcess proc) { + private static void stopMongoDb(int port, RunningMongodProcess proc, Path tempStorage) { println "Shutting-down Mongod on port ${port}." def force = (proc == null) @@ -220,9 +312,13 @@ class GradleMongoPlugin implements Plugin { force = true } - if (force && !Mongod.sendShutdown(InetAddress.getLoopbackAddress(), port)) { + if (force && !de.flapdoodle.embed.mongo.runtime.Mongod.sendShutdown(InetAddress.getLoopbackAddress(), port)) { println "Could not shut down mongo, is access control enabled?" } + + if (tempStorage != null) { + tempStorage.deleteDir() + } } private static void extendAllTasksWithMongoOptions(Project project) { @@ -260,7 +356,7 @@ class GradleMongoPlugin implements Plugin { } } } - + project.gradle.addBuildListener(new BuildListener() { @Override void buildFinished(BuildResult buildResult) { @@ -273,19 +369,19 @@ class GradleMongoPlugin implements Plugin { synchronized (rootProject) { def mongoDependencyCount = rootProject.mongoTaskDependenciesCountByPort.get(port).decrementAndGet() if (mongoDependencyCount == 0 && rootProject.mongoInstancesStartedDuringBuild.get(port)) { - stopMongoDb(port, rootProject.mongoPortToProcessMap.remove(port)) + stopMongoDb(port, rootProject.mongoPortToProcessMap.remove(port), rootProject.mongoPortToTempStorage.remove(port)) } } } } } - + @Override void projectsEvaluated(Gradle gradle) {} - + @Override void projectsLoaded(Gradle gradle) {} - + @Override void settingsEvaluated(Settings gradle) {} }) diff --git a/src/test/groovy/com/sourcemuse/gradle/plugin/BuildScriptBuilder.groovy b/src/test/groovy/com/sourcemuse/gradle/plugin/BuildScriptBuilder.groovy index 6526e07..c82840f 100644 --- a/src/test/groovy/com/sourcemuse/gradle/plugin/BuildScriptBuilder.groovy +++ b/src/test/groovy/com/sourcemuse/gradle/plugin/BuildScriptBuilder.groovy @@ -106,6 +106,11 @@ class BuildScriptBuilder { this } + BuildScriptBuilder withReplicaSet(String name) { + configProperties.replicaSet = asStringProperty(name) + this + } + BuildScriptBuilder withParams(Map params) { configProperties.params = params.inspect() this diff --git a/src/test/groovy/com/sourcemuse/gradle/plugin/MongoPluginConfigSpec.groovy b/src/test/groovy/com/sourcemuse/gradle/plugin/MongoPluginConfigSpec.groovy index dbee4a3..12d8544 100644 --- a/src/test/groovy/com/sourcemuse/gradle/plugin/MongoPluginConfigSpec.groovy +++ b/src/test/groovy/com/sourcemuse/gradle/plugin/MongoPluginConfigSpec.groovy @@ -1,23 +1,30 @@ package com.sourcemuse.gradle.plugin -import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.* -import static com.sourcemuse.gradle.plugin.MongoUtils.* - +import com.mongodb.MongoCredential +import de.flapdoodle.embed.mongo.distribution.Version import org.bson.Document import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.littleshoot.proxy.impl.DefaultHttpProxyServer - -import com.mongodb.MongoCredential - -import de.flapdoodle.embed.mongo.distribution.Version import spock.lang.Issue import spock.lang.Specification import spock.lang.TempDir +import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.DEFAULT_MONGOD_PORT +import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.MONGO_RUNNING_FLAG +import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.START_MANAGED_MONGO_DB_FOR_TEST +import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.START_MONGO_DB_FOR_TEST +import static com.sourcemuse.gradle.plugin.MongoUtils.ensureMongoIsStopped +import static com.sourcemuse.gradle.plugin.MongoUtils.getMongoVersionRunning +import static com.sourcemuse.gradle.plugin.MongoUtils.makeJournaledWrite +import static com.sourcemuse.gradle.plugin.MongoUtils.makeWriteWithTransaction +import static com.sourcemuse.gradle.plugin.MongoUtils.mongoInstanceRunning +import static com.sourcemuse.gradle.plugin.MongoUtils.mongoServerStatus +import static com.sourcemuse.gradle.plugin.MongoUtils.runMongoCommand + class MongoPluginConfigSpec extends Specification { - def static final VERBOSE_LOGGING_SAMPLE = 'ismaster' + def static final VERBOSE_LOGGING_SAMPLE = 'isMaster' @TempDir File tmp @@ -356,6 +363,17 @@ class MongoPluginConfigSpec extends Specification { noExceptionThrown() } + def "transactions can be used"() { + setup: + // transactions require replica set & journaling + generate(buildScript.withReplicaSet("test").withJournalingEnabled().withMongoVersion("5.0.26")) + when: + runGradle(START_MONGO_DB_FOR_TEST) + makeWriteWithTransaction(DEFAULT_MONGOD_PORT) + then: + noExceptionThrown() + } + def cleanup() { ensureMongoIsStopped(buildScript.configuredPort ?: DEFAULT_MONGOD_PORT) } diff --git a/src/test/groovy/com/sourcemuse/gradle/plugin/MongoUtils.groovy b/src/test/groovy/com/sourcemuse/gradle/plugin/MongoUtils.groovy index 6616a22..e55f804 100644 --- a/src/test/groovy/com/sourcemuse/gradle/plugin/MongoUtils.groovy +++ b/src/test/groovy/com/sourcemuse/gradle/plugin/MongoUtils.groovy @@ -1,7 +1,13 @@ package com.sourcemuse.gradle.plugin -import com.mongodb.* -import com.mongodb.client.MongoDatabase +import com.mongodb.ConnectionString +import com.mongodb.MongoClientSettings +import com.mongodb.MongoCredential +import com.mongodb.MongoException +import com.mongodb.WriteConcern +import com.mongodb.client.ClientSession +import com.mongodb.client.MongoClient +import com.mongodb.client.MongoClients import de.flapdoodle.embed.mongo.runtime.Mongod import org.bson.Document @@ -13,16 +19,17 @@ class MongoUtils { private static final String DATABASE_NAME = 'test' static void ensureMongoIsStopped(int port = DEFAULT_MONGOD_PORT) { - Mongod.sendShutdown(InetAddress.getLoopbackAddress(), port) - } - - static MongoDatabase mongoDatabase(int port) { - def mongoClient = new MongoClient(LOOPBACK_ADDRESS, port) - mongoClient.getDatabase(DATABASE_NAME) + if (mongoInstanceRunning(port)) { + // try shutdown command for older versions first + Mongod.sendShutdownLegacy(InetAddress.getLoopbackAddress(), port) + || Mongod.sendShutdown(InetAddress.getLoopbackAddress(), port) + } } static Document mongoServerStatus(int port = DEFAULT_MONGOD_PORT) { - mongoDatabase(port).runCommand(new Document('serverStatus', 1)) + MongoClients.create("mongodb://${LOOPBACK_ADDRESS}:${port}").withCloseable { + return it.getDatabase(DATABASE_NAME).runCommand(new Document('serverStatus', 1)) + } } static boolean mongoInstanceRunning(int port = DEFAULT_MONGOD_PORT) { @@ -53,23 +60,19 @@ class MongoUtils { } static String getMongoVersionRunning(int port) { - try { - def mongoClient = new MongoClient(LOOPBACK_ADDRESS, port) - def result = mongoClient.getDatabase(DATABASE_NAME).runCommand(new Document('buildInfo', 1)) - return result.version - } catch (Exception e) { - return 'none' + MongoClients.create("mongodb://${LOOPBACK_ADDRESS}:${port}").withCloseable { + return it.getDatabase(DATABASE_NAME).runCommand(new Document('buildInfo', 1)).version } } static boolean makeJournaledWrite() { - try { - def options = MongoClientOptions.builder().writeConcern(WriteConcern.JOURNALED).build() - def mongoClient = new MongoClient("${LOOPBACK_ADDRESS}:${DEFAULT_MONGOD_PORT}", options) - writeSampleObjectToDb(mongoClient) + def settings = MongoClientSettings.builder() + .writeConcern(WriteConcern.JOURNALED) + .applyConnectionString(new ConnectionString("mongodb://${LOOPBACK_ADDRESS}:${DEFAULT_MONGOD_PORT}")) + .build() + MongoClients.create(settings).withCloseable { + writeSampleObjectToDb(it) return true - } catch (Exception e) { - return false } } @@ -81,16 +84,19 @@ class MongoUtils { } static boolean runMongoCommand(MongoCredential credential, Document cmd) { - ServerAddress addr = new ServerAddress("${LOOPBACK_ADDRESS}:${DEFAULT_MONGOD_PORT}") - def mongoClient = credential ? - new MongoClient(addr, credential, MongoClientOptions.builder().build()) : - new MongoClient(addr, MongoClientOptions.builder().build()) + def settings = MongoClientSettings.builder() + .applyConnectionString(new ConnectionString("mongodb://${LOOPBACK_ADDRESS}:${DEFAULT_MONGOD_PORT}")) + if (credential) { + settings.credential(credential) + } + + def mongoClient = MongoClients.create(settings.build()) try { mongoClient.getDatabase('admin').runCommand(cmd) } - catch (MongoException e) { + catch (MongoException ignored) { return false } finally { @@ -98,4 +104,19 @@ class MongoUtils { } return true } + + static boolean makeWriteWithTransaction(int port) { + MongoClients.create("mongodb://${LOOPBACK_ADDRESS}:${port}").withCloseable { + try (ClientSession session = it.startSession()) { + session.startTransaction() + + it.getDatabase(DATABASE_NAME) + .getCollection("test") + .insertOne(session, new Document("foo", "bar")) + + session.commitTransaction() + } + } + return true + } } \ No newline at end of file