diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 8e0fdea2..7e151d59 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -25,13 +25,13 @@ jobs:
if: matrix.os == 'macOS-10.14'
uses: actions/upload-artifact@master
with:
- name: titan-cli-0.3.0-darwin_amd64.zip
+ name: titan-cli-darwin_amd64.zip
path: releases
- name: Upload ${{ matrix.os }} Binary
if: matrix.os == 'ubuntu-18.04'
uses: actions/upload-artifact@master
with:
- name: titan-cli-0.3.0-linux_amd64.zip
+ name: titan-cli-linux_amd64.tar
path: releases
release:
name: Draft Release
@@ -41,11 +41,11 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/download-artifact@master
with:
- name: titan-cli-0.3.0-darwin_amd64.zip
+ name: titan-cli-darwin_amd64.zip
path: releases
- uses: actions/download-artifact@master
with:
- name: titan-cli-0.3.0-linux_amd64.zip
+ name: titan-cli-linux_amd64.tar
path: releases
- name: Draft Release
if: startsWith(github.ref, 'refs/tags/')
diff --git a/README.md b/README.md
index f84cef3d..4580f3b4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@
# Titan
## Your Code. Your Environment. Your Data.
+
+![](https://github.com/titan-data/titan/workflows/Publish/badge.svg)
+![GitHub release (latest by date)](https://img.shields.io/github/v/release/titan-data/titan)
+![GitHub All Releases](https://img.shields.io/github/downloads/titan-data/titan/total)
+
## Getting Started
### Requirements
diff --git a/VERSION b/VERSION
index a2268e2d..9fc80f93 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.1
\ No newline at end of file
+0.3.2
\ No newline at end of file
diff --git a/config/jni-config.json b/config/jni-config.json
index 0d4f101c..c0b782e5 100644
--- a/config/jni-config.json
+++ b/config/jni-config.json
@@ -1,2 +1,11 @@
[
+ {
+ "name":"java.net.SocketTimeoutException",
+ "allDeclaredFields":true,
+ "allPublicMethods":true,
+ "allDeclaredConstructors":true,
+ "allPublicConstructors":true,
+ "allDeclaredClasses":true,
+ "allPublicClasses":true
+ }
]
diff --git a/docs/src/cli/cli.rst b/docs/src/cli/cli.rst
index 6ed55121..5c43aa71 100644
--- a/docs/src/cli/cli.rst
+++ b/docs/src/cli/cli.rst
@@ -37,6 +37,7 @@ Subcommands
cmd/checkout
cmd/clone
cmd/commit
+ cmd/delete
cmd/cp
cmd/install
cmd/log
diff --git a/docs/src/cli/cmd/clone.rst b/docs/src/cli/cmd/clone.rst
index a55bbb72..45a98b29 100644
--- a/docs/src/cli/cmd/clone.rst
+++ b/docs/src/cli/cmd/clone.rst
@@ -14,7 +14,7 @@ Syntax
::
- titan clone [repository]
+ titan clone -c [id] [repository]
Arguments
---------
@@ -29,6 +29,12 @@ repository
the name of the original repository is used (which may or may not match
the name used in the remote URI).
+Options
+-------
+
+-c, --commit id Specify the commit ID to checkout.
+
+
Example
-------
diff --git a/docs/src/cli/cmd/delete.rst b/docs/src/cli/cmd/delete.rst
new file mode 100644
index 00000000..d0175b37
--- /dev/null
+++ b/docs/src/cli/cmd/delete.rst
@@ -0,0 +1,41 @@
+.. _cli_cmd_delete:
+
+titan delete
+==============
+
+Delete a previous commit from the current container. The commit must be
+present in `titan log`. For more general information on managing local
+commits, see the :ref:`local_commit` section.
+
+.. warning::
+
+ This will stop and start the associated docker container if it is already
+ running. This will interrupt any active connections, and may require
+ client-specific actions to reconnect.
+
+Syntax
+------
+
+::
+
+ titan delete -c
+
+Arguments
+---------
+
+repository
+ *Required*. The name of the target repository.
+
+Options
+-------
+
+-c, --commit id *Required*. Specify the commit ID to delete. Must be a known
+ commit in `titan log` for the given repository.
+
+Example
+-------
+
+::
+
+ $ titan delete -c 7715327e-9535-4263-870f-f5c92c18cb23 myrepo
+ 7715327e-9535-4263-870f-f5c92c18cb23 deleted
diff --git a/docs/src/remote/clone.rst b/docs/src/remote/clone.rst
index 63a95f9d..89599f68 100644
--- a/docs/src/remote/clone.rst
+++ b/docs/src/remote/clone.rst
@@ -21,5 +21,8 @@ uses whatever the configuration was as of the last commit.
.. note::
- The clone command currently always uses the latest commit. The ability to
- select a specific commit to use will be added in a future release.
+ The clone command currently always uses the latest commit by default. To clone a specific
+ commit, add the commit GUID to the URI with a `#` tag. Example::
+
+ $ titan clone s3://titan-data-demo/hello-world/postgres#0f53a6a4-90ff-4f8c-843a-a6cce36f4f4f hello-world
+
diff --git a/pom.xml b/pom.xml
index 44e0f74a..4a46cf2e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
titan
Titan CLI
- 0.3.1
+ 0.3.2
1.8
@@ -123,7 +123,7 @@
io.titandata
titan-client
- 0.4.1
+ 0.4.4
diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh
index 1773fb5e..cb8f2fe0 100755
--- a/scripts/build-linux.sh
+++ b/scripts/build-linux.sh
@@ -12,14 +12,15 @@ ${entry} native-image -cp /cli/target/titan-$version-jar-with-dependencies.jar\
-H:+ReportUnsupportedElementsAtRuntime\
-H:ReflectionConfigurationFiles=/cli/config/reflect-config.json\
-H:ResourceConfigurationFiles=/cli/config/resource-config.json\
+ -H:JNIConfigurationFiles=/cli/config/jni-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\
+ -J-Djava.security.properties=/cli/java.security.overrides\
--allow-incomplete-classpath\
--enable-http\
--enable-https
${entry} mkdir -p /cli/releases/
-${entry} tar -cvf /cli/releases/titan-cli-$version-linux_amd64.zip titan
\ No newline at end of file
+${entry} tar -cvf /cli/releases/titan-cli-$version-linux_amd64.tar titan
\ No newline at end of file
diff --git a/scripts/build-osx.sh b/scripts/build-osx.sh
index 8281491b..b50de59c 100755
--- a/scripts/build-osx.sh
+++ b/scripts/build-osx.sh
@@ -12,6 +12,7 @@ native-image -cp ${PWD}/target/titan-$version-jar-with-dependencies.jar\
-H:+ReportUnsupportedElementsAtRuntime\
-H:ReflectionConfigurationFiles=${PWD}/config/reflect-config.json\
-H:ResourceConfigurationFiles=${PWD}/config/resource-config.json\
+ -H:JNIConfigurationFiles=${PWD}/config/jni-config.json\
-H:+AddAllCharsets\
--initialize-at-run-time=org.bouncycastle.crypto.prng.SP800SecureRandom\
--initialize-at-run-time=org.bouncycastle.jcajce.provider.drbg.DRBG$Default\
diff --git a/src/main/kotlin/io/titandata/titan/Cli.kt b/src/main/kotlin/io/titandata/titan/Cli.kt
index a5340030..fc758a62 100644
--- a/src/main/kotlin/io/titandata/titan/Cli.kt
+++ b/src/main/kotlin/io/titandata/titan/Cli.kt
@@ -42,6 +42,7 @@ object Cli {
import(startModule)
import(stopModule)
import(commitModule)
+ import(deleteModule)
import(checkoutModule)
import(listModule)
import(logModule)
diff --git a/src/main/kotlin/io/titandata/titan/commands/Clone.kt b/src/main/kotlin/io/titandata/titan/commands/Clone.kt
index fa085dbf..17da63f0 100644
--- a/src/main/kotlin/io/titandata/titan/commands/Clone.kt
+++ b/src/main/kotlin/io/titandata/titan/commands/Clone.kt
@@ -9,6 +9,7 @@ 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 com.github.ajalt.clikt.parameters.options.option
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.inSet
@@ -18,9 +19,11 @@ 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()
+ private val commit by option("-c", "--commit", help="Commit GUID to pull from, defaults to latest")
+
override fun run() {
val provider = dependencies.provider
- provider.clone(uri, repository)
+ provider.clone(uri, repository, commit)
}
}
diff --git a/src/main/kotlin/io/titandata/titan/commands/Delete.kt b/src/main/kotlin/io/titandata/titan/commands/Delete.kt
new file mode 100644
index 00000000..e98794e7
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/commands/Delete.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 Delete : CliktCommand(
+ help = "Delete objects from titan"
+) {
+ private val dependencies: Dependencies by requireObject()
+ private val repository by argument()
+ private val commit by option("-c", "--commit", help="Commit GUID to delete")
+ override fun run() {
+ val provider = dependencies.provider
+ provider.delete(repository, commit)
+ }
+}
+
+val deleteModule = Kodein.Module("delete") {
+ bind().inSet() with provider {
+ Delete()
+ }
+}
diff --git a/src/main/kotlin/io/titandata/titan/providers/Local.kt b/src/main/kotlin/io/titandata/titan/providers/Local.kt
index 569cfa93..46dac963 100644
--- a/src/main/kotlin/io/titandata/titan/providers/Local.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/Local.kt
@@ -18,7 +18,7 @@ data class Container (
)
class Local: Provider {
- private val titanServerVersion = "0.4.1"
+ private val titanServerVersion = "0.4.4"
private val dockerRegistryUrl = "titandata"
private val httpHandler = HttpHandler()
@@ -171,9 +171,17 @@ class Local: Provider {
return cpCommand.cp(container, driver, source, path)
}
- override fun clone(uri: String, container: String?) {
+ override fun clone(uri: String, container: String?, commit: String?) {
val runCommand = Run(::exit, commandExecutor, docker)
val cloneCommand = Clone(::remoteAdd, ::pull, ::checkout, runCommand::run, ::remove, commandExecutor, docker)
- return cloneCommand.clone(uri, container)
+ return cloneCommand.clone(uri, container, commit)
+ }
+
+ override fun delete(repository: String, commit: String?) {
+ val deleteCommand = Delete()
+ if (!commit.isNullOrEmpty()) {
+ return deleteCommand.deleteCommit(repository, commit)
+ }
+ println("No object found to delete.")
}
}
diff --git a/src/main/kotlin/io/titandata/titan/providers/Mock.kt b/src/main/kotlin/io/titandata/titan/providers/Mock.kt
index e8a152a3..c5053a49 100644
--- a/src/main/kotlin/io/titandata/titan/providers/Mock.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/Mock.kt
@@ -89,7 +89,11 @@ class Mock: Provider {
println("copying data into $container with $driver from $source")
}
- override fun clone(uri: String, container: String?) {
+ override fun clone(uri: String, container: String?, commit: String?) {
println("cloning $container from $uri")
}
+
+ override fun delete(repository: String, commit: String?) {
+ println("deleting $commit from $repository")
+ }
}
diff --git a/src/main/kotlin/io/titandata/titan/providers/Provider.kt b/src/main/kotlin/io/titandata/titan/providers/Provider.kt
index d53232ed..21dd21f4 100644
--- a/src/main/kotlin/io/titandata/titan/providers/Provider.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/Provider.kt
@@ -17,11 +17,12 @@ interface Provider {
fun uninstall(force: Boolean)
fun upgrade(force: Boolean, version: String, finalize: Boolean, path: String?)
fun checkout(container: String, guid: String)
+ fun delete(repository: String, commit: 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?)
+ fun clone(uri: String, container: String?, commit: String?)
}
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt b/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt
index c8f73128..fb58ef42 100644
--- a/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Clone.kt
@@ -6,11 +6,13 @@ package io.titandata.titan.providers.local
import io.titandata.client.apis.RemotesApi
import io.titandata.client.apis.RepositoriesApi
+import io.titandata.models.Commit
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
+import io.titandata.titan.exceptions.CommandException
class Clone (
private val remoteAdd: (container:String, uri: String, remoteName: String?) -> Unit,
@@ -24,18 +26,27 @@ class Clone (
private val repositoriesApi: RepositoriesApi = RepositoriesApi(),
private val remoteUtil: RemoteUtil = RemoteUtil()
) {
- fun clone(uri: String, container: String?) {
+ fun clone(uri: String, container: String?, guid: String?) {
val repoName = when(container){
- null -> uri.split("/").last()
+ null -> uri.split("/").last().substringBefore('#')
else -> container
}
+ val commitId = when {
+ guid.isNullOrEmpty() && uri.contains('#') -> uri.split("#").last()
+ else -> guid
+ }
val repository = Repository(repoName, emptyMap())
try {
repositoriesApi.createRepository(repository)
- remoteAdd(repoName, uri, null)
+ remoteAdd(repoName, uri.substringBefore('#'), null)
val remote = remotesApi.getRemote(repoName, "origin")
- val remoteCommits = remotesApi.listRemoteCommits(repoName, remote.name, remoteUtil.getParameters(remote))
- val commit = remoteCommits.first()
+ var commit = Commit("id", emptyMap())
+ if (commitId.isNullOrEmpty()) {
+ val remoteCommits = remotesApi.listRemoteCommits(repoName, remote.name, remoteUtil.getParameters(remote))
+ commit = remoteCommits.first()
+ } else {
+ commit = remotesApi.getRemoteCommit(repoName, remote.name, commitId, remoteUtil.getParameters(remote))
+ }
docker.pull(commit.properties["container"] as String)
val runtime = commit.properties["runtime"] as String
val arguments = runtime.runtimeToArguments().toMutableList()
@@ -44,9 +55,10 @@ class Clone (
run(arguments, false)
pull(repoName, commit.id, null)
checkout(repoName, commit.id)
- } catch (e: Throwable) {
+ } catch (e: CommandException) {
println("Clone failed.")
println(e.message)
+ println(e.output)
remove(repository.name, true)
}
}
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Delete.kt b/src/main/kotlin/io/titandata/titan/providers/local/Delete.kt
new file mode 100644
index 00000000..28361d68
--- /dev/null
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Delete.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2019 by Delphix. All rights reserved.
+ */
+
+package io.titandata.titan.providers.local
+
+import io.titandata.client.apis.CommitsApi
+
+class Delete (
+ private val commitsApi: CommitsApi = CommitsApi()
+) {
+ fun deleteCommit(repository: String, commit: String) {
+ commitsApi.deleteCommit(repository, commit)
+ println("$commit deleted")
+ }
+}
\ 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
index b2750615..76c127ab 100644
--- a/src/main/kotlin/io/titandata/titan/providers/local/Pull.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Pull.kt
@@ -53,7 +53,9 @@ class Pull (
val statuses = operationsApi.getProgress(container, operation.id)
for (status in statuses) {
if (status.type != ProgressEntry.Type.PROGRESS) {
- println(status.message)
+ if (!status.message.isNullOrEmpty()) {
+ println(status.message)
+ }
} else {
val subMessage = status.message as String
if (subMessage.length > padLen) {
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/Push.kt b/src/main/kotlin/io/titandata/titan/providers/local/Push.kt
index cbd85707..2a4d8299 100644
--- a/src/main/kotlin/io/titandata/titan/providers/local/Push.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/local/Push.kt
@@ -53,7 +53,9 @@ class Push (
val statuses = operationsApi.getProgress(container, operation.id)
for (status in statuses) {
if (status.type != ProgressEntry.Type.PROGRESS) {
- println(status.message)
+ if (!status.message.isNullOrEmpty()) {
+ println(status.message)
+ }
} else {
val subMessage = status.message as String
if (subMessage.length > padLen) {
diff --git a/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt b/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt
index f81dacd2..748c8ab4 100644
--- a/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt
+++ b/src/main/kotlin/io/titandata/titan/providers/local/RemoteList.kt
@@ -5,6 +5,7 @@
package io.titandata.titan.providers.local
import io.titandata.client.apis.RemotesApi
+import io.titandata.serialization.RemoteUtil
class RemoteList (
private val remotesApi: RemotesApi = RemotesApi()
@@ -13,9 +14,9 @@ class RemoteList (
fun list(container: String) {
val remotes = remotesApi.listRemotes(container)
- System.out.printf("%-20s %s${n}", "REMOTE", "PROVIDER")
+ System.out.printf("%-20s %-20s %s${n}", "REMOTE", "URI")
for (remote in remotes) {
- System.out.printf("%-20s %s${n}", remote.name, remote.provider)
+ System.out.printf("%-20s %-20s %s${n}", remote.name, RemoteUtil().toUri(remote).first)
}
}
}
\ 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
index 04fd928c..e4a06f9c 100644
--- a/src/main/kotlin/io/titandata/titan/utils/CommandExecutor.kt
+++ b/src/main/kotlin/io/titandata/titan/utils/CommandExecutor.kt
@@ -7,6 +7,7 @@ package io.titandata.titan.utils
import io.titandata.titan.exceptions.CommandException
import java.io.IOException
import java.util.concurrent.TimeUnit
+import org.apache.commons.lang3.SystemUtils
/**
* ORIGINAL FILE FROM TITAN-SERVER
@@ -27,7 +28,14 @@ class CommandExecutor(val timeout: Long = 10) {
builder.command(args)
val process = builder.start()
try {
- process.waitFor(timeout, TimeUnit.MINUTES)
+ /**
+ Windows has an issue with the process.waitFor being at the beginning of this loop.
+ It has been moved to the end with a condition special to Windows to catch this OS
+ difference. Linux and MacOS want the process.waitFor at the beginning of the loop.
+ */
+ if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX) {
+ process.waitFor(timeout, TimeUnit.MINUTES)
+ }
val output = process.inputStream.bufferedReader().readText()
if (process.isAlive) {
throw IOException("Timed out waiting for command: $args")
@@ -40,6 +48,9 @@ class CommandExecutor(val timeout: Long = 10) {
exitCode = process.exitValue(),
output = errOutput)
}
+ if (SystemUtils.IS_OS_WINDOWS) {
+ process.waitFor(timeout, TimeUnit.MINUTES)
+ }
return output
} finally {
process.destroy()
diff --git a/src/main/resources/VERSION b/src/main/resources/VERSION
index a2268e2d..9fc80f93 100644
--- a/src/main/resources/VERSION
+++ b/src/main/resources/VERSION
@@ -1 +1 @@
-0.3.1
\ No newline at end of file
+0.3.2
\ 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
index a25e649f..9bc18186 100644
--- a/src/test/kotlin/io/titandata/titan/providers/MockTest.kt
+++ b/src/test/kotlin/io/titandata/titan/providers/MockTest.kt
@@ -214,7 +214,7 @@ class MockTest {
fun `can clone`(){
val byteStream = ByteArrayOutputStream()
System.setOut(PrintStream(byteStream))
- mockProvider.clone("http://user:pass@path", "container")
+ mockProvider.clone("http://user:pass@path", "container", null)
byteStream.flush()
val expected = String(byteStream.toByteArray()).trim()
assertEquals(expected, "cloning container from http://user:pass@path")