Skip to content

Commit

Permalink
Merge pull request #23 from test-editor/feature/allow_ssh_to_repo
Browse files Browse the repository at this point in the history
add ssh transport configuration
  • Loading branch information
Jan Jelschen authored Dec 21, 2017
2 parents b93784a + ff7d43c commit 0fe02b2
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 9 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ subprojects {
group = 'org.testeditor.web'

ext.versions = [
dropwizard: '1.2.0',
dropwizard: '1.2.2',
xtext: '2.13.0'
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ class FileTestUtils {
return Files.asCharSource(file, UTF_8).read
}

def void write(File parent, String child, String contents) {
def File write(File parent, String child, String contents) {
val fileToWrite = new File(parent, child)
Files.createParentDirs(fileToWrite)
fileToWrite.createNewFile // will not override existing file
Files.asCharSink(fileToWrite, UTF_8).write(contents)
return fileToWrite
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ abstract class XtextApplication<T extends XtextConfiguration> extends Dropwizard
}

protected def void initializeXtextIndex(T configuration, Environment environment) {
gitService.init(configuration.localRepoFileRoot, configuration.remoteRepoUrl)
gitService.init(configuration.localRepoFileRoot, configuration.remoteRepoUrl, configuration.privateKeyLocation, configuration.knownHostsLocation)
indexUpdater.addToIndex(new File(configuration.localRepoFileRoot))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ import org.testeditor.web.dropwizard.DropwizardApplicationConfiguration
public class XtextConfiguration extends DropwizardApplicationConfiguration {

@NotEmpty @JsonProperty
String localRepoFileRoot
String localRepoFileRoot = 'repo'

@NotEmpty @JsonProperty
String remoteRepoUrl

@JsonProperty
String privateKeyLocation

@JsonProperty
String knownHostsLocation

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package org.testeditor.web.xtext.index.persistence

import com.google.common.annotations.VisibleForTesting
import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException
import com.jcraft.jsch.Session
import java.io.File
import java.util.List
import javax.inject.Singleton
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.api.TransportCommand
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.transport.JschConfigSessionFactory
import org.eclipse.jgit.transport.OpenSshConfig.Host
import org.eclipse.jgit.transport.SshTransport
import org.eclipse.jgit.treewalk.CanonicalTreeParser
import org.eclipse.jgit.util.FS
import org.eclipse.xtend.lib.annotations.Accessors
import org.slf4j.LoggerFactory

Expand All @@ -29,12 +38,17 @@ class GitService {

@Accessors(PUBLIC_GETTER)
File projectFolder


String privateKeyLocation
String knownHostsLocation

/**
* initialize this git service. either open the existing git and pull, or clone the remote repo
*/
def void init(String localRepoFileRoot, String remoteRepoUrl) {
def void init(String localRepoFileRoot, String remoteRepoUrl, String privateKeyLocation, String knownHostsLocation) {
logger.info("Initializing with localRepoFileRoot='{}', remoteRepoUrl='{}'.", localRepoFileRoot, remoteRepoUrl)
this.privateKeyLocation = privateKeyLocation
this.knownHostsLocation = knownHostsLocation
projectFolder = verifyIsFolderOrNonExistent(localRepoFileRoot)
if (isExistingGitRepository(projectFolder)) {
openRepository(projectFolder, remoteRepoUrl)
Expand All @@ -43,6 +57,10 @@ class GitService {
cloneRepository(projectFolder, remoteRepoUrl)
}
}

def void init(String localRepoFileRoot, String remoteRepoUrl) {
init(localRepoFileRoot, remoteRepoUrl, null, null)
}

private def File verifyIsFolderOrNonExistent(String localRepoFileRoot) {
val file = new File(localRepoFileRoot)
Expand All @@ -59,8 +77,16 @@ class GitService {
return folder.exists && new File(folder, DOT_GIT).exists
}

/**
* configure transport commands with ssh credentials (if configured for this dropwizard app)
*/
def <T, C extends GitCommand<T>> GitCommand<T> configureTransport(TransportCommand<C, T> command) {
command.setSshSessionFactory
return command
}

def void pull() {
git.pull.call
git.pull.configureTransport.call
}

def ObjectId getHeadTree() {
Expand Down Expand Up @@ -89,7 +115,12 @@ class GitService {
}

private def void cloneRepository(File projectFolder, String remoteRepoUrl) {
git = Git.cloneRepository.setDirectory(projectFolder).setURI(remoteRepoUrl).call
val cloneCommand = Git.cloneRepository => [
setURI(remoteRepoUrl)
setSshSessionFactory
setDirectory(projectFolder)
]
git = cloneCommand.call
}

@VisibleForTesting
Expand All @@ -116,4 +147,40 @@ class GitService {
return config.getString(CONFIG_REMOTE_SECTION, DEFAULT_REMOTE_NAME, CONFIG_KEY_URL)
}

private def <T, C extends GitCommand<T>> void setSshSessionFactory(TransportCommand<C, ?> command) {

val sshSessionFactory = new JschConfigSessionFactory {

override protected void configure(Host host, Session session) {
logger.info('''HashKnownHosts = «session.getConfig('HashKnownHosts'''')
logger.info('''StrictHostKeyChecking = «session.getConfig('StrictHostKeyChecking'''')
}

// provide custom private key location (if not located at ~/.ssh/id_rsa)
// provide custom known hosts file location (if not located at ~/.ssh/known_hosts)
// see also http://www.codeaffine.com/2014/12/09/jgit-authentication/
override protected JSch createDefaultJSch(FS fs) throws JSchException {
val defaultJSch = super.createDefaultJSch(fs)
if (!privateKeyLocation.isNullOrEmpty) {
defaultJSch.addIdentity(privateKeyLocation)
}
if (!knownHostsLocation.isNullOrEmpty) {
defaultJSch.knownHosts = knownHostsLocation
defaultJSch.hostKeyRepository.hostKey.forEach [
logger.info('''host = «host», type = «type», key = «key», fingerprint = «getFingerPrint(defaultJSch)»''')
]
}
return defaultJSch
}

}

command.transportConfigCallback = [ transport |
if (transport instanceof SshTransport) {
transport.sshSessionFactory = sshSessionFactory
}
]

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,81 @@ package org.testeditor.web.xtext.index.persistence

import java.io.File
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.errors.TransportException
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.transport.URIish
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder

class GitServiceInitTest extends AbstractGitTest {

@Rule public TemporaryFolder keyfiles = new TemporaryFolder
@Rule public ExpectedException expectedException = ExpectedException.none

@Test
def void cloneTriesToUsePrivateKeyIfConfigured() {
// given
val invalidPrivateKey = write(keyfiles.root, 'invalid-private-key-file', 'invalid-private-key-content')
expectedException.expect(TransportException)
expectedException.expectMessage('invalid privatekey')

// when
gitService.init(localRepoRoot.path, '[email protected]:test-editor/test-editor-examples.git', invalidPrivateKey.absolutePath, null)

// then (expected exception is thrown)
}

@Test
def void cloneUsesPrivateKeyIfConfiguredButFailsOnHostThen() {
// given
val dummyButValidPrivateKey = write(keyfiles.root, 'dummy-private-key-file',
'''
-----BEGIN RSA PRIVATE KEY BLOCK-----
lQHYBFo7o+MBBADJg6nDraGCWwCCs4+J+VZP94htAXOgzY3LekOumSH55ywNPluM
gc5FPljiCS+UNEl1yYk+oFshClXhVSevtur/mXgUYck9V8n81fOlJBKPowJ/KiC5
KdHRJX7SdUEvK0UymNsIEIhAyqGCT/9OcpIZSJy0mJoaY50da4rxaod9KQARAQAB
AAP9FJmqGX/phTWfsrzVIqoOTHR7SjFzdu/XMVhEDQLzOeSLrfrpnvkyHgVb+WzY
+VHzDzXVcEUyVl6uctpNXqWDa66dgbP/Cwwtjs8JT1ws919/9HuXtnC1mCvyTwHv
zi4AA9I9dARvWc/urMzW1ywW+Xhf96qnX0sPvivw1YxwmQUCANluybZP0Wu+srTc
4GFJtyZZwfPZZUWmApeo9CQ+VqQcVxs4OmvlO3BKsh+hVjOJdpTdfV7tzbpcp5DD
TmeduqsCAO1CDFDi1LaW5lnKEHNlOMKv5d94BRJXW+nd9UjXVPm1U8tVZ1tfXZIo
x4VZWIPIPdmvs1X8Xa/FkLqbJ0LsZ3sCAM+4mfcCuuRwUiiqyGPTh1LQ2aWFtWSU
0Svw9y7RqSJhgCsNcJxpgQDUl7cQcQ0LgaGTMlvyzZ825xDHUi07dhek0bQjdGVz
dGVkaXRvciA8dGVzdGVkaXRvckBleGFtcGxlLmNvbT6IuAQTAQIAIgUCWjuj4wIb
AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQWwCvCdlYmPf8XAP7BXlqNYgn
D73QU6Rixk0txWF2gi4R+VuHPsxuM0LhzHNh1MKsrCAyIdAGUdQUzQNI1d3Z7UdG
3uFUKRzdoHigc4yRjq2imN5DZm+xONtkt1y2tDeu9e0XkaOlsIazS4HzbKeJvd+n
AChJdHV8UAxjjm43lm2AOm6Wm5e98eFD5o2dAdgEWjuj4wEEALU7tZQj3+36+Z4I
jkoQjgTTz5Q/hnY7i8kpr1iCQkd4mR5mYOsTtDzaSa4R/pBMArrPB4p3x4JiDIL5
QaA+LtaSsQTXDSd8NuESWSxgDGg2fr+J9U2HT9TmeZR2XMUoARr2QBr2uQuJJrwF
Qa9cDfl9F+yqvIlCdcpNVQoIryh7ABEBAAEAA/9PnsPPKVOfwbsYarnYYB2EkWmI
v7/bAZ4P6nhWciOcMqdSa7f4jteIRH5KMy2bR0mLuJifhK/p4BmPEOJ7+9WnQcGr
YqnuJ6lFn3fud/aANjGepsE3+Re4qJSfIWQtUuDdpQyjvyuIShkVck2G3YbMJX0O
lE2iNhVjkCtVcHgQQQIA0O5tPKdXxzku2wjOt/6zXJKaASyySzaEeK88J0+nq4ow
4R69rFseNd5aCSapSQz27I63unt4UsXr1zkGbYKPPwIA3g/doOT8byVE4Z97UagP
ExhrBp2DLAapjZwu/9ppCRs418uS0XNp005cnq8tzyp25YbSxAtgJJymJb3JdVrT
xQIAnBOxCGac6AH/Ypt6vtJeHp81OxN6ADPq4hOLU1e0jRHz/wAXjXTj+mQ611NE
BqqozKixwRyM8VtLM6xcDoszNZyxiJ8EGAECAAkFAlo7o+MCGwwACgkQWwCvCdlY
mPf7wAP/WUXIjMWRUj+fc9BwXNwuMkNMvCrgv0vlknB8nRqAClE+kchTIALU3Ejb
oeH/IcZ9lEnLC80eTnh8AuY+iAnCAN54udblx4x1xz7NwXZq6e8KVoHC7KtoM2ho
EOEXryHZ6kNpO+cMSyey6xPA6zZR2yNY0fgDHdh8mVzgghR6c/o=
=KbQq
-----END RSA PRIVATE KEY BLOCK-----
''')
expectedException.expect(TransportException)
expectedException.expectMessage('unknown host')

// when
gitService.init(localRepoRoot.path, '[email protected]:test-editor/test-editor-examples.git', dummyButValidPrivateKey.absolutePath, null)

// then (expected exception is thrown)
}



@Test
def void clonesRemoteRepositoryWhenUninitialized() {
Expand Down

0 comments on commit 0fe02b2

Please sign in to comment.