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")