From af9f209180b3b764d04d7a88dd41fcad161c9b76 Mon Sep 17 00:00:00 2001
From: Juraj Martinka <juraj.martinka@gooddata.com>
Date: Wed, 15 May 2013 09:44:50 +0200
Subject: [PATCH] Random port allocation - randomPort property added to be able
 to assign random free port at runtime.

This way we are able to perform concurrent builds on single machine.
Allocated port is set to the system property "embedmongo.port.<maven-artifactId>" where <maven-artifactId> is artifactId of current (tested) maven module.
---
 CHANGELOG.md                                  |  4 +
 README.md                                     |  4 +-
 pom.xml                                       | 12 +++
 .../embedmongo/StartEmbeddedMongoMojo.java    | 53 +++++++++-
 .../embedmongo/port/PortHelper.java           | 99 +++++++++++++++++++
 .../port/PortUnavailableException.java        | 25 +++++
 .../embedmongo/port/PortHelperTest.java       | 72 ++++++++++++++
 src/test/resources/example1/pom.xml           |  2 +-
 src/test/resources/example2/pom.xml           |  2 +-
 src/test/resources/example3/pom.xml           |  2 +-
 src/test/resources/pom.xml                    |  3 +-
 src/test/resources/randomport/pom.xml         | 86 ++++++++++++++++
 .../joelittlejohn/embedmongo/MongoIT.java     | 39 ++++++++
 13 files changed, 397 insertions(+), 6 deletions(-)
 create mode 100644 src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java
 create mode 100644 src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java
 create mode 100644 src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java
 create mode 100644 src/test/resources/randomport/pom.xml
 create mode 100644 src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea88496..ae012a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # embedmongo-maven-plugin Changelog
 
+## 0.1.8
+
+* Add support for random port allocation - new configuration option `randomPort`
+
 ## 0.1.7
 
 * Add `logFile`/`logFileEncoding` configuration options (thanks @matthewadams)
diff --git a/README.md b/README.md
index d8a7079..1d441ad 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Usage
             </goals>
             <configuration>
                 <port>37017</port> <!-- optional, default 27017 -->
+                <randomPort>true</randomPort> <!-- optional, default is false, if true overrides "static" port configuration -->
                 <version>2.0.4</version>  <!-- optional, default 2.2.1 -->
                 <databaseDirectory>/tmp/mongotest</databaseDirectory>  <!-- optional, default is a new dir in java.io.tmpdir -->
                 <logging>file</logging> <!-- optional (file|console|none), default console -->
@@ -48,7 +49,8 @@ Notes
 
 * By default, the `start` goal is bound to `pre-integration-test`, the `stop` goal is bound to `post-integration-test`. You can of course bind to different phases if required.
 * If you omit/forget the `stop` goal, any Mongo process spawned by the `start` goal will be stopped when the JVM terminates.
-* If you want to run many Maven builds in parallel using Jenkins, try the [Port Allocator Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Port+Allocator+Plugin) to avoid port conflicts.
+* If you want to run many Maven builds in parallel you can use `randomPort` to avoid port conflicts.
+  If you are using Jenkins, you can also try the [Port Allocator Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Port+Allocator+Plugin).
 * If you need to use a proxy to download MongoDB then you can either use `-Dhttp.proxyHost` and `-Dhttp.proxyPort` as additional Maven arguments (this will affect the entire build) or instruct the plugin to use a proxy when downloading Mongo by adding the `proxyHost` and `proxyPort` configuration properties.
 * Using the `file` logging mode results in a new log file created at `./embedmongo.log` unless you specify via `<logFile>` (where `.` means the curent working directory, usually the same as `${basedir}`).
 * If you'd like the start goal to start mongodb and wait, you can add `-Dembedmongo.wait` to your Maven command line arguments
diff --git a/pom.xml b/pom.xml
index 48a12fa..26eec03 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,12 @@
             <artifactId>maven-plugin-api</artifactId>
             <version>2.0</version>
         </dependency>
+        <!--To be able to use MavenSession#userProperties we need maven-core (version 2.0 is too old, however)-->
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-core</artifactId>
+            <version>2.2.1</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-project</artifactId>
@@ -107,6 +113,12 @@
             <artifactId>de.flapdoodle.embed.mongo</artifactId>
             <version>1.31</version>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit-dep</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java b/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java
index 76a7717..2600afb 100644
--- a/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java
+++ b/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java
@@ -28,8 +28,11 @@ import java.net.SocketAddress;
 import java.net.URI;
 import java.net.UnknownHostException;
 import java.util.List;
+import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 
+import com.github.joelittlejohn.embedmongo.port.PortHelper;
+import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
@@ -53,6 +56,7 @@ import de.flapdoodle.embed.process.distribution.GenericVersion;
 import de.flapdoodle.embed.process.distribution.IVersion;
 import de.flapdoodle.embed.process.exceptions.DistributionException;
 import de.flapdoodle.embed.process.runtime.Network;
+import org.apache.maven.project.MavenProject;
 
 /**
  * When invoked, this goal starts an instance of mongo. The required binaries
@@ -76,6 +80,14 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
      */
     private int port;
 
+    /**
+     * Random port should be used for MongoDB instead of the one specified by {@code port}.
+     *
+     * @parameter expression="${embedmongo.randomPort}" default-value="false"
+     * @since 0.1.8
+     */
+    private boolean randomPort;
+
     /**
      * The version of MongoDB to run e.g. 2.1.1, 1.6 v1.8.2, V2_0_4,
      * 
@@ -161,6 +173,23 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
      */
     private String proxyPassword;
 
+    /**
+     * The maven project.
+     *
+     * @parameter expression="${project}"
+     * @readonly
+     */
+    private MavenProject project;
+
+    /**
+     * The Maven Session Object for setting allocated port to session's user properties.
+     *
+     * @parameter expression="${session}"
+     * @readonly
+     */
+    private MavenSession session;
+
+
     @Override
     @SuppressWarnings("unchecked")
     public void execute() throws MojoExecutionException, MojoFailureException {
@@ -176,7 +205,10 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
                     .defaults(Command.MongoD)
                     .processOutput(getOutputConfig())
                     .build();
-
+            if (randomPort) {
+                port = new PortHelper().allocateRandomPort();
+            }
+            savePortToSessionUserProperties();
             MongodConfig mongoConfig = new MongodConfig(getVersion(),
                     new Net(bindIp, port, Network.localhostIsIPv6()),
                     new Storage(getDataDirectory(), null, 0),
@@ -208,6 +240,25 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
         }
     }
 
+    /**
+     * Saves port to the {@link MavenSession#userProperties} to provide client with ability to retrieve port
+     * later in integration-test-phase via {@link PortHelper#getMongoPort(String)} method.
+     * Port is saved as a property {@code MONGO_PORT_PROPERTY + "." + artifactId} where {@code artifactId}
+     * is id of project artifact where the integration tests are run.
+     * The {@code artifactId} suffix is necessary because concurrent executions of {@code embedmongo-maven-plugin}
+     * cannot share the same property.
+     * <p>
+     * {@code userProperties} seems to be the only way how to propagate property to the forked JVM run
+     * started by failsafe plugin.
+     * </p>
+     */
+    private void savePortToSessionUserProperties() {
+        final String portKey = PortHelper.MONGO_PORT_PROPERTY + "." + project.getArtifactId();
+        final String portValue = Integer.toString(port);
+        final Properties userProperties = session.getUserProperties();
+        userProperties.setProperty(portKey, portValue);
+    }
+
     private ProcessOutput getOutputConfig() throws MojoFailureException {
 
         LoggingStyle loggingStyle = LoggingStyle.valueOf(logging.toUpperCase());
diff --git a/src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java
new file mode 100644
index 0000000..3030598
--- /dev/null
+++ b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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.
+ */
+package com.github.joelittlejohn.embedmongo.port;
+
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Port helper for common operations with port.
+ * <p>
+ *     It can :
+ *     <ul>
+ *         <li>allocate random free port - Inspired by
+ * <a href="https://github.com/sonatype/port-allocator-maven-plugin/blob/master/src/main/java/org/sonatype/plugins/portallocator/PortAllocatorMojo.java">
+ *     PortAllocatorMojo</a>.</li>
+ *         <li>Return port already allocated by invoking start goal of {@code embedmongo-maven-plugin}</li>
+ *     </ul>
+
+*  </p>
+ */
+public class PortHelper {
+
+    public static final String MONGO_PORT_PROPERTY = "embedmongo.port";
+
+
+    /**
+     * Finds port where the mongodb started in previous phase by {@code embedmongo-maven-plugin} should be running.
+     * This assumes that embedmongo-maven-plugin' goal start has already been invoked for {@code artifactId}
+     * and allocates port properly.
+     *
+     * @param artifactId maven artifact id of module for which the port set by {@code embedmongo-maven-plugin} should be found.
+     * @return port of mongodb for running integration tests of given {@code artifactId}
+     */
+    public static int getMongoPort(String artifactId) {
+        String portProperty;
+        if (artifactId == null || artifactId.length() == 0) {
+            throw new IllegalArgumentException("maven artifactId has to be specified to find port for proper maven module.");
+        } else {
+            portProperty  = MONGO_PORT_PROPERTY + "." + artifactId;
+        }
+        final String port = System.getProperty(portProperty);
+        if (StringUtils.isEmpty(port)) {
+            throw new IllegalStateException("No mongo port has been set by embedmongo maven plugin via system property '"
+                    + portProperty + "'!\n Check plugin configuration and make sure that mongoDb is started before"
+                    + " you are trying to access its port.");
+        }
+        return Integer.valueOf(port);
+    }
+
+
+    /**
+     * Allocates new free random port.
+     *
+     * <p>
+     * This implementation allocate random free port and closes it immediately.
+     * In some (hopefully) rare situations the port may be occupied in the meantime between calling this method
+     * and using returned port on client side.
+     * </p>
+     * @return random free port
+     */
+    public int allocateRandomPort() {
+        return allocate(0);
+    }
+
+
+    //--------------------------------------------------- HELPER METHODS -----------------------------------------------
+
+    private static int allocate(int portNumber) {
+        ServerSocket server;
+        try {
+            server = new ServerSocket(portNumber);
+        } catch (IOException e) {
+            throw new PortUnavailableException(portNumber, e);
+        }
+
+        portNumber = server.getLocalPort();
+        try {
+            server.close();
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to release port " + portNumber, e);
+        }
+        return portNumber;
+    }
+}
diff --git a/src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java
new file mode 100644
index 0000000..2add5af
--- /dev/null
+++ b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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.
+ */
+package com.github.joelittlejohn.embedmongo.port;
+
+/**
+ * Represents exceptional state when given port is not available.
+ */
+public class PortUnavailableException extends RuntimeException {
+    public PortUnavailableException(int port, Throwable cause) {
+        super("Port " + port + " is not available", cause);
+    }
+}
diff --git a/src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java b/src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java
new file mode 100644
index 0000000..aa406cc
--- /dev/null
+++ b/src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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.
+ */
+package com.github.joelittlejohn.embedmongo.port;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class PortHelperTest {
+
+    private final PortHelper portHelper = new PortHelper();
+    private final ScheduledExecutorService testPooledExecutor = Executors.newScheduledThreadPool(20);
+
+    @After
+    public void tearDown() throws Exception {
+        testPooledExecutor.shutdown();
+    }
+
+    /**
+     * This test executes method {@link com.github.joelittlejohn.embedmongo.port.PortHelper#allocateRandomPort()} many times
+     * concurrently to make sure that port allocation works correctly under stress.
+     */
+    @Test
+    public void testAllocateRandomPort() throws Exception {
+        final int testAllocationCount = 10000;
+        final CountDownLatch allocationsCounter = new CountDownLatch(testAllocationCount);
+
+        final Runnable allocatePort = new Runnable() {
+            @Override
+            public void run() {
+                int port = -1;
+                try {
+                    port = portHelper.allocateRandomPort();
+                    new ServerSocket(port);
+                    // port has been bind successfully
+                } catch (IOException e) {
+                    throw new RuntimeException("Port " + port + " cannot be bind!");
+                } finally {
+                    allocationsCounter.countDown();
+                }
+            }
+        };
+
+        final Random randomGenerator = new Random();
+        for (int i = 0; i < testAllocationCount; i++) {
+            // schedule execution a little to in the future to simulate less predictable environment
+            testPooledExecutor.schedule(allocatePort, randomGenerator.nextInt(10), TimeUnit.MILLISECONDS);
+        }
+        allocationsCounter.await(10, TimeUnit.SECONDS);
+    }
+
+}
diff --git a/src/test/resources/example1/pom.xml b/src/test/resources/example1/pom.xml
index 79183ff..e8c9f0c 100644
--- a/src/test/resources/example1/pom.xml
+++ b/src/test/resources/example1/pom.xml
@@ -29,7 +29,7 @@
             <plugin>
                 <groupId>com.github.joelittlejohn.embedmongo</groupId>
                 <artifactId>embedmongo-maven-plugin</artifactId>
-                <version>0.1.7-SNAPSHOT</version>
+                <version>0.1.8-SNAPSHOT</version>
                 <executions>
                     <execution>
                         <id>start</id>
diff --git a/src/test/resources/example2/pom.xml b/src/test/resources/example2/pom.xml
index cf9c60e..4470b9a 100644
--- a/src/test/resources/example2/pom.xml
+++ b/src/test/resources/example2/pom.xml
@@ -29,7 +29,7 @@
             <plugin>
                 <groupId>com.github.joelittlejohn.embedmongo</groupId>
                 <artifactId>embedmongo-maven-plugin</artifactId>
-                <version>0.1.7-SNAPSHOT</version>
+                <version>0.1.8-SNAPSHOT</version>
                 <executions>
                     <execution>
                         <id>start</id>
diff --git a/src/test/resources/example3/pom.xml b/src/test/resources/example3/pom.xml
index 3724164..59618aa 100644
--- a/src/test/resources/example3/pom.xml
+++ b/src/test/resources/example3/pom.xml
@@ -29,7 +29,7 @@
             <plugin>
                 <groupId>com.github.joelittlejohn.embedmongo</groupId>
                 <artifactId>embedmongo-maven-plugin</artifactId>
-                <version>0.1.7-SNAPSHOT</version>
+                <version>0.1.8-SNAPSHOT</version>
                 <executions>
                     <execution>
                         <id>start</id>
diff --git a/src/test/resources/pom.xml b/src/test/resources/pom.xml
index a873da6..21182d8 100644
--- a/src/test/resources/pom.xml
+++ b/src/test/resources/pom.xml
@@ -12,8 +12,9 @@
 
     <modules>
         <module>example1</module>
-	<module>example2</module>
+        <module>example2</module>
         <module>example3</module>
+        <module>randomport</module>
     </modules>
 
 </project>
diff --git a/src/test/resources/randomport/pom.xml b/src/test/resources/randomport/pom.xml
new file mode 100644
index 0000000..96762ac
--- /dev/null
+++ b/src/test/resources/randomport/pom.xml
@@ -0,0 +1,86 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.github.joelittlejohn.embedmongo</groupId>
+        <artifactId>embedmongo-maven-plugin-test-parent</artifactId>
+        <version>0.1.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>com.github.joelittlejohn.embedmongo</groupId>
+    <artifactId>embedmongo-maven-plugin-random-port-test</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <url>https://github.com/joelittlejohn/embedmongo-maven-plugin</url>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.github.joelittlejohn.embedmongo</groupId>
+                <artifactId>embedmongo-maven-plugin</artifactId>
+                <version>0.1.8-SNAPSHOT</version>
+                <executions>
+                    <execution>
+                        <id>start</id>
+                        <goals>
+                            <goal>start</goal>
+                        </goals>
+                        <configuration>
+                            <port>37017</port>
+                            <databaseDirectory>/tmp/mongotest</databaseDirectory>
+                            <logging>console</logging>
+                            <version>v2.2.0</version>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>stop</id>
+                        <goals>
+                            <goal>stop</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <!--Runs integration tests and verifies their results-->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>2.12.4</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit-dep</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.joelittlejohn.embedmongo</groupId>
+            <artifactId>embedmongo-maven-plugin</artifactId>
+            <version>0.1.8-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java b/src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java
new file mode 100644
index 0000000..8fe6fcc
--- /dev/null
+++ b/src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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.
+ */
+package com.github.joelittlejohn.embedmongo;
+
+import com.github.joelittlejohn.embedmongo.port.PortHelper;
+import org.junit.After;
+import org.junit.Test;
+
+import java.net.Socket;
+
+public class MongoIT {
+
+    private Socket mongoSocket;
+
+    @Test
+    public void testConnectMongo() throws Exception {
+        mongoSocket = new Socket("127.0.0.1", PortHelper.getMongoPort("embedmongo-maven-plugin-random-port-test"));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mongoSocket != null) {
+            mongoSocket.close();
+        }
+    }
+}
-- 
GitLab