diff --git a/.gitignore b/.gitignore
index b14ecc2a..c8e4b1e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,21 @@
# OS Generated files
.DS_Store
+
+# IntelliJ files
+/.idea
+/titan.iml
+
+# Generated files
+target
+bin
+coverage
+/titan
+/titan*.zip
+releases
+
+# Development files
+/notes
+/.env
+
+# Jenkins
+.gradle
\ No newline at end of file
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 00000000..c32394f1
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present 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.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.5";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..0d5e6498
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..fa87ad7d
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar
diff --git a/DEVELOPING.md b/DEVELOPING.md
index 7dba3fa6..d906506a 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -5,12 +5,27 @@ For general information about contributing changes, see the
## How it Works
-Describe the internal mechanisms necessary for developers to understand how
-to get started making changes.
+Titan is written with Kotlin for JVM and binaries are compiled using GraalVM.
+
+## Requirements
+* openjdk 1.8.0_212 (see notes for GraalVM)
+* [GraalVM](https://www.graalvm.org/)
+
+###Setting up GraalVM
+* [Install GraalVM](https://www.graalvm.org/docs/getting-started/#install-graalvm)
+* Set JAVA_HOME to be the openjdk include with GraalVM
+* Add the GraalVM bin directory to your PATH
## Building
+```bash
+./mvnw clean install
+java -jar ./target/titan-VERSION-jar-with-dependencies.jar
+```
-Describe how to build the project.
+Once the jar is created, native binaries can be built with the following scripts.
+```bash
+./scripts/build-osx.sh
+./scripts/build-linux.sh
## Testing
@@ -18,4 +33,4 @@ Describe how to test the project.
## Releasing
-Describe how to generate new releases.
+The version for the CLI is maintained with the `VERSION` file. Bump the version in this file and then run `./scripts/compile-maven.sh` to update the version in the POM.xml file and build a new versioned jar. Currently, an OSX binary release file needs to be committed to a proper release directory. If you are on OSX, run `./scripts/build-osx.sh` to create this file. CI/CD will handle the rest of the builds.
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..b3d57817
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,3 @@
+FROM oracle/graalvm-ce:19.0.0
+RUN gu install native-image
+RUN yum install zip -y
\ No newline at end of file
diff --git a/README.md b/README.md
index 4763a9ec..f84cef3d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,20 @@
-# About this Project
+# Titan
+## Your Code. Your Environment. Your Data.
+## Getting Started
-Describe the project for users.
+### Requirements
+Before downloading Titan, be sure that you have the appropriate Docker Desktop Client installed and running for your operating system.
+* [Docker Desktop Client](https://www.docker.com/products/docker-desktop)
-## Contributing
+### Installation
+The available downloads are listed on the [releases](https://github.com/titan-data/titan/releases) tab. Please download the proper package for your operating system and architecture.
+
+Titan is distributed as a binary with accompanying docker image. Install Titan by unzipping the downloaded release and moving the binary to a directory included in your system's PATH and running the following command from your CLI:
+```bash
+titan install
+```
+
+## Contributing
This project follows the Titan community best practices:
diff --git a/VERSION b/VERSION
new file mode 100644
index 00000000..9325c3cc
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.3.0
\ No newline at end of file
diff --git a/config/jni-config.json b/config/jni-config.json
new file mode 100644
index 00000000..0d4f101c
--- /dev/null
+++ b/config/jni-config.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/config/proxy-config.json b/config/proxy-config.json
new file mode 100644
index 00000000..0d4f101c
--- /dev/null
+++ b/config/proxy-config.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/config/reflect-config.json b/config/reflect-config.json
new file mode 100644
index 00000000..08f93d7a
--- /dev/null
+++ b/config/reflect-config.json
@@ -0,0 +1,542 @@
+[
+{
+ "name":"io.titandata.models.Commit",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Commit[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.EngineParameters",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.EngineParameters[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.EngineRemote",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.EngineRemote[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Error",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Error[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.NopParameters",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.NopParameters[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.NopRemote",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.NopRemote[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Operation",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Operation$State",
+ "fields":[
+ {"name":"ABORTED"},
+ {"name":"COMPLETE"},
+ {"name":"FAILED"},
+ {"name":"RUNNING"}
+ ]
+},
+{
+ "name":"io.titandata.models.Operation$Type",
+ "fields":[
+ {"name":"PULL"},
+ {"name":"PUSH"}
+ ]
+},
+{
+ "name":"io.titandata.models.Operation[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.PluginDescription",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.PluginDescription[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.ProgressEntry",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.ProgressEntry$Type",
+ "fields":[
+ {"name":"ABORT"},
+ {"name":"COMPLETE"},
+ {"name":"END"},
+ {"name":"ERROR"},
+ {"name":"FAILED"},
+ {"name":"MESSAGE"},
+ {"name":"PROGRESS"},
+ {"name":"START"}
+ ]
+},
+{
+ "name":"io.titandata.models.ProgressEntry[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Remote",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.RemoteParameters",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.RemoteParameters[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Remote[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Repository",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Repository[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.S3Parameters",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.S3Parameters[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.S3Remote",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.S3Remote[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.SshParameters",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.SshParameters[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.SshRemote",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.SshRemote[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Volume",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeCapabilities",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeCapabilitiesResponse",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeCapabilitiesResponse[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeCapabilities[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeCreateRequest",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeCreateRequest[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeGetResponse",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeGetResponse[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeListResponse",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeListResponse[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeMountRequest",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeMountRequest[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumePathResponse",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumePathResponse[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeRequest",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeRequest[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeResponse",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.VolumeResponse[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"io.titandata.models.Volume[]",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+},
+{
+ "name":"java.lang.Throwable",
+ "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }]
+},
+{
+ "name":"java.net.URL",
+ "fields":[
+ {"name":"authority"},
+ {"name":"host"}
+ ]
+},
+{
+ "name":"java.net.URLConnection",
+ "fields":[{"name":"requests"}]
+},
+{
+ "name":"kotlin.jvm.internal.DefaultConstructorMarker"
+},
+{
+ "name":"org.slf4j.impl.NOPLoggerFactory"
+},
+{
+ "name":"sun.misc.Unsafe",
+ "fields":[{"name":"theUnsafe"}],
+ "methods":[{"name":"allocateInstance","parameterTypes":["java.lang.Class"] }]
+},
+{
+ "name":"sun.net.www.MessageHeader",
+ "methods":[{"name":"getHeaders","parameterTypes":[] }]
+},
+{
+ "name":"sun.net.www.protocol.http.HttpURLConnection",
+ "fields":[{"name":"requests"}]
+},
+{
+ "name":"sun.nio.cs.ext.ExtendedCharsets",
+ "allDeclaredMethods":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+}
+]
diff --git a/config/resource-config.json b/config/resource-config.json
new file mode 100644
index 00000000..e705415f
--- /dev/null
+++ b/config/resource-config.json
@@ -0,0 +1,7 @@
+{
+ "resources":[
+ {"pattern":"VERSION"},
+ {"pattern":"sun/net/idn/uidna.spp"},
+ {"pattern":"sun/text/resources/unorm.icu"}
+ ]
+}
diff --git a/java.security.overrides b/java.security.overrides
new file mode 100644
index 00000000..4943b996
--- /dev/null
+++ b/java.security.overrides
@@ -0,0 +1 @@
+security.provider.3=org.bouncycaste.jce.provider.BouncyCastleProvider
\ No newline at end of file
diff --git a/mvnw b/mvnw
new file mode 100755
index 00000000..d2f0ea38
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ 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
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ 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
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 00000000..b26ab24f
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. 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,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..c34d8380
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,218 @@
+
+
+ 4.0.0
+
+ io.titandata
+ titan
+ Titan CLI
+
+ 0.3.0
+
+
+ 1.8
+ 1.3.30
+ 4.12
+ UTF-8
+ io.titandata.titan.Cli
+ 0.8.4
+ 2.1.0
+ 3.14.2
+
+
+
+
+ Apache-2.0
+ https://opensource.org/licenses/Apache-2.0
+
+
+
+
+
+ mcred
+ Derek Smart
+ derek.smart@delphix.com
+
+
+
+
+
+ maven-s3-release-repo
+ https://maven.titan-data.io/
+
+
+ jcenter
+ http://jcenter.bintray.com/
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ com.nhaarman.mockitokotlin2
+ mockito-kotlin
+ ${mockito.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit
+ ${kotlin.version}
+ test
+
+
+ org.json
+ json
+ 20180130
+
+
+ com.github.ajalt
+ clikt
+ 1.7.0
+
+
+ io.github.cdimascio
+ java-dotenv
+ 3.1.7
+
+
+ org.kodein.di
+ kodein-di-generic-jvm
+ 6.0.1
+
+
+ org.kohsuke
+ wordnet-random-name
+ 1.3
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.7
+ maven-plugin
+
+
+ org.apache.commons
+ commons-lang3
+ 3.9
+
+
+ com.squareup.okhttp3
+ okhttp
+ ${okhttp.version}
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ ${okhttp.version}
+
+
+ org.slf4j
+ slf4j-nop
+ 1.5.3
+
+
+ io.titandata
+ titan-client
+ 0.4.1
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/test/kotlin
+
+
+ org.springframework.build
+ aws-maven
+ 5.0.0.RELEASE
+
+
+
+
+ src/main/resources
+ true
+
+ **/VERSION
+
+
+
+ src/main/resources
+ false
+
+ **/VERSION
+
+
+
+
+
+ kotlin-maven-plugin
+ org.jetbrains.kotlin
+ ${kotlin.version}
+
+ ${java.version}
+
+
+
+ compile
+ compile
+
+
+ test-compile
+ test-compile
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ prepare-agent
+
+
+ report
+ test
+
+ report
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.6
+
+
+ make-assembly
+ package
+ single
+
+
+
+ true
+ ${main.class}
+
+
+
+ jar-with-dependencies
+
+
+
+
+
+
+
+
+
diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh
new file mode 100755
index 00000000..10c6dea6
--- /dev/null
+++ b/scripts/build-linux.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -ex
+
+version=`cat VERSION`
+entry="docker run -v ${HOME}/.m2:/root/.m2 -v ${PWD}:/cli --workdir /cli gvm-native:19.0.0"
+docker build -t gvm-native:19.0.0 .
+${entry} ./mvnw jar:jar install:install -DgroupId=org.bouncycastle -DartifactId=bcprov-jdk15on -Dversion=1.62
+${entry} native-image -cp /cli/target/titan-$version-jar-with-dependencies.jar\
+ -H:Name=titan\
+ -H:Class=io.titandata.titan.Cli\
+ -H:+ReportUnsupportedElementsAtRuntime\
+ -H:ReflectionConfigurationFiles=/cli/config/reflect-config.json\
+ -H:ResourceConfigurationFiles=/cli/config/resource-config.json\
+ -H:+AddAllCharsets\
+ --initialize-at-run-time=org.bouncycastle.crypto.prng.SP800SecureRandom\
+ --initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default\
+ --initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV\
+ -J-Djava.security.properties=${PWD}/java.security.overrides\
+ --allow-incomplete-classpath\
+ --enable-http\
+ --enable-https
+
+${entry} mkdir -p /cli/releases/${version}
+${entry} tar -cvf /cli/releases/${version}/titan-cli-$version-linux_amd64.zip titan
\ No newline at end of file
diff --git a/scripts/build-osx.sh b/scripts/build-osx.sh
new file mode 100755
index 00000000..afb887f9
--- /dev/null
+++ b/scripts/build-osx.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -ex
+
+version=`cat VERSION`
+
+./mvnw jar:jar install:install -DgroupId=org.bouncycastle -DartifactId=bcprov-jdk15on -Dversion=1.62
+
+native-image -cp ${PWD}/target/titan-$version-jar-with-dependencies.jar\
+ -H:Name=titan\
+ -H:Class=io.titandata.titan.Cli\
+ -H:+ReportUnsupportedElementsAtRuntime\
+ -H:ReflectionConfigurationFiles=${PWD}/config/reflect-config.json\
+ -H:ResourceConfigurationFiles=${PWD}/config/resource-config.json\
+ -H:+AddAllCharsets\
+ --initialize-at-run-time=org.bouncycastle.crypto.prng.SP800SecureRandom\
+ --initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default\
+ --initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV\
+ -J-Djava.security.properties=${PWD}/java.security.overrides\
+ --allow-incomplete-classpath\
+ --enable-http\
+ --enable-https
+
+mkdir -p ${PWD}/releases/${version}
+zip ${PWD}/releases/${version}/titan-cli-$version-darwin_amd64.zip titan
\ No newline at end of file
diff --git a/scripts/compile-maven.sh b/scripts/compile-maven.sh
new file mode 100755
index 00000000..4f31d909
--- /dev/null
+++ b/scripts/compile-maven.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -ex
+
+version=`cat VERSION`
+
+cp ${PWD}/VERSION ${PWD}/src/main/resources
+
+./mvnw versions:set -DnewVersion=$version
+./mvnw versions:commit
+./mvnw clean install
\ No newline at end of file
diff --git a/scripts/release-prepare.sh b/scripts/release-prepare.sh
new file mode 100755
index 00000000..028a8785
--- /dev/null
+++ b/scripts/release-prepare.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+version=`cat VERSION`
+osx=/releases/${version}/titan-cli-$version-darwin_amd64.zip
+
+if [ ! -f "${PWD}/${osx}" ]; then
+ echo "${osx} does not exist. Use './scripts/build-osx.sh' to create an OSX release."
+ exit 1
+fi
diff --git a/src/main/kotlin/io/titandata/progresstracker/ApplicationProgressTracker.kt b/src/main/kotlin/io/titandata/progresstracker/ApplicationProgressTracker.kt
new file mode 100644
index 00000000..f7532560
--- /dev/null
+++ b/src/main/kotlin/io/titandata/progresstracker/ApplicationProgressTracker.kt
@@ -0,0 +1,8 @@
+package io.titandata.progresstracker
+
+
+object ApplicationProgressTracker {
+ var progressTracker: io.titandata.progresstracker.ProgressTracker = io.titandata.progresstracker.PrintStreamProgressTracker(
+ successMessage = "Success!",
+ failureMessage = "Something failed!")
+}
diff --git a/src/main/kotlin/io/titandata/progresstracker/BlockBasedApi.kt b/src/main/kotlin/io/titandata/progresstracker/BlockBasedApi.kt
new file mode 100644
index 00000000..df08639c
--- /dev/null
+++ b/src/main/kotlin/io/titandata/progresstracker/BlockBasedApi.kt
@@ -0,0 +1,18 @@
+package io.titandata.progresstracker
+
+
+object BlockBasedApi {
+
+ inline fun trackTask(taskName: String, body: () -> T): T {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.pushTask(taskName)
+ return body()
+ }
+
+ inline fun trackLastTask(taskName: String, body: () -> T): T {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.pushTask(taskName)
+ val output = body()
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.end()
+ return output
+ }
+
+}
diff --git a/src/main/kotlin/io/titandata/progresstracker/PrintStreamProgressTracker.kt b/src/main/kotlin/io/titandata/progresstracker/PrintStreamProgressTracker.kt
new file mode 100644
index 00000000..7b84e2d9
--- /dev/null
+++ b/src/main/kotlin/io/titandata/progresstracker/PrintStreamProgressTracker.kt
@@ -0,0 +1,116 @@
+package io.titandata.progresstracker
+
+import java.io.PrintStream
+import kotlin.concurrent.thread
+
+
+private const val DARK_GREEN = "\u001B[0;32m"
+private const val DARK_RED = "\u001B[0;31m"
+private const val BRIGHT_YELLOW = "\u001B[1;33m"
+
+private const val DEFAULT_COLOR = "\u001B[0m"
+
+
+class PrintStreamProgressTracker(
+ private val successMessage: String,
+ private val failureMessage: String = "Failed",
+ private val tasksPrefix: String = "",
+ private val printStream: PrintStream = System.out,
+ private val animation: Array = arrayOf('-', '\\', '|', '/', '-', '\\', '|', '/'),
+ private val refreshRateInMillis: Long = 250L): ProgressTracker {
+
+ private val tasks: MutableList = ArrayList(2)
+ private var clearTasks: Boolean = false
+ private var isMonitorStarted: Boolean = false
+ private val monitor: Thread = createMonitor()
+ private var failedTask: String = ""
+ private var hasFailed = false
+
+ override fun pushTask(taskName: String) {
+ tasks.add(taskName)
+ startMonitorIfNeeded()
+ }
+
+ override fun end() {
+ clearTasks = true
+ monitor.interrupt()
+ }
+
+ override fun close() {
+ end()
+ }
+
+ override fun markAsFailed() {
+ failedTask = tasks.last()
+ hasFailed = true
+ monitor.interrupt()
+ }
+
+ private fun startMonitorIfNeeded() {
+ if (!isMonitorStarted) {
+ isMonitorStarted = true
+ monitor.start()
+ }
+ }
+
+ private fun createMonitor(): Thread {
+ return thread(start = false) {
+ while(tasks.isNotEmpty()) {
+ for(frame in animation) {
+ printCurrentTask(frame)
+ if(shouldBreak()) {
+ break
+ }
+ }
+ }
+ printExitMessage()
+ }
+ }
+
+ private fun printCurrentTask(frame: Char) {
+ if (isCurrentTaskDone()) {
+ printDone(tasks.removeAt(0))
+ } else {
+ printInProgress(frame, tasks[0])
+ }
+ }
+
+ private fun printInProgress(frame: Char, taskName: String) {
+ printStream.print("\r$tasksPrefix$BRIGHT_YELLOW$frame $taskName$DEFAULT_COLOR")
+ }
+
+ private fun printDone(taskName: String) {
+ val taskIsSuccess = failedTask != taskName
+ if (taskIsSuccess)
+ printStream.print("\r$tasksPrefix$DARK_GREEN√ $taskName$DEFAULT_COLOR\r\n")
+ else
+ printStream.print("\r$tasksPrefix${DARK_RED}X $taskName$DEFAULT_COLOR\r\n")
+ }
+
+ private fun waitMillis(millis: Long): Boolean {
+ return try {
+ if(!clearTasks)
+ Thread.sleep(millis)
+ true
+ } catch (ex:InterruptedException) {
+ clearTasks = true
+ false
+ }
+ }
+
+ private fun shouldBreak(): Boolean {
+ return !waitMillis(refreshRateInMillis) || tasks.isEmpty()
+ }
+
+ private fun isCurrentTaskDone(): Boolean {
+ return tasks.size >= 2 || (tasks.size == 1 && clearTasks)
+ }
+
+ private fun printExitMessage() {
+ if(!hasFailed)
+ printStream.println(successMessage)
+ else
+ printStream.println(failureMessage)
+ }
+
+}
diff --git a/src/main/kotlin/io/titandata/progresstracker/ProgressTracker.kt b/src/main/kotlin/io/titandata/progresstracker/ProgressTracker.kt
new file mode 100644
index 00000000..88ea43f1
--- /dev/null
+++ b/src/main/kotlin/io/titandata/progresstracker/ProgressTracker.kt
@@ -0,0 +1,9 @@
+package io.titandata.progresstracker
+
+import java.io.Closeable
+
+interface ProgressTracker: Closeable, AutoCloseable {
+ fun pushTask(taskName: String)
+ fun markAsFailed()
+ fun end()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/progresstracker/StringAndBlockBasedApi.kt b/src/main/kotlin/io/titandata/progresstracker/StringAndBlockBasedApi.kt
new file mode 100644
index 00000000..edfd14c6
--- /dev/null
+++ b/src/main/kotlin/io/titandata/progresstracker/StringAndBlockBasedApi.kt
@@ -0,0 +1,18 @@
+package io.titandata.progresstracker
+
+
+object StringAndBlockBasedApi {
+
+ inline fun String.trackTask(body: () -> T) {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.pushTask(this)
+ body()
+ }
+
+ inline fun String.trackLastTask(body: () -> T) {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.pushTask(this)
+ body()
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.end()
+ }
+
+}
+
diff --git a/src/main/kotlin/io/titandata/progresstracker/StringBasedApi.kt b/src/main/kotlin/io/titandata/progresstracker/StringBasedApi.kt
new file mode 100644
index 00000000..c9761792
--- /dev/null
+++ b/src/main/kotlin/io/titandata/progresstracker/StringBasedApi.kt
@@ -0,0 +1,24 @@
+package io.titandata.progresstracker
+
+
+object StringBasedApi {
+
+ fun String.trackTask(taskName: String = ""): String {
+ if (taskName.isNotEmpty())
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.pushTask(taskName)
+ else
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.pushTask(this)
+ return this
+ }
+
+ fun String.endTask(): String {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.end()
+ return this
+ }
+
+ fun String.markAsFailed(): String {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker.markAsFailed()
+ return this
+ }
+
+}
diff --git a/src/main/kotlin/io/titandata/titan/Cli.kt b/src/main/kotlin/io/titandata/titan/Cli.kt
new file mode 100644
index 00000000..e3f2e0dd
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/Cli.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan
+
+import io.titandata.titan.commands.*
+import io.titandata.titan.providers.ProviderFactory
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.parameters.options.versionOption
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.instance
+import org.kodein.di.generic.setBinding
+
+object Cli {
+ class Titan: CliktCommand(help = "Titan CLI") {
+
+ override fun run() {
+ val providerFactory = ProviderFactory()
+ val provider = providerFactory.getFactory("Local")
+ context.obj = Dependencies(provider)
+ if (context.invokedSubcommand?.commandName != "install") {
+ provider.checkInstall()
+ }
+ }
+ }
+
+ @JvmStatic
+ fun main(args: Array) {
+ val version = Cli::class.java.getResource("/VERSION").readText()
+ val kodein = Kodein {
+ bind() from setBinding()
+ import(installModule)
+ import(runModule)
+ import(cloneModule)
+ import(migrateModule)
+ import(cpModule)
+ import(startModule)
+ import(stopModule)
+ import(commitModule)
+ import(checkoutModule)
+ import(listModule)
+ import(logModule)
+ import(pullModule)
+ import(pushModule)
+ import(abortModule)
+ import(remoteModule)
+ import(statusModule)
+ import(removeModule)
+ import(uninstallModule)
+ import(upgradeModule)
+ }
+ val commands: Set by kodein.instance()
+ Titan().subcommands(commands).versionOption(version).main(args)
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/Dependencies.kt b/src/main/kotlin/io/titandata/titan/Dependencies.kt
new file mode 100644
index 00000000..392d0db0
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/Dependencies.kt
@@ -0,0 +1,5 @@
+package io.titandata.titan
+
+import io.titandata.titan.providers.Provider
+
+data class Dependencies(val provider: Provider)
diff --git a/src/main/kotlin/io/titandata/titan/Version.kt b/src/main/kotlin/io/titandata/titan/Version.kt
new file mode 100644
index 00000000..34164a35
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/Version.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan
+
+data class Version (
+ val major: Int = 0,
+ val minor: Int = 0,
+ val micro: Int = 0,
+ val preRelease: String? = null
+) {
+ companion object {
+ @JvmStatic
+ fun fromString(version: String): Version {
+ val preRelease = if (version.contains("-")) {
+ version.split("-")[1]
+ } else {
+ null
+ }
+ val split = if (version.contains("-")) {
+ version.split("-")[0].split(".")
+ } else {
+ version.split(".")
+ }
+ return Version(
+ split[0].toInt(),
+ split[1].toInt(),
+ split[2].toInt(),
+ preRelease
+ )
+ }
+
+ fun Version.compare(to: Version): Int {
+ if (this.major > to.major) return 1
+ if (this.major < to.major) return -1
+ if (this.minor > to.minor) return 1
+ if (this.minor < to.minor) return -1
+ if (this.micro > to.micro) return 1
+ if (this.micro < to.micro) return -1
+ //TODO compare preRelease tag
+ return 0
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/clients/Docker.kt b/src/main/kotlin/io/titandata/titan/clients/Docker.kt
new file mode 100644
index 00000000..1573ea39
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/clients/Docker.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.clients
+
+import io.titandata.titan.Version
+import io.titandata.titan.Version.Companion.compare
+import io.titandata.titan.utils.CommandExecutor
+import org.json.JSONArray
+import org.json.JSONObject
+import org.kohsuke.randname.RandomNameGenerator
+import kotlin.random.Random
+
+class Docker(private val executor: CommandExecutor) {
+
+ private val titanLaunchArgs = mutableListOf(
+ "--privileged",
+ "--pid=host",
+ "--network=host",
+ "-d",
+ "--restart","always",
+ "--name=titan-launch",
+ "-v", "/var/lib:/var/lib",
+ "-v", "/run/docker:/run/docker",
+ "-v", "/lib:/var/lib/titan/system",
+ "-v", "titan-data:/var/lib/titan/data",
+ "-v", "/var/run/docker.sock:/var/run/docker.sock"
+ )
+
+ fun version(): String {
+ return executor.exec(listOf("docker", "-v")).trim()
+ }
+
+ fun titanIsDownloaded(): Boolean {
+ val images = executor.exec(listOf("docker", "images", "titan", "--format", "\"{{.Repository}}\""))
+ return images.isNotEmpty()
+ }
+
+ fun titanLatestIsDownloaded(titanServerVersion: Version): Boolean {
+ val images= executor.exec(listOf("docker", "images", "titan", "--format", "\"{{.Tag}}\""))
+ val tags = images.split(System.lineSeparator())
+ for (item in tags) {
+ val tag = item.replace("\"", "")
+ if (tag != "latest" && tag != "") {
+ val local = Version.fromString(tag)
+ if (local.compare(titanServerVersion) == 0) return true
+ }
+ }
+ return false
+ }
+
+ fun containerIsRunning(container: String): Boolean {
+ val result = executor.exec(listOf("docker", "ps", "-f", "name=$container", "--format", "\"{{.Names}}\""))
+ return result.isNotEmpty()
+ }
+
+ fun containerIsStopped(container: String): Boolean {
+ val result = executor.exec(listOf("docker", "ps", "-f", "status=exited", "|", "grep", "\"$container\""))
+ return result.isNotEmpty()
+ }
+
+ fun titanLaunchIsAvailable():Boolean {
+ return containerIsRunning("titan-launch")
+ }
+
+ fun titanLaunchIsStopped(): Boolean {
+ return containerIsStopped("titan-launch")
+ }
+
+ fun titanServerIsAvailable(): Boolean {
+ return containerIsRunning("titan-server")
+ }
+
+ fun titanServerIsStopped(): Boolean {
+ return containerIsStopped("titan-server")
+ }
+ fun launchTitanServers(): String {
+ titanLaunchArgs.add("-e")
+ titanLaunchArgs.add("TITAN_IMAGE=titan:latest")
+ return run("titan:latest", "/bin/bash /titan/launch", titanLaunchArgs)
+ }
+
+ fun teardownTitanServers(): String {
+ titanLaunchArgs.removeAt(titanLaunchArgs.indexOf("-d"))
+ titanLaunchArgs.removeAt(titanLaunchArgs.indexOf("--restart"))
+ titanLaunchArgs.removeAt(titanLaunchArgs.indexOf("always"))
+ titanLaunchArgs.removeAt(titanLaunchArgs.indexOf("--name=titan-launch"))
+ titanLaunchArgs.add("--rm")
+ return run("titan:latest", "/bin/bash /titan/teardown", titanLaunchArgs)
+ }
+
+ fun pull(image: String): String {
+ return executor.exec(listOf("docker", "pull", image)).trim()
+ }
+
+ fun tag(source: String, target: String): String {
+ return executor.exec(listOf("docker", "tag", source, target)).trim()
+ }
+
+ fun rm(container: String, force: Boolean): String {
+ val forceFlag = if(force) "-f" else ""
+ val args = mutableListOf("docker", "rm", forceFlag)
+ args.addAll(container.toList())
+ return executor.exec(args).trim()
+ }
+
+ fun rmStopped(container:String): String {
+ val containerId = executor.exec(listOf("docker", "ps", "-f", "status=exited", "-f", "name=$container", "--format", "{{.ID}}")).trim()
+ return executor.exec(listOf("docker", "container", "rm", containerId))
+ }
+
+ fun run(image: String, entry: String, arguments: List): String {
+ val mutArgs = mutableListOf("docker", "run")
+ mutArgs.addAll(arguments)
+ mutArgs.addAll(image.toList())
+ if (entry.isNotEmpty()) mutArgs.addAll(entry.toList())
+ return executor.exec(mutArgs)
+ }
+
+ fun inspectContainer(container: String): JSONObject? {
+ val results = executor.exec(listOf("docker", "inspect", "--type", "container", container))
+ return JSONArray(results).optJSONObject(0)
+ }
+
+ fun inspectImage(image: String): JSONObject? {
+ val results = executor.exec(listOf("docker", "inspect", "--type", "image", image))
+ return JSONArray(results).optJSONObject(0)
+ }
+
+ fun createVolume(name: String, path: String, driver: String = "titan"): String {
+ return executor.exec(listOf("docker", "volume", "create", "-d", driver, "-o", "path=$path", name))
+ }
+
+ fun removeVolume(name: String): String {
+ return executor.exec(listOf("docker", "volume", "rm", name))
+ }
+
+ fun cp(source: String, target: String): String {
+ return executor.exec(listOf("docker", "cp", "-a", "$source/.", "titan-server:/var/lib/titan/mnt/$target"))
+ }
+
+ fun stop(container: String): String {
+ return executor.exec(listOf("docker", "stop", container))
+ }
+
+ fun start(container: String): String {
+ return executor.exec(listOf("docker", "start", container))
+ }
+
+ companion object {
+ fun String.toList(delimiter: String = " "): List {
+ return this.split(delimiter)
+ }
+
+ fun String.runtimeToArguments(): List {
+ val arguments = this.removePrefix("[").removeSuffix("]").toList(", ").toMutableList()
+ if (arguments.contains("--mount")) {
+ arguments.removeAt((arguments.indexOf("--mount") + 1))
+ arguments.removeAt(arguments.indexOf("--mount"))
+ }
+ return arguments
+ }
+
+ fun List.fetchName(): String {
+ return when {
+ this.contains("--name") -> this[(this.indexOf("--name")+1)]
+ else -> RandomNameGenerator(Random.nextInt()).next()
+ }
+ }
+
+ fun List.hasDetach(): Boolean {
+ return when {
+ this.contains("-d") -> true
+ this.contains("--detach") -> true
+ else -> false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/clients/VolumeDriver.kt b/src/main/kotlin/io/titandata/titan/clients/VolumeDriver.kt
new file mode 100644
index 00000000..7a529921
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/clients/VolumeDriver.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.clients
+
+import io.titandata.titan.utils.HttpHandler
+import io.titandata.titan.utils.HttpHandler.Companion.asString
+import org.json.JSONObject
+
+class VolumeDriver(private val httpHandler: HttpHandler, private val volumeDriverUrl: String = "http://localhost:5000"){
+
+ fun list(): JSONObject {
+ val response = httpHandler.post("$volumeDriverUrl/VolumeDriver.List", emptyMap()).asString()
+ return JSONObject(response)
+ }
+
+ fun mount(volumeName: String): JSONObject {
+ val response = httpHandler.post("$volumeDriverUrl/VolumeDriver.Mount", mapOf("Name" to volumeName)).asString()
+ return JSONObject(response)
+ }
+
+ fun unmount(volumeName: String): JSONObject {
+ val response = httpHandler.post("$volumeDriverUrl/VolumeDriver.Unmount", mapOf("Name" to volumeName)).asString()
+ return JSONObject(response)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/commands/Abort.kt b/src/main/kotlin/io/titandata/titan/commands/Abort.kt
new file mode 100644
index 00000000..cc703191
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Abort.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Abort : CliktCommand(help = "Abort current push or pull operation") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository : String by argument()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.abort(repository)
+ }
+}
+
+val abortModule = Kodein.Module("abort") {
+ bind().inSet() with provider {
+ Abort()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Checkout.kt b/src/main/kotlin/io/titandata/titan/commands/Checkout.kt
new file mode 100644
index 00000000..b1f6eb8c
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Checkout.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Checkout : CliktCommand(help = "Checkout a specific commit") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ private val commit by option("-c", "--commit", help = "Required. Commit GUID to checkout").required()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.checkout(repository, commit)
+ }
+}
+
+val checkoutModule = Kodein.Module("checkout") {
+ bind().inSet() with provider {
+ Checkout()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Clone.kt b/src/main/kotlin/io/titandata/titan/commands/Clone.kt
new file mode 100644
index 00000000..fa085dbf
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Clone.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.arguments.optional
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Clone : CliktCommand(help = "Clone a remote repository to local repository") {
+ private val dependencies: Dependencies by requireObject()
+ private val uri by argument()
+ private val repository by argument().optional()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.clone(uri, repository)
+ }
+}
+
+val cloneModule = Kodein.Module("clone") {
+ bind().inSet() with provider {
+ Clone()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Commit.kt b/src/main/kotlin/io/titandata/titan/commands/Commit.kt
new file mode 100644
index 00000000..3a86661e
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Commit.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.option
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Commit : CliktCommand(help = "Commit current data state") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ private val message by option("-m", "--message", help="Commit message").default("")
+ override fun run() {
+ val provider = dependencies.provider
+ provider.commit(repository, message)
+ }
+}
+
+val commitModule = Kodein.Module("commit") {
+ bind().inSet() with provider {
+ Commit()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Cp.kt b/src/main/kotlin/io/titandata/titan/commands/Cp.kt
new file mode 100644
index 00000000..cab315ac
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Cp.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Cp : CliktCommand(
+ help = "Copy data into a repository",
+ epilog = "If the container for the repository has only one volume, data will be copied there. If there are multiple volumes, the destination path must be specified."
+) {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ private val source by option("-s", "--source", help="Required. Source location of the files on the local machine").required()
+ private val destination by option("-d", "--destination", help="Destination of the files inside of the container").default("")
+ override fun run() {
+ val provider = dependencies.provider
+ provider.cp(repository, "local", source, destination)
+ }
+}
+
+val cpModule = Kodein.Module("cp") {
+ bind().inSet() with provider {
+ Cp()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Install.kt b/src/main/kotlin/io/titandata/titan/commands/Install.kt
new file mode 100644
index 00000000..fcd36b11
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Install.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Install : CliktCommand(help = "Install titan infrastructure") {
+ private val dependencies: Dependencies by requireObject()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.install()
+ }
+}
+
+val installModule = Kodein.Module("install") {
+ bind().inSet() with provider {
+ Install()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/List.kt b/src/main/kotlin/io/titandata/titan/commands/List.kt
new file mode 100644
index 00000000..f3d92c15
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/List.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class List : CliktCommand(help = "List repositories", name = "ls") {
+ private val dependencies: Dependencies by requireObject()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.list()
+ }
+}
+
+val listModule = Kodein.Module("ls") {
+ bind().inSet() with provider {
+ List()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Log.kt b/src/main/kotlin/io/titandata/titan/commands/Log.kt
new file mode 100644
index 00000000..f9711fcc
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Log.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Log : CliktCommand(help = "List commits for a repository") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.log(repository)
+ }
+}
+
+val logModule = Kodein.Module("log") {
+ bind().inSet() with provider {
+ Log()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Migrate.kt b/src/main/kotlin/io/titandata/titan/commands/Migrate.kt
new file mode 100644
index 00000000..ed01f8f1
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Migrate.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Migrate : CliktCommand(
+ help = "Migrate an existing docker database container to titan repository",
+ epilog = "Container becomes the new name of the docker container.\n\nExample: `titan migrate -s oldPostgres titanPostgres`"
+) {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ private val source by option("-s", "--source", help="Required. Source docker database container").required()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.migrate(source, repository)
+ }
+}
+
+val migrateModule = Kodein.Module("migrate") {
+ bind().inSet() with provider {
+ Migrate()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Pull.kt b/src/main/kotlin/io/titandata/titan/commands/Pull.kt
new file mode 100644
index 00000000..8e756ea0
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Pull.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.option
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Pull : CliktCommand(help = "Pull a new data state from remote") {
+ private val dependencies: Dependencies by requireObject()
+ private val commit by option("-c", "--commit", help="Commit GUID to pull from, defaults to latest")
+ private val remote: String? by option("-r", "--remote", help="Name of the remote provider, defaults to origin")
+ private val repository by argument()
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.pull(repository, commit, remote)
+ }
+}
+
+val pullModule = Kodein.Module("pull") {
+ bind().inSet() with provider {
+ Pull()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Push.kt b/src/main/kotlin/io/titandata/titan/commands/Push.kt
new file mode 100644
index 00000000..36a49934
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Push.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.option
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Push : CliktCommand(help = "Push data state to remote") {
+ private val dependencies: Dependencies by requireObject()
+ private val commit : String? by option("-c", "--commit", help="Commit GUID to push, defaults to latest")
+ private val remote: String? by option("-r", "--remote", help="Name of the remote provider, defaults to origin")
+ private val repository : String by argument()
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.push(repository, commit, remote)
+ }
+}
+
+val pushModule = Kodein.Module("push") {
+ bind().inSet() with provider {
+ Push()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Remote.kt b/src/main/kotlin/io/titandata/titan/commands/Remote.kt
new file mode 100644
index 00000000..11b31276
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Remote.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Remote : CliktCommand(help = "Add, log, ls and rm remotes") {
+ override fun run() {}
+}
+
+class RemoteAdd : CliktCommand(help = "Set remote destination for a repository", name = "add") {
+ private val dependencies: Dependencies by requireObject()
+ private val remote: String? by option("-r", "--remote", help="Name of the remote provider, defaults to origin")
+ private val uri: String by argument()
+ private val repository: String by argument()
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.remoteAdd(repository, uri, remote)
+ }
+}
+
+class RemoteLog : CliktCommand(help = "Display log on remote", name = "log") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository: String by argument()
+ private val remote: String? by option("-r", "--remote", help="Name of the remote provider, defaults to origin")
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.remoteLog(repository, remote)
+ }
+}
+
+class RemoteList: CliktCommand(help = "List remotes for a repository", name = "ls") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository: String by argument()
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.remoteList(repository)
+ }
+}
+
+class RemoteRemove: CliktCommand(help = "Remove remote from a repository", name = "rm") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository: String by argument()
+ private val remote: String by argument()
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.remoteRemove(repository, remote)
+ }
+}
+
+val remoteModule = Kodein.Module("remote") {
+ bind().inSet() with provider {
+ Remote().subcommands(
+ RemoteAdd(),
+ RemoteLog(),
+ RemoteList(),
+ RemoteRemove()
+ )
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Remove.kt b/src/main/kotlin/io/titandata/titan/commands/Remove.kt
new file mode 100644
index 00000000..4a626c22
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Remove.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Remove : CliktCommand(help = "Remove a repository", name = "rm") {
+ private val force by option("-f", "--force", help="Stop container if running").flag(default=false)
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.remove(repository, force)
+ }
+}
+
+val removeModule = Kodein.Module("rm") {
+ bind().inSet() with provider {
+ Remove()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Run.kt b/src/main/kotlin/io/titandata/titan/commands/Run.kt
new file mode 100644
index 00000000..9388b852
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Run.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.arguments.multiple
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Run : CliktCommand(
+ help = "Create repository and start container",
+ epilog = "Containers that contain a repository are launched using docker run arguments and passed verbatim using `--` as the flag.\n\nExample: `titan run -- --name newRepo -d -p 5432:5432 postgres:10`"
+) {
+ private val dependencies: Dependencies by requireObject()
+ private val arguments by argument().multiple()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.run(arguments)
+ }
+}
+
+val runModule = Kodein.Module("run") {
+ bind().inSet() with provider {
+ Run()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Start.kt b/src/main/kotlin/io/titandata/titan/commands/Start.kt
new file mode 100644
index 00000000..7ca37811
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Start.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Start : CliktCommand(help = "Start a container for a repository") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.start(repository)
+ }
+}
+
+val startModule = Kodein.Module("start") {
+ bind().inSet() with provider {
+ Start()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Status.kt b/src/main/kotlin/io/titandata/titan/commands/Status.kt
new file mode 100644
index 00000000..7eb7fa1b
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Status.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Status : CliktCommand(help = "Display current status for a repository") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository: String by argument()
+
+ override fun run() {
+ val provider = dependencies.provider
+ provider.status(repository)
+ }
+}
+
+val statusModule = Kodein.Module("status") {
+ bind().inSet() with provider {
+ Status()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Stop.kt b/src/main/kotlin/io/titandata/titan/commands/Stop.kt
new file mode 100644
index 00000000..f1671827
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Stop.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Stop : CliktCommand(help = "Stop a running container for a repository") {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.stop(repository)
+ }
+}
+
+val stopModule = Kodein.Module("stop") {
+ bind().inSet() with provider {
+ Stop()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Uninstall.kt b/src/main/kotlin/io/titandata/titan/commands/Uninstall.kt
new file mode 100644
index 00000000..c3f28cde
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Uninstall.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Uninstall : CliktCommand(help = "Uninstall titan infrastructure") {
+ private val force by option("-f", "--force", help="Destroy all repositories").flag(default=false)
+ private val dependencies: Dependencies by requireObject()
+ override fun run() {
+ val provider = dependencies.provider
+ provider.uninstall(force)
+ }
+}
+
+val uninstallModule = Kodein.Module("uninstall") {
+ bind().inSet() with provider {
+ Uninstall()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Upgrade.kt b/src/main/kotlin/io/titandata/titan/commands/Upgrade.kt
new file mode 100644
index 00000000..b6d16c2d
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Upgrade.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.commands
+
+import io.titandata.titan.Dependencies
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.requireObject
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import org.kodein.di.Kodein
+import org.kodein.di.generic.bind
+import org.kodein.di.generic.inSet
+import org.kodein.di.generic.provider
+
+class Upgrade : CliktCommand(help = "Upgrade titan CLI and infrastructure") {
+ private val dependencies: Dependencies by requireObject()
+ private val force by option("-f", "--force", help="Stop running containers").flag(default = false)
+ private val version = Upgrade::class.java.getResource("/VERSION").readText()
+ private val finalize by option("--finalize").flag(default = false)
+ private val path by option("-p", "--path", help="Full installation path of Titan").default("")
+ override fun run() {
+ val provider = dependencies.provider
+
+ provider.upgrade(force, version, finalize, path)
+ }
+}
+
+val upgradeModule = Kodein.Module("upgrade") {
+ bind().inSet() with provider {
+ Upgrade()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/exceptions/CommandException.kt b/src/main/kotlin/io/titandata/titan/exceptions/CommandException.kt
new file mode 100644
index 00000000..8a90050f
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/exceptions/CommandException.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.exceptions
+
+import java.io.IOException
+
+class CommandException(message: String, val exitCode: Int, val output: String) : IOException(message)
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/Local.kt b/src/main/kotlin/io/titandata/titan/providers/Local.kt
new file mode 100644
index 00000000..adf70687
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/Local.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers
+
+import io.titandata.client.apis.RepositoriesApi
+import kotlin.system.exitProcess
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.exceptions.CommandException
+import io.titandata.titan.utils.CommandExecutor
+import io.titandata.titan.utils.HttpHandler
+import io.titandata.titan.providers.local.*
+
+data class Container (
+ val name: String,
+ val status: String
+)
+
+class Local: Provider {
+ private val titanServerVersion = "0.4.1"
+ private val dockerRegistryUrl = "titandata"
+
+ private val httpHandler = HttpHandler()
+ private val commandExecutor = CommandExecutor()
+ private val docker = Docker(commandExecutor)
+ private val repositoriesApi = RepositoriesApi()
+
+ private fun exit(message:String, code: Int = 1) {
+ println(message)
+ exitProcess(code)
+ }
+
+ private fun getContainersStatus(): List {
+ val returnList = mutableListOf()
+ val repositories = repositoriesApi.listRepositories()
+ for (repo in repositories) {
+ val container = repo.name
+ val containerInfo = docker.inspectContainer(container)
+ var status = "unknown"
+ if (containerInfo != null) {
+ status = containerInfo.getJSONObject("State").getString("Status")
+ }
+ returnList.add(Container(container, status))
+ }
+ return returnList
+ }
+
+ override fun upgrade(force: Boolean, version: String, finalize: Boolean, path: String?) {
+ val upgradeCommand = Upgrade(::start, ::stop, ::exit, ::getContainersStatus, commandExecutor, httpHandler)
+ return upgradeCommand.upgrade(force, version, finalize, path)
+ }
+
+ override fun pull(container: String, commit: String?, remoteName: String?) {
+ val pullCommand = Pull(::exit)
+ return pullCommand.pull(container, commit, remoteName)
+ }
+
+ override fun push(container: String, commit: String?, remoteName: String?) {
+ val pushCommand = Push(::exit)
+ return pushCommand.push(container, commit, remoteName)
+ }
+
+ override fun checkInstall() {
+ val checkInstallCommand = CheckInstall(::exit, commandExecutor, docker)
+ return checkInstallCommand.checkInstall()
+ }
+
+ override fun install() {
+ val installCommand = Install(titanServerVersion, dockerRegistryUrl, commandExecutor, docker)
+ return installCommand.install()
+ }
+
+ override fun commit(container: String, message: String) {
+ try {
+ val user= commandExecutor.exec(listOf("git", "config", "user.name")).trim()
+ val email = commandExecutor.exec(listOf("git", "config", "user.email")).trim()
+ val commitCommand = Commit(user, email)
+ return commitCommand.commit(container, message)
+ } catch (e: CommandException) {
+ exit("Git not configured.")
+ }
+ }
+
+ override fun abort(container: String) {
+ val abortCommand = Abort(::exit)
+ return abortCommand.abort(container)
+ }
+
+ override fun status(container: String) {
+ val statusCommand = Status(::getContainersStatus)
+ return statusCommand.status(container)
+ }
+
+ override fun remoteAdd(container:String, uri: String, remoteName: String?) {
+ val remoteAddCommand = RemoteAdd(::exit)
+ return remoteAddCommand.remoteAdd(container, uri, remoteName)
+ }
+
+ override fun remoteLog(container:String, remoteName: String?) {
+ val remoteLogCommand = RemoteLog(::exit)
+ return remoteLogCommand.remoteLog(container, remoteName)
+ }
+
+ override fun remoteList(container: String) {
+ val remoteListCommand = RemoteList()
+ return remoteListCommand.list(container)
+ }
+
+ override fun remoteRemove(container: String, remote: String) {
+ val remoteRemoveCommand = RemoteRemove()
+ return remoteRemoveCommand.remove(container, remote)
+ }
+
+ override fun migrate(container: String, name: String) {
+ val migrateCommand = Migrate(::exit, ::commit, commandExecutor, docker)
+ return migrateCommand.migrate(container, name)
+ }
+
+ override fun run(arguments: List) {
+ val runCommand = Run(::exit, commandExecutor, docker)
+ return runCommand.run(arguments)
+ }
+
+ override fun list() {
+ System.out.printf("%-20s %s\n", "CONTAINER", "STATUS")
+ for (container in getContainersStatus()) {
+ System.out.printf("%-20s %s\n", container.name, container.status)
+ }
+ }
+
+ override fun uninstall(force: Boolean) {
+ val uninstallCommand = Uninstall(::exit, ::remove, commandExecutor, docker)
+ return uninstallCommand.uninstall(force)
+ }
+
+ override fun checkout(container: String, guid: String) {
+ val checkoutCommand = Checkout(commandExecutor, docker)
+ return checkoutCommand.checkout(container, guid)
+ }
+
+ override fun log(container: String) {
+ val logCommand = Log()
+ return logCommand.log(container)
+ }
+
+ override fun stop(container: String) {
+ val stopCommand = Stop(commandExecutor, docker)
+ return stopCommand.stop(container)
+ }
+
+ override fun start(container: String) {
+ val startCommand = Start(commandExecutor, docker)
+ return startCommand.start(container)
+ }
+
+ override fun remove(container: String, force: Boolean) {
+ val removeCommand = Remove(::exit, commandExecutor, docker)
+ return removeCommand.remove(container, force)
+ }
+
+ override fun cp(container: String, driver: String, source: String, path: String) {
+ val cpCommand = Cp(::exit, ::start, ::stop, commandExecutor, docker)
+ return cpCommand.cp(container, driver, source, path)
+ }
+
+ override fun clone(uri: String, container: String?) {
+ val runCommand = Run(::exit, commandExecutor, docker)
+ val cloneCommand = Clone(::remoteAdd, ::pull, ::checkout, runCommand::run, commandExecutor, docker)
+ return cloneCommand.clone(uri, container)
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/providers/Mock.kt b/src/main/kotlin/io/titandata/titan/providers/Mock.kt
new file mode 100644
index 00000000..ed057c9f
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/Mock.kt
@@ -0,0 +1,95 @@
+package io.titandata.titan.providers
+
+class Mock: Provider {
+ override fun checkInstall() {
+ println("Mock Provider Installed")
+ }
+
+ override fun pull(container: String, commit: String?, remoteName: String?) {
+ println("Pulling from remote")
+ }
+
+ override fun push(container: String, commit: String?, remoteName: String?) {
+ println("Pushing to remote")
+ }
+
+ override fun commit(container: String, message: String) {
+ println("Committing new state")
+ }
+
+ override fun install() {
+ println("Initializing new repository")
+ }
+
+ override fun abort(container: String) {
+ println("Aborting current operation")
+ }
+
+ override fun status(container: String) {
+ println("Display current status")
+ }
+
+ override fun remoteAdd(container: String, uri: String, remoteName: String?) {
+ println("Add remote")
+ }
+
+ override fun remoteLog(container: String, remoteName: String?) {
+ println("Display remote log")
+ }
+
+ override fun remoteList(container: String) {
+ println("Display list of remotes")
+ }
+
+ override fun remoteRemove(container: String, remote: String) {
+ println("Removing $remote for $container")
+ }
+
+ override fun migrate(container: String, name: String) {
+ println("Migrating $container to $name controlled environment")
+ }
+
+ override fun run(arguments: List) {
+ println("Running controlled container")
+ }
+
+ override fun uninstall(force: Boolean) {
+ println("Tearing down containers")
+ }
+
+ override fun upgrade(force: Boolean, version: String, finalize: Boolean, path: String?) {
+ println("Upgrading to $version")
+ }
+
+ override fun checkout(container: String, guid: String) {
+ println("Checking out data set $guid")
+ }
+
+ override fun list() {
+ println("List containers")
+ }
+
+ override fun log(container: String) {
+ println("Log for $container")
+ }
+
+ override fun stop(container: String) {
+ println("Stopping $container")
+ }
+
+ override fun start(container: String) {
+ println("Starting $container")
+ }
+
+ override fun remove(container: String, force: Boolean) {
+ println("Removing $container")
+ }
+
+ override fun cp(container: String, driver: String, source: String, path: String) {
+ println("copying data into $container with $driver from $source")
+ }
+
+ override fun clone(uri: String, container: String?) {
+ println("cloning $container from $uri")
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/providers/Provider.kt b/src/main/kotlin/io/titandata/titan/providers/Provider.kt
new file mode 100644
index 00000000..77e9bec4
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/Provider.kt
@@ -0,0 +1,27 @@
+package io.titandata.titan.providers
+
+interface Provider {
+ fun checkInstall()
+ fun pull(container: String, commit: String?, remoteName: String?)
+ fun push(container: String, commit: String?, remoteName: String?)
+ fun commit(container: String, message: String)
+ fun install()
+ fun abort(container: String)
+ fun status(container: String)
+ fun remoteAdd(container: String, uri: String, remoteName: String?)
+ fun remoteLog(container: String, remoteName: String?)
+ fun remoteList(container:String)
+ fun remoteRemove(container: String, remote:String)
+ fun migrate(container: String, name: String)
+ fun run(arguments: List)
+ fun uninstall(force: Boolean)
+ fun upgrade(force: Boolean, version: String, finalize: Boolean, path: String?)
+ fun checkout(container: String, guid: String)
+ fun list()
+ fun log(container: String)
+ fun stop(container: String)
+ fun start(container: String)
+ fun remove(container: String, force: Boolean)
+ fun cp(container: String, driver: String, source: String, path: String)
+ fun clone(uri: String, container: String?)
+}
diff --git a/src/main/kotlin/io/titandata/titan/providers/ProviderFactory.kt b/src/main/kotlin/io/titandata/titan/providers/ProviderFactory.kt
new file mode 100644
index 00000000..fd83cf95
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/ProviderFactory.kt
@@ -0,0 +1,10 @@
+package io.titandata.titan.providers
+
+class ProviderFactory {
+ fun getFactory(name: String): Provider {
+ return when(name) {
+ "Local" -> Local()
+ else -> Mock()
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Abort.kt b/src/main/kotlin/io/titandata/titan/providers/local/Abort.kt
new file mode 100644
index 00000000..3120a04d
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Abort.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.OperationsApi
+import io.titandata.client.infrastructure.ClientException
+import io.titandata.models.Operation
+
+class Abort (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val operationsApi: OperationsApi = OperationsApi()
+) {
+ fun abort(container: String) {
+ try {
+ val operations = operationsApi.listOperations(container)
+ var abortCount = 0
+ for (operation in operations) {
+ if (operation.state == Operation.State.RUNNING) {
+ println("aborting operation ${operation.id}")
+ operationsApi.deleteOperation(container, operation.id)
+ abortCount++
+ }
+ }
+ if (abortCount == 0) {
+ exit("no operation in progress", 0)
+ }
+
+ } catch (e: ClientException) {
+ exit(e.message!!,1)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/CheckInstall.kt b/src/main/kotlin/io/titandata/titan/providers/local/CheckInstall.kt
new file mode 100644
index 00000000..43e14502
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/CheckInstall.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+
+class CheckInstall (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor)
+) {
+
+ fun checkInstall() {
+ try {
+ docker.version()
+ } catch (e: Exception) {
+ exit("Docker not found, install docker and run 'install' to configured required infrastructure", 1)
+ }
+ if (!docker.titanIsDownloaded()) {
+ exit("Titan is not configured, run 'install' to configure required infrastructure", 1)
+ }
+ if (!docker.titanLaunchIsAvailable()) {
+ exit("Titan is not configured, run 'install' to configure required infrastructure", 1)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Checkout.kt b/src/main/kotlin/io/titandata/titan/providers/local/Checkout.kt
new file mode 100644
index 00000000..c46d0845
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Checkout.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+
+class Checkout (
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val commitsApi: CommitsApi = CommitsApi()
+) {
+ fun checkout(container: String, guid: String) {
+ println("Stopping container $container")
+ docker.stop(container)
+ println("Checkout $guid")
+ commitsApi.checkoutCommit(container, guid)
+ println("Starting container $container")
+ docker.start(container)
+ println("$guid checked out")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt b/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt
new file mode 100644
index 00000000..41a2331f
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RemotesApi
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.clients.Docker.Companion.runtimeToArguments
+import io.titandata.models.Repository
+import io.titandata.titan.utils.CommandExecutor
+import io.titandata.serialization.RemoteUtil
+
+class Clone (
+ private val remoteAdd: (container:String, uri: String, remoteName: String?) -> Unit,
+ private val pull: (container: String, commit: String?, remoteName: String?) -> Unit,
+ private val checkout: (container: String, hash: String) -> Unit,
+ private val run: (arguments: List, createRepo: Boolean) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val remotesApi: RemotesApi = RemotesApi(),
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi(),
+ private val remoteUtil: RemoteUtil = RemoteUtil()
+) {
+ fun clone(uri: String, container: String?) {
+ val repoName = when(container){
+ null -> uri.split("/").last()
+ else -> container
+ }
+ val repository = Repository(repoName, emptyMap())
+ repositoriesApi.createRepository(repository)
+ remoteAdd(repoName, uri, null)
+ val remote = remotesApi.getRemote(repoName, "origin")
+ val remoteCommits = remotesApi.listRemoteCommits(repoName, remote.name, remoteUtil.getParameters(remote))
+ val commit = remoteCommits.first()
+ docker.pull(commit.properties["container"] as String)
+ val runtime = commit.properties["runtime"] as String
+ val arguments = runtime.runtimeToArguments().toMutableList()
+ arguments[arguments.indexOf("--name") + 1] = repoName
+ arguments.add(commit.properties["container"] as String)
+ run(arguments, false)
+ pull(repoName, commit.id, null)
+ checkout(repoName, commit.id)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Commit.kt b/src/main/kotlin/io/titandata/titan/providers/local/Commit.kt
new file mode 100644
index 00000000..39705b6a
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Commit.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.models.Commit
+import java.util.*
+
+class Commit (
+ private val user: String,
+ private val email: String,
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi(),
+ private val commitsApi: CommitsApi = CommitsApi(),
+ private val uuid: String = UUID.randomUUID().toString()
+) {
+ fun commit(container: String, message: String) {
+ val repoMetadata = repositoriesApi.getRepository(container).properties
+ val metadata = mapOf(
+ "user" to user,
+ "email" to email,
+ "message" to message!!,
+ "container" to repoMetadata["container"]!!,
+ "runtime" to repoMetadata["runtime"]!!
+ )
+ val commit = Commit(uuid, metadata)
+ val response = commitsApi.createCommit(container, commit)
+ val hash = response.id
+ println("Commit $hash")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Cp.kt b/src/main/kotlin/io/titandata/titan/providers/local/Cp.kt
new file mode 100644
index 00000000..d8fcac46
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Cp.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.VolumeApi
+import io.titandata.titan.clients.Docker
+import io.titandata.models.VolumeMountRequest
+import io.titandata.titan.utils.CommandExecutor
+import org.json.JSONObject
+
+class Cp (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val start: (container: String) -> Unit,
+ private val stop: (container: String) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val volumeApi: VolumeApi = VolumeApi()
+) {
+ fun cp(container: String, driver: String, source: String, path: String) {
+ var mutablePath = path
+ val containerInfo = docker.inspectContainer(container)
+ if (containerInfo == null) {
+ exit("Container information is not available",1)
+ }
+ val running = containerInfo!!.getJSONObject("State").getBoolean("Running")
+ if(running) {
+ stop(container)
+ }
+ val mounts = containerInfo.getJSONObject("HostConfig").optJSONArray("Mounts")
+ if (mounts.count() > 1 && mutablePath.isEmpty()) {
+ exit("$container has more than 1 volume mount. --path is required.",1)
+ }
+ if (mutablePath.isEmpty()) {
+ val mount = mounts[0] as JSONObject
+ mutablePath = mount.getString("Target")
+ }
+ for (item in mounts) {
+ val mount = item as JSONObject
+ if (mount.optString("Target") == mutablePath) {
+ val volumeName = mount.optString("Source")
+ println("Copying data to $volumeName")
+ val volMountRequest = VolumeMountRequest(volumeName, "")
+ volumeApi.mountVolume(volMountRequest)
+ /*
+ TODO add multiple cp sources
+ when(driver) {
+ else -> docker.cp(source.removeSuffix("/"), volumeName)
+
+ }
+ */
+ docker.cp(source.removeSuffix("/"), volumeName)
+
+ volumeApi.unmountVolume(volMountRequest)
+ }
+ }
+ if (running) {
+ start(container)
+ }
+ println("$container running with data from $source")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Install.kt b/src/main/kotlin/io/titandata/titan/providers/local/Install.kt
new file mode 100644
index 00000000..57b67a77
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Install.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.progresstracker.PrintStreamProgressTracker
+import io.titandata.progresstracker.ProgressTracker
+import io.titandata.progresstracker.StringBasedApi.markAsFailed
+import io.titandata.progresstracker.StringBasedApi.trackTask
+import io.titandata.titan.Version
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+import kotlin.system.exitProcess
+
+class Install (
+ private val titanServerVersion: String,
+ private val dockerRegistryUrl: String,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor)
+) {
+
+ private fun createProgressTracker(): ProgressTracker {
+ io.titandata.progresstracker.ApplicationProgressTracker.progressTracker = PrintStreamProgressTracker(
+ tasksPrefix = "\t",
+ successMessage = "Titan cli successfully installed, happy data versioning :)",
+ failureMessage = "Docker not found")
+ return io.titandata.progresstracker.ApplicationProgressTracker.progressTracker
+ }
+
+ fun install() {
+ createProgressTracker().use {
+ println("Initializing titan infrastructure ...")
+ try {
+ "Checking docker installation".trackTask()
+ docker.version()
+ } catch (e: Exception) {
+ "Checking docker installation".markAsFailed()
+ exitProcess(2)
+ }
+ if(!docker.titanLatestIsDownloaded(Version.fromString(titanServerVersion))) {
+ "Pulling titan docker image (may take a while)".trackTask()
+ docker.pull("${dockerRegistryUrl}/titan:$titanServerVersion")
+ "Tagging titan docker image".trackTask()
+ docker.tag("${dockerRegistryUrl}/titan:$titanServerVersion", "titan:$titanServerVersion")
+ "Tagging latest titan".trackTask()
+ docker.tag("${dockerRegistryUrl}/titan:$titanServerVersion", "titan")
+ }
+ if (docker.titanServerIsAvailable()) {
+ "Removing stale containers".trackTask()
+ docker.rm("titan-server", true)
+ }
+ if (docker.titanLaunchIsAvailable()) {
+ "Removing stale containers".trackTask()
+ docker.rm("titan-launch", true)
+ }
+ "Starting titan server docker containers".trackTask()
+ docker.launchTitanServers()
+ }
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Log.kt b/src/main/kotlin/io/titandata/titan/providers/local/Log.kt
new file mode 100644
index 00000000..31a02ae5
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Log.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+
+class Log (
+ private val commitsApi: CommitsApi = CommitsApi()
+) {
+ fun log(container: String) {
+ var first = true
+ for (commit in commitsApi.listCommits(container)) {
+ if (!first) {
+ println("")
+ } else {
+ first = false
+ }
+ val metadata = commit.properties
+ println("commit ${commit.id}")
+ if (metadata.containsKey("author")) {
+ println("Author: ${metadata["author"]}")
+ }
+ if (metadata.containsKey("user")) {
+ println("User: ${metadata["user"]}")
+ }
+ if (metadata.containsKey("email")) {
+ println("Email: ${metadata["email"]}")
+ }
+ println("Date: ${metadata["timestamp"]}")
+ if (metadata["message"] != "") {
+ println("\n${metadata["message"]}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Migrate.kt b/src/main/kotlin/io/titandata/titan/providers/local/Migrate.kt
new file mode 100644
index 00000000..ba2e2eac
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Migrate.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.client.apis.VolumeApi
+import io.titandata.titan.clients.Docker
+import io.titandata.models.Repository
+import io.titandata.models.VolumeMountRequest
+import io.titandata.titan.utils.CommandExecutor
+import org.json.JSONArray
+import org.json.JSONObject
+
+class Migrate (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val commit: (container: String, message: String) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi(),
+ private val volumeApi: VolumeApi = VolumeApi()
+) {
+
+ private fun getLocalSrcFromPath(path: String, containerInfo: JSONObject): String {
+ var returnString = ""
+ for (item in containerInfo.getJSONArray("Mounts")) {
+ val mount = item as JSONObject
+ if (mount.getString("Destination") == path) {
+ returnString = mount.getString("Source")
+ }
+ }
+ return returnString
+ }
+
+ fun migrate(container: String, name: String) {
+ val containerInfo = docker.inspectContainer(container)
+ if (containerInfo == null) {
+ exit("Container information is not available",1)
+ }
+ if(containerInfo!!.getJSONObject("State").getBoolean("Running")) {
+ exit("Cannot migrate a running container. Please stop $container",1)
+ }
+ if(name.contains("/")) {
+ exit("Container name cannot contain a slash",1)
+ }
+ val image = containerInfo.getString("Image")
+ val imageInfo = docker.inspectImage(image)
+ if (imageInfo == null) {
+ exit("Image information is not available",1)
+ }
+ val volumes = imageInfo!!.getJSONObject("Config").optJSONObject("Volumes")
+ if (volumes == null) {
+ exit("No volumes found for image $image", 1)
+ }
+ println("Creating repository $name")
+ val arguments = mutableListOf("-d","--label","io.titandata.titan")
+ val repo = Repository(name, emptyMap())
+ repositoriesApi.createRepository(repo)
+ for (path in volumes.keys()) {
+ val volumeName = "$name/v1"
+ println("Creating docker volume $volumeName with path $path")
+ docker.createVolume(volumeName, path)
+ val localSrc = getLocalSrcFromPath(path, containerInfo as JSONObject)
+ if (localSrc.isNotEmpty()) {
+ println("Copying data to $volumeName")
+ val volMountRequest = VolumeMountRequest(volumeName, "")
+ volumeApi.mountVolume(volMountRequest)
+ docker.cp(localSrc, volumeName)
+ volumeApi.unmountVolume(volMountRequest)
+ }
+ arguments.add("--mount")
+ arguments.add("type=volume,src=$volumeName,dst=$path,volume-driver=titan")
+ }
+
+ val ports = containerInfo.getJSONObject("HostConfig").optJSONObject("PortBindings")
+ for (item in ports.keys()) {
+ val port = ports[item] as JSONArray
+ val host = port[0] as JSONObject
+ val containerPort = item.split("/")[0]
+ arguments.add("-p")
+ if (host.optString("HostIp").isNotEmpty()){
+ arguments.add("${host.optString("HostIp")}:${host.getString("HostPort")}:$containerPort")
+ } else {
+ arguments.add("${host.getString("HostPort")}:$containerPort")
+ }
+ }
+ arguments.add("--name")
+ arguments.add(name)
+
+ val repoDigest = imageInfo.getJSONArray("RepoDigests")[0] as String
+ val metadata = mapOf(
+ "container" to repoDigest,
+ "runtime" to arguments.toString()
+ )
+
+ //TODO check for arguments to run validation since switch to array
+
+ val updateRepo = Repository(name, metadata)
+ repositoriesApi.updateRepository(name, updateRepo)
+ docker.run(image, "", arguments)
+ commit(name, "Initial Migration")
+ println("$container migrated to controlled environment $name")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Pull.kt b/src/main/kotlin/io/titandata/titan/providers/local/Pull.kt
new file mode 100644
index 00000000..b2750615
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Pull.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.OperationsApi
+import io.titandata.client.apis.RemotesApi
+import io.titandata.client.infrastructure.ClientException
+import io.titandata.models.*
+import io.titandata.serialization.RemoteUtil
+
+class Pull (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val remotesApi: RemotesApi = RemotesApi(),
+ private val operationsApi: OperationsApi = OperationsApi(),
+ private val remoteUtil: RemoteUtil = RemoteUtil()
+) {
+
+ fun pull(container: String, guid: String?, remoteName: String?) {
+ val name = remoteName ?: "origin"
+ val remotes = remotesApi.listRemotes(container)
+ if(remotes.isEmpty()) {
+ exit("remote is not set, run 'remote add' first", 1)
+ }
+
+ val remote = remotesApi.getRemote(container, name)
+ var commit = io.titandata.models.Commit("id", emptyMap())
+ try {
+ commit = remotesApi.getRemoteCommit(container, remote.name, guid!!, remoteUtil.getParameters(remote))
+ } catch (e: kotlin.KotlinNullPointerException) {
+ val remoteCommits = remotesApi.listRemoteCommits(container, remote.name, remoteUtil.getParameters(remote))
+ if (remoteCommits.isEmpty()) {
+ exit("no commits found in remote, unable to pull latest", 1)
+ }
+ commit = remoteCommits.first()
+
+ } catch (e: ClientException) {
+ exit(e.message!!, 1)
+ }
+ if(commit.id == "id") {
+ exit("remote commit not found", 1)
+ }
+ var operation = Operation("id", Operation.Type.PULL, Operation.State.RUNNING, remote.name, commit.id)
+ try {
+ operation = operationsApi.pull(container, remote.name, commit.id, remoteUtil.getParameters(remote))
+ } catch (e: ClientException) {
+ exit(e.message!!,1)
+ }
+ println("${operation.type} ${operation.commitId} from ${operation.remote} ${operation.state}")
+ var padLen = 0
+ while (operation.state == Operation.State.RUNNING){
+ val statuses = operationsApi.getProgress(container, operation.id)
+ for (status in statuses) {
+ if (status.type != ProgressEntry.Type.PROGRESS) {
+ println(status.message)
+ } else {
+ val subMessage = status.message as String
+ if (subMessage.length > padLen) {
+ padLen = subMessage.length
+ }
+ System.out.printf("\r%s", subMessage.padEnd((padLen - subMessage.length) + 1, ' '))
+ }
+ }
+ Thread.sleep(2000)
+ operation = operationsApi.getOperation(container, operation.id)
+ }
+ println("${operation.type} ${operation.commitId} from ${operation.remote} ${operation.state}")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Push.kt b/src/main/kotlin/io/titandata/titan/providers/local/Push.kt
new file mode 100644
index 00000000..cbd85707
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Push.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+import io.titandata.client.apis.RemotesApi
+import io.titandata.client.apis.OperationsApi
+import io.titandata.client.infrastructure.ClientException
+import io.titandata.models.*
+import io.titandata.models.Commit
+import io.titandata.serialization.RemoteUtil
+
+class Push (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val commitsApi: CommitsApi = CommitsApi(),
+ private val remotesApi: RemotesApi = RemotesApi(),
+ private val operationsApi: OperationsApi = OperationsApi(),
+ private val remoteUtil: RemoteUtil = RemoteUtil()
+) {
+ fun push(container: String, guid: String?, remoteName: String?) {
+ val name = remoteName ?: "origin"
+ val remotes = remotesApi.listRemotes(container)
+ if(remotes.isEmpty()) {
+ exit("remote is not set, run 'remote add' first", 1)
+ }
+
+ val commits = commitsApi.listCommits(container)
+ if (commits.isEmpty()) {
+ exit("container has no history, run 'commit' to first commit state",1)
+ }
+ var commit: Commit = io.titandata.models.Commit("id", emptyMap())
+ if (!guid.isNullOrEmpty()) {
+ try{
+ commit = commitsApi.getCommit(container, guid!!)
+ } catch (e: ClientException) {
+ exit(e.message!!, 1)
+ }
+ } else {
+ commit = commits.last()
+ }
+ val remote = remotesApi.getRemote(container, name)
+ var operation = Operation("id", Operation.Type.PUSH, Operation.State.RUNNING, remote.name, commit.id)
+ try {
+ operation = operationsApi.push(container, remote.name, commit.id, remoteUtil.getParameters(remote))
+ } catch (e: ClientException) {
+ exit(e.message!!,1)
+ }
+ println("${operation.type} ${operation.commitId} to ${operation.remote} ${operation.state}")
+ var padLen = 0
+ while (operation.state == Operation.State.RUNNING){
+ val statuses = operationsApi.getProgress(container, operation.id)
+ for (status in statuses) {
+ if (status.type != ProgressEntry.Type.PROGRESS) {
+ println(status.message)
+ } else {
+ val subMessage = status.message as String
+ if (subMessage.length > padLen) {
+ padLen = subMessage.length
+ }
+ System.out.printf("\r%s", subMessage.padEnd((padLen - subMessage.length) + 1, ' '))
+ }
+ }
+ Thread.sleep(2000)
+ operation = operationsApi.getOperation(container, operation.id)
+ }
+ println("${operation.type} ${operation.commitId} to ${operation.remote} ${operation.state}")
+ // TODO get appropriate source hash for clone !! Confirm beta issue
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/RemoteAdd.kt b/src/main/kotlin/io/titandata/titan/providers/local/RemoteAdd.kt
new file mode 100644
index 00000000..61a59253
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/RemoteAdd.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RemotesApi
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.client.infrastructure.ClientException
+import io.titandata.models.Repository
+import io.titandata.serialization.RemoteUtil
+
+class RemoteAdd (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi(),
+ private val remotesApi: RemotesApi = RemotesApi(),
+ private val remoteUtil: RemoteUtil = RemoteUtil()
+) {
+ fun remoteAdd(
+ container:String,
+ uri: String,
+ remoteName: String?
+ ) {
+ val name = remoteName ?: "origin"
+ try {
+ remotesApi.getRemote(container, name)
+ exit("remote $name already exists for $container", 1)
+ } catch (e: ClientException) { }
+ val remote = remoteUtil.parseUri(uri, name, emptyMap())
+ remotesApi.createRemote(container, remote)
+ val metadata = repositoriesApi.getRepository(container).properties.toMutableMap()
+ metadata["remote"] = remoteName ?: container
+ val repo = Repository(container, metadata)
+ repositoriesApi.updateRepository(container,repo)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt b/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt
new file mode 100644
index 00000000..ec967ddf
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RemotesApi
+
+class RemoteList (
+ private val remotesApi: RemotesApi = RemotesApi()
+) {
+ fun list(container: String) {
+ val remotes = remotesApi.listRemotes(container)
+ System.out.printf("%-20s %s\n", "REMOTE", "PROVIDER")
+ for (remote in remotes) {
+ System.out.printf("%-20s %s\n", remote.name, remote.provider)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/RemoteLog.kt b/src/main/kotlin/io/titandata/titan/providers/local/RemoteLog.kt
new file mode 100644
index 00000000..d90b7370
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/RemoteLog.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RemotesApi
+import io.titandata.client.infrastructure.ClientException
+import io.titandata.serialization.RemoteUtil
+import io.titandata.models.NopRemote
+import io.titandata.models.Remote
+
+class RemoteLog (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val remotesApi: RemotesApi = RemotesApi(),
+ private val remoteUtil: RemoteUtil = RemoteUtil()
+) {
+
+ private fun getRemotes(container: String, remoteName: String?): Array {
+ return try {
+ when (remoteName.isNullOrBlank()) {
+ true -> remotesApi.listRemotes(container)
+ else -> arrayOf(remotesApi.getRemote(container, remoteName))
+ }
+ } catch (e: ClientException) {
+ arrayOf(NopRemote( name="NOP"))
+ }
+ }
+
+ fun remoteLog(container:String, remoteName: String?) {
+ val remotes = getRemotes(container, remoteName)
+ if(remotes.isEmpty()) {
+ exit("remote is not set, run 'remote add' first", 1)
+ }
+
+ var first = true
+ loop@ for (remote in remotes) {
+ if (remote.provider == "nop") {
+ break@loop
+ }
+ try {
+ if (!first) {
+ println("")
+ } else {
+ first = false
+ }
+ val commits = remotesApi.listRemoteCommits(container, remote.name, remoteUtil.getParameters(remote))
+ for (commit in commits) {
+ println("Remote: ${remote.name}")
+ println("Commit ${commit.id}")
+ if (commit.properties.containsKey("author")) {
+ println("Author: ${commit.properties["author"]}")
+ }
+ if (commit.properties.containsKey("user")) {
+ println("User: ${commit.properties["user"]}")
+ }
+ if (commit.properties.containsKey("email")) {
+ println("Email: ${commit.properties["email"]}")
+ }
+ println("Date: ${commit.properties["timestamp"]}")
+ if (commit.properties["message"] != "") {
+ println("\n${commit.properties["message"]}")
+ }
+ }
+ } catch (e: ClientException) {
+ println("${remote.name} has not been initialized.")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/RemoteRemove.kt b/src/main/kotlin/io/titandata/titan/providers/local/RemoteRemove.kt
new file mode 100644
index 00000000..26c807e8
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/RemoteRemove.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RemotesApi
+
+class RemoteRemove (
+ private val remotesApi: RemotesApi = RemotesApi()
+) {
+ fun remove(container: String, remote: String) {
+ remotesApi.deleteRemote(container, remote)
+ println("Removed $remote from $container")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Remove.kt b/src/main/kotlin/io/titandata/titan/providers/local/Remove.kt
new file mode 100644
index 00000000..602cf678
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Remove.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.client.apis.VolumeApi
+import io.titandata.titan.clients.Docker
+import io.titandata.models.VolumeMountRequest
+import io.titandata.titan.utils.CommandExecutor
+import java.lang.Exception
+
+class Remove (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi(),
+ private val volumeApi: VolumeApi = VolumeApi()
+) {
+ fun remove(container: String, force: Boolean) {
+ try {
+ val containerInfo = docker.inspectContainer(container)
+ if (containerInfo != null) {
+ if (!force) {
+ if (containerInfo.getJSONObject("State").getString("Status") == "running") {
+ exit("container $container is running, stop or use '-f' to force", 1)
+ }
+ }
+ println("Removing container $container")
+ if (docker.containerIsRunning(container)) {
+ docker.rm(container, force)
+ } else {
+ docker.rmStopped(container)
+ }
+ }
+ } catch (e: Exception) { }
+ for (volume in volumeApi.listVolumes().volumes) {
+ val name = volume.name.split("/")[0]
+ if (name == container) {
+ println("Deleting volume ${volume.name}")
+ val volMountRequest = VolumeMountRequest(volume.name)
+ volumeApi.unmountVolume(volMountRequest)
+ docker.removeVolume(volume.name)
+ }
+ }
+ println("Deleting repository $container")
+ repositoriesApi.deleteRepository(container)
+ println("$container removed")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Run.kt b/src/main/kotlin/io/titandata/titan/providers/local/Run.kt
new file mode 100644
index 00000000..6354c63b
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Run.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.clients.Docker.Companion.fetchName
+import io.titandata.titan.clients.Docker.Companion.hasDetach
+import io.titandata.models.Repository
+import io.titandata.titan.utils.CommandExecutor
+
+class Run (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi()
+) {
+ fun run(arguments: List, createRepo: Boolean = true) {
+ if (!arguments.hasDetach()) {
+ exit("No detach (-d or --detach) argument found, only detached containers are supported",1)
+ }
+ val containerName = arguments.fetchName()
+ if(containerName.contains("/")) {
+ exit("Container name cannot contain a slash",1)
+ }
+ val image = arguments.last()
+ val imageInfo = docker.inspectImage(image)
+ if (imageInfo == null) {
+ exit("Image information is not available",1)
+ }
+ val volumes = imageInfo!!.getJSONObject("Config").optJSONObject("Volumes")
+ if (volumes == null) {
+ exit("No volumes found for image $image",1)
+ }
+ println("Creating repository $containerName")
+ val repo = Repository(containerName, emptyMap())
+ if (createRepo) {
+ repositoriesApi.createRepository(repo)
+ }
+ val argList = mutableListOf("--label","io.titandata.titan")
+ for ((index, path) in volumes.keys().withIndex()) {
+ val volumeName = "$containerName/v$index"
+ println("Creating docker volume $volumeName with path $path")
+
+ docker.createVolume(volumeName, path)
+
+ argList.add("--mount")
+ argList.add("type=volume,src=$volumeName,dst=$path,volume-driver=titan")
+ }
+ val argumentEdit= arguments.toMutableList()
+ if (argumentEdit.contains("--name")) {
+ argumentEdit.removeAt((argumentEdit.indexOf("--name") + 1))
+ argumentEdit.removeAt(argumentEdit.indexOf("--name"))
+ }
+ if (argumentEdit.contains(image)) {
+ argumentEdit.removeAt(argumentEdit.indexOf(image))
+ }
+ argList.add("--name")
+ argList.add(containerName)
+ argList.addAll(argumentEdit)
+ val repoDigest = imageInfo.getJSONArray("RepoDigests")[0] as String
+ val metadata = mapOf(
+ "container" to repoDigest,
+ "runtime" to argList.toString()
+ )
+ val updateRepo = Repository(containerName, metadata)
+ repositoriesApi.updateRepository(containerName, updateRepo)
+ docker.run(repoDigest, "", argList)
+ println("Running controlled container $containerName")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Start.kt b/src/main/kotlin/io/titandata/titan/providers/local/Start.kt
new file mode 100644
index 00000000..f702ecc5
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Start.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+
+class Start (
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor)
+) {
+ fun start(container: String) {
+ docker.start(container)
+ println("$container started")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Status.kt b/src/main/kotlin/io/titandata/titan/providers/local/Status.kt
new file mode 100644
index 00000000..db4d8db1
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Status.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.providers.Container
+
+class Status (
+ private val getContainersStatus: () -> List
+){
+ fun status(container: String) {
+ for(cont in getContainersStatus()) {
+ if(container == cont.name) {
+ println("Status: ${cont.status}")
+ }
+ }
+ println("*** SAMPLE STATUS ***")
+ println("Size: 5.4GiB")
+ println("Current Head: d3f4c1")
+ println("Volumes:")
+ println(" 1.23GiB /var/lib/postgres/data")
+ println("Operations: None")
+ println("*** SAMPLE STATUS ***")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Stop.kt b/src/main/kotlin/io/titandata/titan/providers/local/Stop.kt
new file mode 100644
index 00000000..e7332e58
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Stop.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+
+class Stop (
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor)
+) {
+ fun stop(container: String) {
+ docker.stop(container)
+ println("$container stopped")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Uninstall.kt b/src/main/kotlin/io/titandata/titan/providers/local/Uninstall.kt
new file mode 100644
index 00000000..b987574b
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Uninstall.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.exceptions.CommandException
+import io.titandata.titan.utils.CommandExecutor
+
+class Uninstall (
+ private val exit: (message: String, code: Int) -> Unit,
+ private val remove: (container: String, force: Boolean) -> Unit,
+ private val commandExecutor: CommandExecutor = CommandExecutor(),
+ private val docker: Docker = Docker(commandExecutor),
+ private val repositoriesApi: RepositoriesApi = RepositoriesApi()
+) {
+ fun uninstall(force: Boolean) {
+ if (docker.titanServerIsAvailable()) {
+ val repositories = repositoriesApi.listRepositories()
+ for (repo in repositories) {
+ if (!force) {
+ exit("repository ${repo.name} exists, remove first or use '-f'", 1)
+ }
+ remove(repo.name, true)
+ }
+ }
+ if (docker.titanServerIsAvailable()) docker.rm("titan-server", true)
+ if (docker.titanLaunchIsAvailable()) docker.rm("titan-launch", true)
+ docker.teardownTitanServers()
+ try {
+ docker.removeVolume("titan-modules")
+ } catch (e: CommandException) {}
+ try {
+ docker.removeVolume("titan-data")
+ } catch (e: CommandException) {}
+ println("Uninstalled titan infrastructure")
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Upgrade.kt b/src/main/kotlin/io/titandata/titan/providers/local/Upgrade.kt
new file mode 100644
index 00000000..6e468976
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Upgrade.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.Version
+import io.titandata.titan.Version.Companion.compare
+import io.titandata.titan.exceptions.CommandException
+import io.titandata.titan.providers.Container
+import io.titandata.titan.utils.CommandExecutor
+import io.titandata.titan.utils.HttpHandler
+import io.titandata.titan.utils.HttpHandler.Companion.asBytes
+import io.titandata.titan.utils.HttpHandler.Companion.asJsonObject
+import io.titandata.titan.utils.HttpHandler.Companion.asJsonArray
+import org.apache.commons.lang3.SystemUtils
+import org.json.JSONObject
+import java.io.File
+
+class Upgrade(
+ private val start: (container: String) -> Unit,
+ private val stop: (container: String) -> Unit,
+ private val exit: (message: String, code: Int) -> Unit,
+ private val getContainersStatus: () -> List,
+ private val executor: CommandExecutor = CommandExecutor(),
+ private val httpHandler: HttpHandler = HttpHandler()
+) {
+
+ private val titanVersionURL = "https://api.github.com/repos/titan-data/titan/releases/latest"
+ private var assetUrl = ""
+
+ private fun getLatestVersion(): String {
+ var latestVersion = ""
+ try {
+ val response = httpHandler.get(titanVersionURL).asJsonObject()
+ latestVersion = response.getString("tag_name")
+ assetUrl = response.getString("assets_url")
+ } catch(e: java.net.UnknownHostException) {
+ exit("Upgrade server cannot be reached.", 1)
+ }
+ return latestVersion
+ }
+
+ private fun getBinaryURL(version: String): String {
+ val assets = httpHandler.get(assetUrl).asJsonArray()
+ var path = "titan-cli-$version-"
+ path += when{
+ SystemUtils.IS_OS_MAC -> "darwin_amd64.zip"
+ SystemUtils.IS_OS_LINUX -> "linux_amd64.zip"
+ SystemUtils.IS_OS_WINDOWS -> "todo" //TODO windows build
+ else -> exit("unsupported operating system", 1)
+ }
+ var downloadUrl = ""
+ for (item in assets) {
+ val asset = item as JSONObject
+ if(asset.getString("name") == path) {
+ downloadUrl = asset.getString("browser_download_url")
+ }
+ }
+ return downloadUrl
+ }
+
+ private fun getTitanBinaryPath(): String {
+ return when {
+ SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX -> {
+ val fullpath = try {
+ executor.exec(listOf("which", "titan"))
+ } catch (e: CommandException) {
+ exit("Cannot find Titan in path. Use -p to to specify the install path.", 1)
+ }
+ val symCheck = executor.exec(listOf("readlink", fullpath.toString().trim()))
+ when {
+ !symCheck.isBlank() -> symCheck.dropLast(6).trim()
+ else -> fullpath.toString().dropLast(6).trim()
+ }
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ "Not Yet Implemented"
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ private fun downloadToTemp(binaryPath: String) {
+ when {
+ SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX -> {
+ try {
+ File("/tmp/titan-latest.zip").writeBytes(httpHandler.get(binaryPath).asBytes())
+ } catch (e: java.net.UnknownHostException) {
+ exit("Upgrade server not found.", 1)
+ }
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ println("Not Yet Implemented")
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ private fun extractTempToNew() {
+ when {
+ SystemUtils.IS_OS_MAC -> {
+ executor.exec(listOf("unzip","-o","-d","/tmp/","/tmp/titan-latest.zip"))
+ }
+ SystemUtils.IS_OS_LINUX -> {
+ executor.exec(listOf("tar","-xvf","/tmp/titan-latest.zip","-C","/tmp/"))
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ println("Not Yet Implemented")
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ private fun copyCurrentToOld(installPath: String) {
+ when {
+ SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX -> {
+ executor.exec(listOf("cp", "$installPath/titan", "$installPath/titan_OLD"))
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ println("Not Yet Implemented")
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ private fun upgradeInfrastructure() {
+ when {
+ SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX -> {
+ executor.exec(listOf("/tmp/titan","install"))
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ println("Not Yet Implemented")
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ private fun replaceTitanBinaries(installPath: String) {
+ when {
+ SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX -> {
+ executor.exec(listOf("mv","/tmp/titan","$installPath/titan"))
+ executor.exec(listOf("rm","$installPath/titan_OLD"))
+ executor.exec(listOf("rm","/tmp/titan-latest.zip"))
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ println("Not Yet Implemented")
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ private fun finalizeUpgrade() {
+ when {
+ SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX -> {
+ executor.exec(listOf("/tmp/titan","upgrade","--finalize"))
+ }
+ SystemUtils.IS_OS_WINDOWS -> {
+ println("Not Yet Implemented")
+ }
+ else -> throw Exception("Operating system is unsupported.")
+ }
+ }
+
+ fun upgrade(force: Boolean, version: String, finalize: Boolean, path: String?) {
+ val restartList = mutableListOf()
+ if(!force){
+ for (container in getContainersStatus()) {
+ if (container.status == "running") {
+ exit("container ${container.name} is running, stop or use '-f' to force", 1)
+ }
+ }
+ } else {
+ for (container in getContainersStatus()) {
+ if (container.status == "running") {
+ restartList.add(container.name)
+ stop(container.name)
+ }
+ }
+ }
+
+ val latestVersion = getLatestVersion()
+ val local = Version.fromString(version)
+ val latest = Version.fromString(latestVersion)
+ val upgradeStatus = local.compare(latest)
+
+ val latestPath = getBinaryURL(latestVersion)
+ val installPath = if(!path.isNullOrEmpty()) path.toString() else getTitanBinaryPath()
+
+ if (finalize) {
+ println("Upgrading infrastructure")
+ upgradeInfrastructure()
+
+ println("Replacing Version")
+ replaceTitanBinaries(installPath)
+
+ for(container in restartList) {
+ start(container)
+ }
+
+ exit("Titan is now $latestVersion", 0)
+ }
+
+ if (upgradeStatus == 0) exit("Titan is up to date.", 1)
+ if (upgradeStatus == 1) exit("Titan is ahead of the latest version.", 1)
+
+ println("Downloading Latest Version")
+ downloadToTemp(latestPath)
+
+ println("Extracting Archive")
+ extractTempToNew()
+
+ println("Coping current bin to OLD")
+ copyCurrentToOld(installPath)
+
+ finalizeUpgrade()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/titandata/titan/utils/CommandExecutor.kt b/src/main/kotlin/io/titandata/titan/utils/CommandExecutor.kt
new file mode 100644
index 00000000..fbc7f018
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/utils/CommandExecutor.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.utils
+
+import io.titandata.titan.exceptions.CommandException
+import java.io.IOException
+import java.util.concurrent.TimeUnit
+
+/**
+ * ORIGINAL FILE FROM TITAN-SERVER
+ *
+ * Handle invocation of external commands. This is a wrapper around the native interfaces that
+ * handles all of the stdin/stdout errors, and will throw an exception if the command returns
+ * a non-zero exit status. It is also a convenient to mock out any dependencies on the external
+ * system.
+ */
+class CommandExecutor(val timeout: Long = 10) {
+
+ /**
+ * Execute the given command. Throws an exception if the
+ */
+ fun exec(args: List, debug: Boolean = false): String {
+ val builder = ProcessBuilder()
+ if (debug) println(args)
+ builder.command(args)
+ val process = builder.start()
+ try {
+ process.waitFor(timeout, TimeUnit.MINUTES)
+ val output = process.inputStream.bufferedReader().readText()
+ if (process.isAlive) {
+ throw IOException("Timed out waiting for command: $args")
+ }
+ if (debug) println("Exit Value: ${process.exitValue()}")
+ if (process.exitValue() != 0) {
+ val errOutput = process.errorStream.bufferedReader().readText()
+ if (debug) println(errOutput)
+ throw CommandException("Command failed: $args",
+ exitCode = process.exitValue(),
+ output = errOutput)
+ }
+ return output
+ } finally {
+ process.destroy()
+ }
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/utils/HttpHandler.kt b/src/main/kotlin/io/titandata/titan/utils/HttpHandler.kt
new file mode 100644
index 00000000..e7431381
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/utils/HttpHandler.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.utils
+
+import okhttp3.*
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.IOException
+import java.util.concurrent.TimeUnit
+
+class HttpHandler(private val timeout: Long = 60, private val timeoutUnit: TimeUnit = TimeUnit.MINUTES){
+
+ private fun call(request: Request): ResponseBody {
+ val caller = OkHttpClient.Builder()
+ .readTimeout(timeout, timeoutUnit)
+ .build()
+ val response = caller.newCall(request).execute()
+ if (!response.isSuccessful) {
+ throw IOException("Unexpected Code: $response")
+ }
+ return response.body()!!
+ }
+
+ fun get(url: String): ResponseBody {
+ val request = Request.Builder()
+ .url(url)
+ .build()
+ return call(request)
+ }
+
+ fun post(url: String, data: Map): ResponseBody {
+ val json = MediaType.parse("application/json; charset=utf-8")
+ val requestBody = RequestBody.create(json, JSONObject(data).toString());
+ val request = Request.Builder()
+ .url(url)
+ .post(requestBody)
+ .build()
+ return call(request)
+ }
+
+ companion object {
+ fun ResponseBody.asString(): String {
+ return this.string()
+ }
+
+ fun ResponseBody.asBytes(): ByteArray {
+ return this.bytes()
+ }
+
+ fun ResponseBody.asJsonArray(): JSONArray {
+ return JSONArray(this.asString())
+ }
+
+ fun ResponseBody.asJsonObject(): JSONObject {
+ return JSONObject(this.asString())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION
new file mode 100644
index 00000000..9325c3cc
--- /dev/null
+++ b/src/main/resources/VERSION
@@ -0,0 +1 @@
+0.3.0
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/progresstracker/PrintStreamProgressTrackerTest.kt b/src/test/kotlin/io/titandata/progresstracker/PrintStreamProgressTrackerTest.kt
new file mode 100644
index 00000000..67e9d507
--- /dev/null
+++ b/src/test/kotlin/io/titandata/progresstracker/PrintStreamProgressTrackerTest.kt
@@ -0,0 +1,112 @@
+package io.titandata.progresstracker
+
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Test
+import org.mockito.ArgumentMatchers.contains
+import org.mockito.Mockito
+import java.io.PrintStream
+
+
+class PrintStreamProgressTrackerTest {
+
+ @Test
+ fun testInProgressTaskAnimation() {
+ val printStream = mock {}
+ val progressTracker = PrintStreamProgressTracker(
+ animation = arrayOf('1', '2'),
+ successMessage = "Success!",
+ printStream = printStream,
+ refreshRateInMillis = 1L)
+ progressTracker.use {
+ it.pushTask("Testing task")
+ simulateLongRunningTask()
+ }
+
+ val inOrder = Mockito.inOrder(printStream)
+ inOrder.verify(printStream).print(contains("1 Testing task"))
+ inOrder.verify(printStream).print(contains("2 Testing task"))
+ inOrder.verify(printStream).print(contains("1 Testing task"))
+ inOrder.verify(printStream).print(contains("2 Testing task"))
+ }
+
+ @Test
+ fun testCompletedTaskIsPrinted() {
+ val printStream = mock {}
+ val progressTracker = PrintStreamProgressTracker(
+ animation = arrayOf('1', '2'),
+ successMessage = "Success!",
+ printStream = printStream,
+ refreshRateInMillis = 1L)
+ progressTracker.use {
+ it.pushTask("Testing task")
+ }
+ simulateLongRunningTask()
+ val inOrder = Mockito.inOrder(printStream)
+ inOrder.verify(printStream).print(contains("√ Testing task"))
+ inOrder.verify(printStream).println("Success!")
+ }
+
+ @Test
+ fun testExitMessageIsPrinted() {
+ val printStream = mock {}
+ val progressTracker = PrintStreamProgressTracker(
+ animation = arrayOf('1', '2'),
+ successMessage = "Success!",
+ printStream = printStream,
+ refreshRateInMillis = 1L)
+ progressTracker.use {
+ it.pushTask("Testing task")
+ }
+ simulateLongRunningTask()
+ verify(printStream).println(contains("Success!"))
+ }
+
+ @Test
+ fun testExitOnQuickTasks() {
+ val printStream = mock {}
+ val progressTracker = PrintStreamProgressTracker(
+ animation = arrayOf('1', '2'),
+ successMessage = "Success!",
+ printStream = printStream,
+ refreshRateInMillis = 1L)
+ progressTracker.use {
+ it.pushTask("Testing task")
+ simulateLongRunningTask()
+ }
+ val inOrder = Mockito.inOrder(printStream)
+ inOrder.verify(printStream).print(contains("1 Testing task"))
+ inOrder.verify(printStream).print(contains("√ Testing task"))
+ inOrder.verify(printStream).println(contains("Success!"))
+
+ }
+
+ @Test
+ fun testFailureMessage() {
+ val printStream = mock {}
+ val progressTracker = PrintStreamProgressTracker(
+ animation = arrayOf('1', '2'),
+ successMessage = "Success!",
+ failureMessage = "Tasks failed!",
+ printStream = printStream,
+ refreshRateInMillis = 1L)
+ progressTracker.use {
+ it.pushTask("Testing task")
+ simulateLongRunningTask()
+ it.pushTask("Task that will fail")
+ it.markAsFailed()
+ }
+ val inOrder = Mockito.inOrder(printStream)
+ inOrder.verify(printStream).print(contains("1 Testing task"))
+ inOrder.verify(printStream).print(contains("√ Testing task"))
+ inOrder.verify(printStream).print(contains("X Task that will fail"))
+ inOrder.verify(printStream).println(contains("Tasks failed!"))
+ verify(printStream, never()).println(contains("Success!"))
+ }
+
+ private fun simulateLongRunningTask(timeInMillis: Long = 10L) {
+ Thread.sleep(timeInMillis)
+ }
+
+}
diff --git a/src/test/kotlin/io/titandata/titan/CliTest.kt b/src/test/kotlin/io/titandata/titan/CliTest.kt
new file mode 100644
index 00000000..07d364b4
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/CliTest.kt
@@ -0,0 +1,14 @@
+package io.titandata.titan
+
+import org.junit.Test
+
+class CliTest {
+ val arguments = emptyArray()
+ //val cli = Cli.main(arguments)
+
+ @Test
+ fun contextLoads() {
+ //println(cli)
+ }
+
+}
diff --git a/src/test/kotlin/io/titandata/titan/DependenciesTest.kt b/src/test/kotlin/io/titandata/titan/DependenciesTest.kt
new file mode 100644
index 00000000..c35b0d54
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/DependenciesTest.kt
@@ -0,0 +1,17 @@
+package io.titandata.titan
+
+import org.junit.Test
+import io.titandata.titan.providers.Provider
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import io.titandata.titan.providers.ProviderFactory
+
+class DependenciesTest {
+ val provider = ProviderFactory().getFactory("mock")
+ val dependencies = Dependencies(provider)
+
+ @Test
+ fun `can get provider`(){
+ assertThat(dependencies.provider, instanceOf(Provider::class.java))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/VersionTest.kt b/src/test/kotlin/io/titandata/titan/VersionTest.kt
new file mode 100644
index 00000000..fb5e5bc8
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/VersionTest.kt
@@ -0,0 +1,104 @@
+package io.titandata.titan
+
+import org.junit.Test
+import kotlin.test.assertEquals
+import io.titandata.titan.Version.Companion.compare
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+
+class VersionTest {
+ private val version = Version(1,0,0, "rc-1")
+
+ @Test
+ fun `can load version`(){
+ assertThat(version, instanceOf(Version::class.java))
+ }
+
+ @Test
+ fun `can get major value`(){
+ assertEquals(1, version.major)
+ }
+
+ @Test
+ fun `can get minor value`(){
+ assertEquals(0, version.minor)
+ }
+
+ @Test
+ fun `can get micro value`(){
+ assertEquals(0, version.micro)
+ }
+
+ @Test
+ fun `can get pre-release value`(){
+ assertEquals("rc-1", version.preRelease)
+ }
+
+ @Test
+ fun `can load from string`() {
+ val version = Version.fromString("2.1.3")
+ assertThat(version, instanceOf(Version::class.java))
+ assertEquals(2, version.major)
+ assertEquals(1, version.minor)
+ assertEquals(3, version.micro)
+ }
+
+ @Test
+ fun `can load pre-release from string`() {
+ val version = Version.fromString("2.1.3-rc1")
+ assertThat(version, instanceOf(Version::class.java))
+ assertEquals(2, version.major)
+ assertEquals(1, version.minor)
+ assertEquals(3, version.micro)
+ assertEquals("rc1", version.preRelease)
+ }
+
+ @Test
+ fun `versions are the same`(){
+ val current = Version(1,0,0)
+ val latest = Version(1,0,0)
+ assertEquals(0, current.compare(latest))
+ }
+
+ @Test
+ fun `current is behind latest major`(){
+ val current = Version(0,0,1)
+ val latest = Version(1,0,0)
+ assertEquals(-1, current.compare(latest))
+ }
+
+ @Test
+ fun `current is ahead of latest major`(){
+ val current = Version(1,0,0)
+ val latest = Version(0,0,0)
+ assertEquals(1, current.compare(latest))
+ }
+
+ @Test
+ fun `current is behind latest minor`(){
+ val current = Version(0,0,1)
+ val latest = Version(0,1,0)
+ assertEquals(-1, current.compare(latest))
+ }
+
+ @Test
+ fun `current is ahead of latest minor`(){
+ val current = Version(0,1,0)
+ val latest = Version(0,0,0)
+ assertEquals(1, current.compare(latest))
+ }
+
+ @Test
+ fun `current is behind latest micro`(){
+ val current = Version(0,0,0)
+ val latest = Version(0,0,1)
+ assertEquals(-1, current.compare(latest))
+ }
+
+ @Test
+ fun `current is ahead of latest micro`(){
+ val current = Version(0,0,1)
+ val latest = Version(0,0,0)
+ assertEquals(1, current.compare(latest))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/clients/DockerTest.kt b/src/test/kotlin/io/titandata/titan/clients/DockerTest.kt
new file mode 100644
index 00000000..d3703876
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/clients/DockerTest.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.clients
+
+import io.titandata.titan.utils.CommandExecutor
+import io.titandata.titan.clients.Docker.Companion.toList
+import io.titandata.titan.clients.Docker.Companion.fetchName
+import io.titandata.titan.clients.Docker.Companion.hasDetach
+import io.titandata.titan.clients.Docker.Companion.runtimeToArguments
+import org.junit.Test
+import com.nhaarman.mockitokotlin2.*
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class DockerTest {
+ private val executor = mock {
+ on {exec(listOf("docker", "-v")) } doReturn "Docker version 18.09.2, build 6247962"
+ on {exec(listOf("docker", "images", "titan", "--format", "\"{{.Repository}}\""))} doReturn "titan"
+ on {exec(listOf("docker", "ps", "-f", "name=titan-launch", "--format", "\"{{.Names}}\""))} doReturn "titan-launch"
+ on {exec(listOf("docker", "ps", "-f", "name=titan-server", "--format", "\"{{.Names}}\""))} doReturn "titan-server"
+ on {exec(listOf("docker", "pull", "titan"))} doReturn """Using default tag: latest
+latest: Pulling from titan
+Digest: sha256:bc6f593df26c0631a1ce3a06afdd5a4a8fda703b071fb18805091e2372c68201
+Status: Downloaded newer image for titan:latest
+ """
+ on {exec(listOf("docker","tag","source","target"))} doReturn ""
+ on {exec(listOf("docker","stop","container"))} doReturn "container"
+ on {exec(listOf("docker","start","container"))} doReturn "container"
+ on {exec(listOf("docker", "inspect", "--type", "container", "container"))} doReturn """[
+ {
+ "Id": "48a93f6ac01c054dc8eb02313dc28450b48e4368cf29d8a8366200368f0e7789",
+ }
+]"""
+ on {exec(listOf("docker", "inspect", "--type", "image", "image"))} doReturn """[
+ {
+ "Id": "48a93f6ac01c054dc8eb02313dc28450b48e4368cf29d8a8366200368f0e7789",
+ }
+]"""
+ on {exec(listOf("docker", "volume", "create", "-d", "titan", "-o", "path=path", "volume"))} doReturn "volume"
+ on {exec(listOf("docker", "volume", "rm", "volume"))} doReturn "volume"
+ on {exec(listOf("docker", "cp", "-a", "source/.", "titan-server:/var/lib/titan/mnt/target"))} doReturn ""
+ on {exec(listOf("docker", "rm", "-f", "container"))} doReturn ""
+ on {exec(listOf("docker", "rm", "", "container"))} doReturn ""
+ on {exec(listOf("docker", "run", "--name", "name", "image", "entry.sh"))} doReturn "entry output string"
+ on {exec(listOf("docker", "run", "--name", "name", "image"))} doReturn "no entry output string"
+ on {exec(listOf("docker", "run", "--privileged","--pid=host","--network=host","-d","--restart","always","--name=titan-launch","-v", "/var/lib:/var/lib","-v", "/run/docker:/run/docker","-v", "/lib:/var/lib/titan/system","-v", "titan-data:/var/lib/titan/data","-v", "/var/run/docker.sock:/var/run/docker.sock", "-e", "TITAN_IMAGE=titan:latest","titan:latest", "/bin/bash", "/titan/launch"))} doReturn ""
+ }
+ private val docker = Docker(executor)
+
+ @Test
+ fun `can get version`() {
+ assertEquals("Docker version 18.09.2, build 6247962", docker.version())
+ }
+
+ @Test
+ fun `can check if titan images are downloaded`() {
+ assertTrue(docker.titanIsDownloaded())
+ }
+
+ @Test
+ fun `can check if titan images are not downloaded`() {
+ val falseExecutor = mock {
+ on {exec(listOf("docker", "images", "titan", "--format", "\"{{.Repository}}\""))} doReturn ""
+ }
+ val falseDocker = Docker(falseExecutor)
+ assertFalse(falseDocker.titanIsDownloaded())
+ }
+
+ @Test
+ fun `can check if titan-launch is available`() {
+ assertTrue(docker.titanLaunchIsAvailable())
+ }
+
+ @Test
+ fun `can check if titan-launch is not available`() {
+ val falseExecutor = mock {
+ on {exec(listOf("docker", "ps", "-f", "name=titan-launch", "--format", "\"{{.Names}}\""))} doReturn ""
+ }
+ val falseDocker = Docker(falseExecutor)
+ assertFalse(falseDocker.titanLaunchIsAvailable())
+ }
+
+ @Test
+ fun `can get string from list`() {
+ val checkList = listOf("Test","String","From","List")
+ assertEquals(checkList, "Test String From List".toList())
+ }
+
+ @Test
+ fun `can check if titan is running`() {
+ assertTrue(docker.titanServerIsAvailable())
+ }
+
+ @Test
+ fun `can check if titan is not running`() {
+ val falseExecutor = mock {
+ on {exec(listOf("docker", "ps", "-f", "name=titan-server", "--format", "\"{{.Names}}\""))} doReturn ""
+ }
+ val falseDocker = Docker(falseExecutor)
+ assertFalse(falseDocker.titanServerIsAvailable())
+ }
+
+ @Test
+ fun `can pull titan`() {
+ assertTrue(docker.pull("titan").contains("sha256:bc6f593df26c0631a1ce3a06afdd5a4a8fda703b071fb18805091e2372c68201"))
+ }
+
+ @Test
+ fun `can tag images`() {
+ assertEquals("", docker.tag("source", "target"))
+ }
+
+ @Test
+ fun `can stop container`(){
+ assertEquals("container", docker.stop("container"))
+ }
+
+ @Test
+ fun `can start container`(){
+ assertEquals("container", docker.start("container"))
+ }
+
+ @Test
+ fun `can inspect container`(){
+ val result = docker.inspectContainer("container")
+ assertEquals("48a93f6ac01c054dc8eb02313dc28450b48e4368cf29d8a8366200368f0e7789", result!!.getString("Id"))
+ }
+
+ @Test
+ fun `can inspect image`(){
+ val result = docker.inspectImage("image")
+ assertEquals("48a93f6ac01c054dc8eb02313dc28450b48e4368cf29d8a8366200368f0e7789", result!!.getString("Id"))
+ }
+
+ @Test
+ fun `can create volume`(){
+ val result = docker.createVolume("volume", "path")
+ assertEquals("volume", result)
+ }
+
+ @Test
+ fun `can remove volume`(){
+ val result = docker.removeVolume("volume")
+ assertEquals("volume", result)
+ }
+
+ @Test
+ fun `can copy files to volume`(){
+ val result = docker.cp("source", "target")
+ assertEquals("", result)
+ }
+
+ @Test
+ fun `can force rm container`(){
+ val result = docker.rm("container", true)
+ assertEquals("", result)
+ }
+
+ @Test
+ fun `can rm container`(){
+ val result = docker.rm("container", false)
+ assertEquals("", result)
+ }
+
+ @Test
+ fun `can run container with entry`(){
+ val result = docker.run("image", "entry.sh", listOf("--name","name"))
+ assertEquals("entry output string", result)
+ }
+
+ @Test
+ fun `can run container without entry`(){
+ val result = docker.run("image", "", listOf("--name","name"))
+ assertEquals("no entry output string", result)
+ }
+
+ @Test
+ fun `can launch titan servers`() {
+ val result = docker.launchTitanServers()
+ assertEquals("", result)
+ }
+
+ @Test
+ fun `can get name from metadata`(){
+ val arguments = listOf("--name","nameValue")
+ assertEquals("nameValue", arguments.fetchName())
+ }
+
+ @Test
+ fun `can get generated name from metadata`(){
+ val arguments = emptyList()
+ val generatedName = arguments.fetchName()
+ assertTrue(generatedName is String)
+ assertTrue(generatedName.contains("_"))
+ }
+
+ @Test
+ fun `run arguments has detach mode`(){
+ val arguments = listOf("-d", "container", "image")
+ assertTrue(arguments.hasDetach())
+ val newArguments = listOf("--detach", "container", "image")
+ assertTrue(newArguments.hasDetach())
+ }
+
+ @Test
+ fun `fun arguments do not have detach mode`(){
+ val arguments = listOf("container", "image")
+ assertFalse(arguments.hasDetach())
+ }
+
+ @Test
+ fun `can get arguments from runtime string`(){
+ val runtime = "[--label, io.titandata.titan, --mount, type=volume,src=jerrytest/v0,dst=/var/lib/postgresql/data,volume-driver=titan, --name, jerrytest, -d, -p, 5432:5432, -e, POSTGRES_USER=postgres, -e, POSTGRES_PASSWORD=mysecretpassword, postgres:10]"
+ val arguments = runtime.runtimeToArguments()
+ assertFalse(arguments.contains("--mount"))
+ val expected = listOf("--label", "io.titandata.titan", "--name", "jerrytest", "-d", "-p", "5432:5432", "-e", "POSTGRES_USER=postgres", "-e", "POSTGRES_PASSWORD=mysecretpassword", "postgres:10")
+ assertEquals(expected, arguments)
+ val runtimeWithoutMount = "[--label, io.titandata.titan, --name, jerrytest, -d, -p, 5432:5432, -e, POSTGRES_USER=postgres, -e, POSTGRES_PASSWORD=mysecretpassword, postgres:10]"
+ val argumentsWithoutMount = runtimeWithoutMount.runtimeToArguments()
+ assertEquals(expected, argumentsWithoutMount)
+ }
+
+ @Test
+ fun `check output from methods`() {
+ //val checkDocker = Docker(CommandExecutor())
+ //println(checkDocker.removeVolume("jerry/v2"))
+
+ assertTrue(true)
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/clients/VolumeDriverTest.kt b/src/test/kotlin/io/titandata/titan/clients/VolumeDriverTest.kt
new file mode 100644
index 00000000..2dec2c3a
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/clients/VolumeDriverTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.clients
+
+import io.titandata.titan.utils.HttpHandler
+import org.junit.Test
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.json.JSONObject
+import kotlin.test.assertEquals
+
+class VolumeDriverTest {
+ private val mockServer = MockWebServer()
+ private val listResponse = MockResponse()
+ .setBody("{\"Volumes\":[{\"Mountpoint\":\"/var/lib/titan/mnt/jerry/v0\",\"Name\":\"jerry/v0\"}],\"Err\":\"\"}")
+ .setResponseCode(200)
+ private val mountResponse = MockResponse()
+ .setBody("{\"Err\":\"\",\"Mountpoint\":\"/var/lib/titan/mnt/elaine/v1\"}")
+ .setResponseCode(200)
+ private val unmountResponse = MockResponse()
+ .setBody("{\"Err\":\"\"}")
+ .setResponseCode(200)
+ private val volumeDriver = VolumeDriver(HttpHandler(), mockServer.url("/").toString())
+
+ @Test
+ fun `can get volume driver list`(){
+ mockServer.enqueue(listResponse)
+ val list = volumeDriver.list()
+ val array = list.getJSONArray("Volumes")[0] as JSONObject
+ assertEquals("/var/lib/titan/mnt/jerry/v0", array.getString("Mountpoint"))
+ }
+
+ @Test
+ fun `can mount new volume`(){
+ mockServer.enqueue(mountResponse)
+ val response = volumeDriver.mount("volumeName")
+ assertEquals("/var/lib/titan/mnt/elaine/v1", response.getString("Mountpoint"))
+ }
+
+ @Test
+ fun `can unmount volume`(){
+ mockServer.enqueue(unmountResponse)
+ val response = volumeDriver.unmount("volumeName")
+ assertEquals("", response.getString("Err"))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/exceptions/CommandExceptionTest.kt b/src/test/kotlin/io/titandata/titan/exceptions/CommandExceptionTest.kt
new file mode 100644
index 00000000..7cc0e9f2
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/exceptions/CommandExceptionTest.kt
@@ -0,0 +1,28 @@
+package io.titandata.titan.exceptions
+
+import org.junit.Test
+import kotlin.test.assertEquals
+import java.io.IOException
+import kotlin.test.assertFailsWith
+
+class CommandExceptionTest {
+
+ @Test
+ fun `can handle exception`() {
+ assertFailsWith {
+ throw CommandException("message", 1, "output")
+ }
+ }
+
+ @Test
+ fun `can get exception values`() {
+ try {
+ throw CommandException("message", 1, "output")
+ } catch (e: CommandException) {
+ assertEquals("message", e.message)
+ assertEquals(1, e.exitCode)
+ assertEquals("output", e.output)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/MockTest.kt b/src/test/kotlin/io/titandata/titan/providers/MockTest.kt
new file mode 100644
index 00000000..101f7e00
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/MockTest.kt
@@ -0,0 +1,223 @@
+package io.titandata.titan.providers
+
+import org.junit.Test
+import java.io.PrintStream
+import io.titandata.titan.providers.Mock as MockProvider
+import java.io.ByteArrayOutputStream
+import kotlin.test.assertEquals
+
+class MockTest {
+
+ private val mockProvider = MockProvider()
+
+ @Test
+ fun `can check install`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.checkInstall()
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Mock Provider Installed")
+ }
+
+ @Test
+ fun `can pull`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.pull("container", "commit", null)
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Pulling from remote")
+ }
+
+ @Test
+ fun `can push`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.push("container", "commit", null)
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Pushing to remote")
+ }
+
+ @Test
+ fun `can commit`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.commit("container", "message")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Committing new state")
+ }
+
+ @Test
+ fun `can install`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.install()
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Initializing new repository")
+ }
+
+ @Test
+ fun `can abort`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.abort("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Aborting current operation")
+ }
+
+ @Test
+ fun `can status`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.status("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Display current status")
+ }
+
+ @Test
+ fun `can remoteAdd`() {
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.remoteAdd("container", "uri", "remoteName")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Add remote")
+ }
+
+ @Test
+ fun `can remoteLog`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.remoteLog("container", null)
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Display remote log")
+ }
+
+ @Test
+ fun `can migrate`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.migrate("container", "name")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Migrating container to name controlled environment")
+ }
+
+ @Test
+ fun `can run`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.run(emptyList())
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Running controlled container")
+ }
+
+ @Test
+ fun `can uninstall`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.uninstall(true)
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Tearing down containers")
+ }
+
+ @Test
+ fun `can upgrade`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.upgrade(true, "0.0.1", false, "")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Upgrading to 0.0.1")
+ }
+
+ @Test
+ fun `can checkout`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.checkout("container", "hash")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Checking out data set hash")
+ }
+
+ @Test
+ fun `can list`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.list()
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "List containers")
+ }
+
+ @Test
+ fun `can log`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.log("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Log for container")
+ }
+
+ @Test
+ fun `can stop`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.stop("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Stopping container")
+ }
+
+ @Test
+ fun `can start`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.start("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Starting container")
+ }
+
+ @Test
+ fun `can remove`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.remove("container", true)
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Removing container")
+ }
+
+ @Test
+ fun `can cp`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.cp("container", "driver", "source", "path")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "copying data into container with driver from source")
+ }
+
+ @Test
+ fun `can clone`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ mockProvider.clone("http://user:pass@path", "container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "cloning container from http://user:pass@path")
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/ProviderFactoryTest.kt b/src/test/kotlin/io/titandata/titan/providers/ProviderFactoryTest.kt
new file mode 100644
index 00000000..1009097e
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/ProviderFactoryTest.kt
@@ -0,0 +1,26 @@
+package io.titandata.titan.providers
+
+import org.junit.Test
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import kotlin.test.assertEquals
+
+class ProviderFactoryTest {
+ val providerFactory = ProviderFactory()
+
+ @Test
+ fun `can get mock provider`(){
+ val provider = providerFactory.getFactory("mock")
+ //TODO test without reflection or add reflection to dev dependencies only
+ //assertThat(provider, instanceOf(Provider::class.java))
+ //assertEquals("io.titandata.titan.providers.Mock", provider::class.qualifiedName)
+ }
+
+ @Test
+ fun `can get local provider`(){
+ val provider = providerFactory.getFactory("Local")
+ //TODO test without reflection or add reflection to dev dependencies only
+ //assertThat(provider, instanceOf(Provider::class.java))
+ //assertEquals("io.titandata.titan.providers.Local", provider::class.qualifiedName)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/AbortTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/AbortTest.kt
new file mode 100644
index 00000000..6064a6be
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/AbortTest.kt
@@ -0,0 +1,16 @@
+package io.titandata.titan.providers.local
+
+import org.junit.Test
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+
+class AbortTest {
+ //TODO Update test for titan-server 0.3.1 release with URI and Multiple Remotes
+ //private val command = Abort(::getEngine)
+
+ @Test
+ fun `can instantiate`(){
+ //assertThat(command, instanceOf(Abort::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/CheckInstallTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/CheckInstallTest.kt
new file mode 100644
index 00000000..17119e36
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/CheckInstallTest.kt
@@ -0,0 +1,16 @@
+package io.titandata.titan.providers.local
+
+import org.junit.Test
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+
+class CheckInstallTest {
+ private fun exit(message: String, code: Int) {}
+ private val command = CheckInstall(::exit)
+
+ @Test
+ fun `can instantiate`(){
+ assertThat(command, instanceOf(CheckInstall::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/CheckoutTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/CheckoutTest.kt
new file mode 100644
index 00000000..fd36926d
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/CheckoutTest.kt
@@ -0,0 +1,39 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+import com.nhaarman.mockitokotlin2.*
+import org.junit.Test
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import kotlin.test.assertEquals
+
+class CheckoutTest {
+ private val executor = mock {
+ on {exec(listOf("docker","stop","container"))} doReturn "container"
+ on {exec(listOf("docker","start","container"))} doReturn "container"
+ }
+ private val docker = Docker(executor)
+
+ @Test
+ fun `can instantiate`(){
+ val command = Checkout()
+ assertThat(command, instanceOf(Checkout::class.java))
+ }
+
+ @Test
+ fun `can checkout`(){
+ val commitsApi: CommitsApi = mock()
+ doNothing().whenever(commitsApi).checkoutCommit("container", "hash")
+ val command = Checkout(executor, docker, commitsApi)
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ command.checkout("container", "hash")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Stopping container container\nCheckout hash\nStarting container container\nhash checked out")
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/CloneTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/CloneTest.kt
new file mode 100644
index 00000000..9de0ccdf
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/CloneTest.kt
@@ -0,0 +1,19 @@
+package io.titandata.titan.providers.local
+
+import org.junit.Test
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+
+class CloneTest {
+ private fun remoteAdd(container:String, uri: String, remoteName: String?) {}
+ private fun pull(container: String, commit: String?, remoteName: String?) {}
+ private fun checkout(container: String, hash: String) {}
+ private fun run(arguments: List, createRepo:Boolean) {}
+ private val command = Clone(::remoteAdd, ::pull, ::checkout, ::run)
+
+ @Test
+ fun `can instantiate`(){
+ assertThat(command, instanceOf(Clone::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/CommitTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/CommitTest.kt
new file mode 100644
index 00000000..41e2cf08
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/CommitTest.kt
@@ -0,0 +1,61 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+import io.titandata.client.apis.RepositoriesApi
+import io.titandata.models.Repository
+import com.nhaarman.mockitokotlin2.doReturn
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Test
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import kotlin.test.assertEquals
+
+class CommitTest {
+
+ @Test
+ fun `can instantiate`(){
+ val command = Commit("user", "email")
+ assertThat(command, instanceOf(Commit::class.java))
+ }
+ /*
+ @Test
+ fun `can commit with system user`(){
+ val reposApi: RepositoriesApi = mock()
+ val commitsApi: CommitsApi = mock()
+ val repo = Repository("container", mapOf("container" to "container", "runtime" to "runtime"))
+ doReturn(repo).whenever(reposApi).getRepository("container")
+ val commitObj = io.titandata.titan.models.Commit("uuid", mapOf("message" to "message", "author" to "systemuser", "container" to "container", "runtime" to "runtime"))
+ doReturn(commitObj).whenever(commitsApi).createCommit("container", commitObj)
+ val command = Commit(reposApi,commitsApi,"systemuser", "uuid")
+
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ command.commit("container", "message")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Commit uuid")
+ }
+
+ @Test
+ fun `can commit without user`(){
+ val reposApi: RepositoriesApi = mock()
+ val commitsApi: CommitsApi = mock()
+ val repo = Repository("container", mapOf("container" to "container", "runtime" to "runtime"))
+ doReturn(repo).whenever(reposApi).getRepository("container")
+ val commitObj = io.titandata.titan.models.Commit("uuid", mapOf("message" to "message", "author" to "unknown", "container" to "container", "runtime" to "runtime"))
+ doReturn(commitObj).whenever(commitsApi).createCommit("container", commitObj)
+ val command = Commit(reposApi,commitsApi,null, "uuid")
+
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ command.commit("container", "message")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "Commit uuid")
+ }
+
+ */
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/CpTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/CpTest.kt
new file mode 100644
index 00000000..f7363d72
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/CpTest.kt
@@ -0,0 +1,17 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class CpTest {
+ private fun exit(message: String, code: Int) {}
+ private fun start(container: String) {}
+ private fun stop(container: String) {}
+ private val command = Cp(::exit,::start,::stop)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Cp::class.java))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/InstallTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/InstallTest.kt
new file mode 100644
index 00000000..d3a43efd
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/InstallTest.kt
@@ -0,0 +1,14 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class InstallTest {
+ private val command = Install("0.3.0", "delphix")
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Install::class.java))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/LogTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/LogTest.kt
new file mode 100644
index 00000000..ccf0f150
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/LogTest.kt
@@ -0,0 +1,38 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+import com.nhaarman.mockitokotlin2.doReturn
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+import kotlin.test.assertEquals
+
+class LogTest {
+
+ @Test
+ fun `can instantiate`(){
+ val command = Log()
+ Assert.assertThat(command, CoreMatchers.instanceOf(Log::class.java))
+ }
+
+ @Test
+ fun `can get log`(){
+ val commitsApi: CommitsApi = mock()
+ val commitObj = io.titandata.models.Commit("uuid", mapOf("message" to "message", "author" to "unknown", "container" to "container", "runtime" to "runtime", "timestamp" to "timestamp"))
+ val commitObj2 = io.titandata.models.Commit("uuid2", mapOf("message" to "", "author" to "unknown", "container" to "container", "runtime" to "runtime", "timestamp" to "timestamp"))
+ val commitArray = arrayOf(commitObj,commitObj2)
+ doReturn(commitArray).whenever(commitsApi).listCommits("container")
+ val command = Log(commitsApi)
+
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ command.log("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "commit uuid\nAuthor: unknown\nDate: timestamp\n\nmessage\n\ncommit uuid2\nAuthor: unknown\nDate: timestamp")
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/MigrateTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/MigrateTest.kt
new file mode 100644
index 00000000..e3c3d989
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/MigrateTest.kt
@@ -0,0 +1,16 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class MigrateTest {
+ private fun exit(message: String, code: Int) {}
+ private fun commit(container: String, message: String) {}
+ private val command = Migrate(::exit,::commit)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Migrate::class.java))
+ }
+}
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/PullTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/PullTest.kt
new file mode 100644
index 00000000..1100c33b
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/PullTest.kt
@@ -0,0 +1,16 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class PullTest {
+ //TODO Update test for titan-server 0.3.1 release with URI and Multiple Remotes
+ private fun exit(message: String, code: Int) {}
+ private val command = Pull(::exit)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Pull::class.java))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/PushTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/PushTest.kt
new file mode 100644
index 00000000..a1ce4987
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/PushTest.kt
@@ -0,0 +1,23 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class PushTest {
+ //TODO Update test for titan-server 0.3.1 release with URI and Multiple Remotes
+
+ /*
+ private fun getEngine(metadata: Map): Engine {
+ return Engine("","","")
+ }
+ private fun abortIfRunning(metadata: Map) {}
+ private fun exit(message: String, code: Int) {}
+ private val command = Push(::getEngine, ::abortIfRunning, ::exit)
+ */
+
+ @Test
+ fun `can instantiate`(){
+ //Assert.assertThat(command, CoreMatchers.instanceOf(Push::class.java))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/RemoteAddTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/RemoteAddTest.kt
new file mode 100644
index 00000000..2f7ca03a
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/RemoteAddTest.kt
@@ -0,0 +1,20 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class RemoteAddTest {
+ //TODO Update test for titan-server 0.3.1 release with URI and Multiple Remotes
+
+ private fun exit(message: String, code: Int) {}
+ //private val command = RemoteAdd(::getEngine,::exit)
+
+ /*
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(RemoteAdd::class.java))
+ }
+ */
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/RemoteLogTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/RemoteLogTest.kt
new file mode 100644
index 00000000..e5810c22
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/RemoteLogTest.kt
@@ -0,0 +1,18 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class RemoteLogTest {
+ //TODO Update test for titan-server 0.3.1 release with URI and Multiple Remotes
+
+ //private fun exit(message: String, code: Int) {}
+ //private val command = RemoteLog(::getEngine,::exit)
+
+ @Test
+ fun `can instantiate`(){
+ //Assert.assertThat(command, CoreMatchers.instanceOf(RemoteLog::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/RemoveTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/RemoveTest.kt
new file mode 100644
index 00000000..6761c46c
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/RemoveTest.kt
@@ -0,0 +1,16 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class RemoveTest {
+ private fun exit(message: String, code: Int) {}
+ private val command = Remove(::exit)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Remove::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/RunTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/RunTest.kt
new file mode 100644
index 00000000..9df07095
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/RunTest.kt
@@ -0,0 +1,16 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class RunTest {
+ private fun exit(message: String, code: Int) {}
+ private val command = Run(::exit)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Run::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/StartTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/StartTest.kt
new file mode 100644
index 00000000..219d5aaa
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/StartTest.kt
@@ -0,0 +1,35 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+import org.junit.Test
+import kotlin.test.assertEquals
+import com.nhaarman.mockitokotlin2.doReturn
+import com.nhaarman.mockitokotlin2.mock
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+
+class StartTest {
+ private val executor = mock {
+ on {exec(listOf("docker","start","container"))} doReturn ""
+ }
+ private val docker = Docker(executor)
+ private val command = Start(executor, docker)
+
+ @Test
+ fun `can instantiate`(){
+ assertThat(command, instanceOf(Start::class.java))
+ }
+
+ @Test
+ fun `can start`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ command.start("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "container started")
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/StatusTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/StatusTest.kt
new file mode 100644
index 00000000..3fa9ef47
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/StatusTest.kt
@@ -0,0 +1,19 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.providers.Container
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class StatusTest {
+ private fun getContainersStatus(): List {
+ return listOf()
+ }
+ private val command = Status(::getContainersStatus)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Status::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/StopTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/StopTest.kt
new file mode 100644
index 00000000..3ebb0ce0
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/StopTest.kt
@@ -0,0 +1,35 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.clients.Docker
+import io.titandata.titan.utils.CommandExecutor
+import org.junit.Test
+import kotlin.test.assertEquals
+import com.nhaarman.mockitokotlin2.doReturn
+import com.nhaarman.mockitokotlin2.mock
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+
+class StopTest {
+ private val executor = mock {
+ on {exec(listOf("docker","start","container"))} doReturn ""
+ }
+ private val docker = Docker(executor)
+ private val command = Stop(executor, docker)
+
+ @Test
+ fun `can instantiate`(){
+ assertThat(command, instanceOf(Stop::class.java))
+ }
+
+ @Test
+ fun `can start`(){
+ val byteStream = ByteArrayOutputStream()
+ System.setOut(PrintStream(byteStream))
+ command.stop("container")
+ byteStream.flush()
+ val expected = String(byteStream.toByteArray()).trim()
+ assertEquals(expected, "container stopped")
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/UninstallTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/UninstallTest.kt
new file mode 100644
index 00000000..6da24905
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/UninstallTest.kt
@@ -0,0 +1,17 @@
+package io.titandata.titan.providers.local
+
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class UninstallTest {
+ private fun exit(message: String, code: Int) {}
+ private fun remove(container: String, force: Boolean) {}
+ private val command = Uninstall(::exit, ::remove)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Uninstall::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/providers/local/UpgradeTest.kt b/src/test/kotlin/io/titandata/titan/providers/local/UpgradeTest.kt
new file mode 100644
index 00000000..938a8f91
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/providers/local/UpgradeTest.kt
@@ -0,0 +1,22 @@
+package io.titandata.titan.providers.local
+
+import io.titandata.titan.providers.Container
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.junit.Test
+
+class UpgradeTest {
+ private fun start(container: String) {}
+ private fun stop(container: String) {}
+ private fun exit(message: String, code: Int) {}
+ private fun getContainersStatus(): List {
+ return listOf()
+ }
+ private val command = Upgrade(::start,::stop,::exit,::getContainersStatus)
+
+ @Test
+ fun `can instantiate`(){
+ Assert.assertThat(command, CoreMatchers.instanceOf(Upgrade::class.java))
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/titandata/titan/utils/HttpHandlerTest.kt b/src/test/kotlin/io/titandata/titan/utils/HttpHandlerTest.kt
new file mode 100644
index 00000000..300072b7
--- /dev/null
+++ b/src/test/kotlin/io/titandata/titan/utils/HttpHandlerTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.utils
+
+import org.junit.Test
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+import io.titandata.titan.utils.HttpHandler.Companion.asString
+import io.titandata.titan.utils.HttpHandler.Companion.asBytes
+import io.titandata.titan.utils.HttpHandler.Companion.asJsonObject
+import io.titandata.titan.utils.HttpHandler.Companion.asJsonArray
+import okhttp3.ResponseBody
+import org.hamcrest.CoreMatchers.instanceOf
+import org.junit.Assert.assertThat
+import org.mockito.BDDMockito.then
+import org.mockito.BDDMockito.given
+import java.io.IOException
+import kotlin.test.assertFailsWith
+
+class HttpHandlerTest {
+ private val mockServer = MockWebServer()
+ private val mockUrl = mockServer.url("/").toString()
+ private val handler = HttpHandler()
+
+ @Test
+ fun `can get`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(200)
+ mockedResponse.setBody("1.1.1.1")
+ mockServer.enqueue(mockedResponse)
+ val response = handler.get(mockUrl)
+ assertThat(response, instanceOf(ResponseBody::class.java))
+ assertEquals("1.1.1.1", response.string())
+ }
+
+ @Test
+ fun `can post`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(200)
+ mockedResponse.setBody("1.1.1.1")
+ mockServer.enqueue(mockedResponse)
+ val response = handler.post(mockUrl, mapOf("data" to "data"))
+ assertThat(response, instanceOf(ResponseBody::class.java))
+ assertEquals("1.1.1.1", response.string())
+ }
+
+ @Test
+ fun `can handle exception`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(500)
+ mockServer.enqueue(mockedResponse)
+ assertFailsWith {
+ handler.get(mockUrl)
+ }
+ }
+
+ @Test
+ fun `can get asString`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(200)
+ mockedResponse.setBody("1.1.1.1")
+ mockServer.enqueue(mockedResponse)
+ val response = handler.get(mockUrl).asString()
+ assertTrue(response is String)
+ assertEquals("1.1.1.1", response)
+ }
+
+ @Test
+ fun `can get asBytes`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(200)
+ mockedResponse.setBody("1.1.1.1")
+ mockServer.enqueue(mockedResponse)
+ val response = handler.get(mockUrl).asBytes()
+ assertTrue(response is ByteArray)
+ assertEquals(46, response[1])
+ }
+
+ @Test
+ fun `can get asJsonObject`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(200)
+ mockedResponse.setBody("{\"json\":\"data\"}")
+ mockServer.enqueue(mockedResponse)
+ val response = handler.get(mockUrl).asJsonObject()
+ assertEquals("data", response.getString("json"))
+ }
+
+ @Test
+ fun `can get asJsonArray`() {
+ val mockedResponse = MockResponse()
+ mockedResponse.setResponseCode(200)
+ mockedResponse.setBody("[{\"json\":\"data\"}]")
+ mockServer.enqueue(mockedResponse)
+ val response = handler.get(mockUrl).asJsonArray()
+ val firstItem = response.getJSONObject(0)
+ assertEquals("data", firstItem.getString("json"))
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 00000000..ca6ee9ce
--- /dev/null
+++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file