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