├── .editorconfig ├── .github ├── release.yml ├── renovate.json5 └── workflows │ ├── build.yaml │ └── release.yaml ├── .gitignore ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── build.gradle ├── gradle-ssh-plugin ├── acceptance-test │ ├── build.gradle │ ├── fixture │ │ ├── build.gradle │ │ ├── keys │ │ │ ├── etc │ │ │ │ └── ssh │ │ │ │ │ ├── ssh_host_dsa_key │ │ │ │ │ ├── ssh_host_dsa_key.pub │ │ │ │ │ ├── ssh_host_ecdsa_key │ │ │ │ │ ├── ssh_host_ecdsa_key.pub │ │ │ │ │ ├── ssh_host_ed25519_key │ │ │ │ │ ├── ssh_host_ed25519_key.pub │ │ │ │ │ ├── ssh_host_rsa_key │ │ │ │ │ └── ssh_host_rsa_key.pub │ │ │ ├── id_rsa │ │ │ └── id_rsa.pub │ │ ├── run-sshd.sh │ │ ├── settings.gradle │ │ └── spec │ │ │ ├── CommandSpec.gradle │ │ │ ├── ExtensionSpec.gradle │ │ │ ├── FileTransferSpec.gradle │ │ │ ├── RemoteSpec.gradle │ │ │ ├── ScriptSpec.gradle │ │ │ ├── ShellSpec.gradle │ │ │ └── build.gradle │ └── src │ │ └── test │ │ └── groovy │ │ └── org │ │ └── hidetake │ │ └── gradle │ │ └── ssh │ │ └── plugin │ │ └── AcceptanceSpec.groovy └── plugin │ ├── build.gradle │ └── src │ ├── main │ └── groovy │ │ └── org │ │ └── hidetake │ │ └── gradle │ │ └── ssh │ │ └── plugin │ │ ├── RemoteContainerExtension.groovy │ │ ├── SshPlugin.groovy │ │ └── VersionExtension.groovy │ └── test │ └── groovy │ └── org │ └── hidetake │ └── gradle │ └── ssh │ └── plugin │ ├── DryRunSpec.groovy │ ├── ExtensionSpec.groovy │ └── SshPluginSpec.groovy ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── groovy-ssh ├── Dockerfile ├── README.md ├── cli │ ├── build.gradle │ ├── gssh-example.groovy │ └── src │ │ ├── main │ │ └── groovy │ │ │ └── org │ │ │ └── hidetake │ │ │ └── groovy │ │ │ └── ssh │ │ │ ├── Main.groovy │ │ │ └── Runtime.groovy │ │ └── test │ │ ├── groovy │ │ └── org │ │ │ └── hidetake │ │ │ └── groovy │ │ │ └── ssh │ │ │ ├── MainDryRunSpec.groovy │ │ │ └── MainSpec.groovy │ │ └── resources │ │ ├── hostkey_dsa │ │ └── hostkey_dsa.pub ├── core │ ├── bin │ │ └── .gitignore │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── groovy │ │ │ └── org │ │ │ │ └── hidetake │ │ │ │ └── groovy │ │ │ │ └── ssh │ │ │ │ ├── Release.groovy │ │ │ │ ├── Ssh.groovy │ │ │ │ ├── connection │ │ │ │ ├── AddHostKey.groovy │ │ │ │ ├── AllowAnyHosts.groovy │ │ │ │ ├── Connection.groovy │ │ │ │ ├── ConnectionManager.groovy │ │ │ │ ├── ConnectionSettings.groovy │ │ │ │ ├── HostAuthentication.groovy │ │ │ │ ├── HostAuthenticationSettings.groovy │ │ │ │ ├── HostKeyRepository.groovy │ │ │ │ ├── JSchLogger.groovy │ │ │ │ ├── ProxyConnection.groovy │ │ │ │ ├── ProxyConnectionSettings.groovy │ │ │ │ ├── ProxyValidator.groovy │ │ │ │ ├── UserAuthentication.groovy │ │ │ │ └── UserAuthenticationSettings.groovy │ │ │ │ ├── core │ │ │ │ ├── ParallelSessionsException.groovy │ │ │ │ ├── Proxy.groovy │ │ │ │ ├── ProxyType.java │ │ │ │ ├── Remote.groovy │ │ │ │ ├── RunHandler.groovy │ │ │ │ ├── Service.groovy │ │ │ │ ├── container │ │ │ │ │ ├── Container.groovy │ │ │ │ │ ├── ContainerBuilder.groovy │ │ │ │ │ ├── ProxyContainer.groovy │ │ │ │ │ ├── RemoteContainer.groovy │ │ │ │ │ └── RoleAccessible.groovy │ │ │ │ ├── settings │ │ │ │ │ ├── CompositeSettings.groovy │ │ │ │ │ ├── GlobalSettings.groovy │ │ │ │ │ ├── LoggingMethod.java │ │ │ │ │ ├── PerServiceSettings.groovy │ │ │ │ │ ├── SettingsHelper.groovy │ │ │ │ │ └── ToStringProperties.groovy │ │ │ │ └── type │ │ │ │ │ └── InputStreamValue.groovy │ │ │ │ ├── interaction │ │ │ │ ├── Buffer.groovy │ │ │ │ ├── BufferRule.groovy │ │ │ │ ├── Context.groovy │ │ │ │ ├── InteractionException.groovy │ │ │ │ ├── InteractionHandler.groovy │ │ │ │ ├── Interactions.groovy │ │ │ │ ├── Listener.groovy │ │ │ │ ├── MatchResult.groovy │ │ │ │ ├── Processor.groovy │ │ │ │ ├── Receiver.groovy │ │ │ │ ├── Rule.groovy │ │ │ │ ├── Stream.java │ │ │ │ ├── StreamRule.groovy │ │ │ │ └── Wildcard.groovy │ │ │ │ ├── operation │ │ │ │ ├── Command.groovy │ │ │ │ ├── CommandSettings.groovy │ │ │ │ ├── DefaultOperations.groovy │ │ │ │ ├── DryRunOperation.groovy │ │ │ │ ├── DryRunOperations.groovy │ │ │ │ ├── Operation.groovy │ │ │ │ ├── Operations.groovy │ │ │ │ ├── SftpError.java │ │ │ │ ├── SftpException.groovy │ │ │ │ ├── SftpOperations.groovy │ │ │ │ ├── SftpProgress.groovy │ │ │ │ ├── Shell.groovy │ │ │ │ └── ShellSettings.groovy │ │ │ │ ├── session │ │ │ │ ├── BadExitStatusException.groovy │ │ │ │ ├── Session.groovy │ │ │ │ ├── SessionExtension.groovy │ │ │ │ ├── SessionExtensions.groovy │ │ │ │ ├── SessionHandler.groovy │ │ │ │ ├── SessionSettings.groovy │ │ │ │ ├── SessionTask.groovy │ │ │ │ ├── execution │ │ │ │ │ ├── BackgroundCommand.groovy │ │ │ │ │ ├── Command.groovy │ │ │ │ │ ├── Escape.groovy │ │ │ │ │ ├── Script.groovy │ │ │ │ │ ├── Shell.groovy │ │ │ │ │ ├── Sudo.groovy │ │ │ │ │ ├── SudoException.groovy │ │ │ │ │ ├── SudoHelper.groovy │ │ │ │ │ └── SudoSettings.groovy │ │ │ │ ├── forwarding │ │ │ │ │ ├── LocalPortForwardSettings.groovy │ │ │ │ │ ├── PortForward.groovy │ │ │ │ │ └── RemotePortForwardSettings.groovy │ │ │ │ └── transfer │ │ │ │ │ ├── FileGet.groovy │ │ │ │ │ ├── FilePut.groovy │ │ │ │ │ ├── FileTransferMethod.java │ │ │ │ │ ├── FileTransferSettings.groovy │ │ │ │ │ ├── SftpRemove.groovy │ │ │ │ │ ├── get │ │ │ │ │ ├── FileReceiver.groovy │ │ │ │ │ ├── Provider.groovy │ │ │ │ │ ├── RecursiveReceiver.groovy │ │ │ │ │ ├── Scp.groovy │ │ │ │ │ ├── ScpException.groovy │ │ │ │ │ ├── Sftp.groovy │ │ │ │ │ ├── StreamReceiver.groovy │ │ │ │ │ └── WritableReceiver.groovy │ │ │ │ │ └── put │ │ │ │ │ ├── EnterDirectory.groovy │ │ │ │ │ ├── Instructions.groovy │ │ │ │ │ ├── LeaveDirectory.groovy │ │ │ │ │ ├── Provider.groovy │ │ │ │ │ ├── Scp.groovy │ │ │ │ │ ├── Sftp.groovy │ │ │ │ │ └── StreamContent.groovy │ │ │ │ └── util │ │ │ │ ├── FileTransferProgress.groovy │ │ │ │ ├── ManagedBlocking.groovy │ │ │ │ └── Utility.groovy │ │ └── resources │ │ │ └── org │ │ │ └── hidetake │ │ │ └── groovy │ │ │ └── ssh │ │ │ └── Release.properties │ │ └── test │ │ └── groovy │ │ └── org │ │ └── hidetake │ │ └── groovy │ │ └── ssh │ │ ├── SshClassSpec.groovy │ │ ├── connection │ │ ├── ConnectionSettingsSpec.groovy │ │ ├── HostKeyRepositorySpec.groovy │ │ └── ProxyValidatorSpec.groovy │ │ ├── core │ │ ├── ProxySpec.groovy │ │ ├── RemoteSpec.groovy │ │ ├── RunHandlerSpec.groovy │ │ ├── ServiceSpec.groovy │ │ ├── container │ │ │ ├── ContainerBuilderSpec.groovy │ │ │ └── RemoteContainerSpec.groovy │ │ └── settings │ │ │ ├── SettingsHelperSpec.groovy │ │ │ └── ToStringPropertiesSpec.groovy │ │ ├── interaction │ │ ├── InteractionHandlerSpec.groovy │ │ └── RuleSpec.groovy │ │ ├── session │ │ ├── SessionExtensionSpec.groovy │ │ ├── SessionHandlerSpec.groovy │ │ ├── SessionSettingsSpec.groovy │ │ └── execution │ │ │ └── EscapeSpec.groovy │ │ └── util │ │ ├── FileTransferProgressSpec.groovy │ │ └── RetrySpec.groovy ├── docs │ ├── build.gradle │ └── src │ │ └── docs │ │ └── asciidoc │ │ ├── example-script.adoc │ │ ├── getting-started.adoc │ │ ├── index.adoc │ │ ├── introduction.adoc │ │ ├── migration-guide.adoc │ │ ├── user-guide.adoc │ │ └── version-loader.adoc ├── os-integration-test │ ├── bin │ │ └── .gitignore │ ├── build.gradle │ ├── etc │ │ └── ssh │ │ │ ├── id_ecdsa │ │ │ ├── id_ecdsa.pub │ │ │ ├── id_rsa │ │ │ ├── id_rsa.pub │ │ │ ├── id_rsa_pass │ │ │ ├── id_rsa_pass.pub │ │ │ ├── known_hosts │ │ │ ├── ssh_host_dsa_key │ │ │ ├── ssh_host_dsa_key.pub │ │ │ ├── ssh_host_ecdsa_key │ │ │ ├── ssh_host_ecdsa_key.pub │ │ │ ├── ssh_host_ed25519_key │ │ │ ├── ssh_host_ed25519_key.pub │ │ │ ├── ssh_host_rsa_key │ │ │ └── ssh_host_rsa_key.pub │ ├── run-sshd.sh │ └── src │ │ ├── main │ │ └── groovy │ │ │ └── org │ │ │ └── hidetake │ │ │ └── groovy │ │ │ └── ssh │ │ │ └── test │ │ │ └── os │ │ │ ├── FileDivCategory.groovy │ │ │ ├── Fixture.groovy │ │ │ ├── MkdirType.groovy │ │ │ ├── RemoteFixture.groovy │ │ │ ├── SshAgent.groovy │ │ │ └── UserManagementExtension.groovy │ │ └── test │ │ ├── groovy │ │ └── org │ │ │ └── hidetake │ │ │ └── groovy │ │ │ └── ssh │ │ │ └── test │ │ │ └── os │ │ │ ├── AbstractFileTransferSpec.groovy │ │ │ ├── CommandSpec.groovy │ │ │ ├── GatewaySpec.groovy │ │ │ ├── HostAuthenticationSpec.groovy │ │ │ ├── ScpSpec.groovy │ │ │ ├── ScriptSpec.groovy │ │ │ ├── SftpSpec.groovy │ │ │ ├── ShellSpec.groovy │ │ │ ├── SudoSpec.groovy │ │ │ └── UserAuthenticationSpec.groovy │ │ └── resources │ │ └── logback.xml ├── plugin-integration │ ├── create-branch-for-release.sh │ └── run-plugin-integration-test.sh └── server-integration-test │ ├── bin │ └── .gitignore │ ├── build.gradle │ └── src │ ├── main │ └── groovy │ │ └── org │ │ └── hidetake │ │ └── groovy │ │ └── ssh │ │ └── test │ │ └── server │ │ ├── CommandHelper.groovy │ │ ├── FileDivCategory.groovy │ │ ├── FilenameUtils.groovy │ │ ├── HostKeyFixture.groovy │ │ ├── SshServerMock.groovy │ │ ├── SudoHelper.groovy │ │ └── UserKeyFixture.groovy │ └── test │ ├── groovy │ └── org │ │ └── hidetake │ │ └── groovy │ │ └── ssh │ │ └── test │ │ └── server │ │ ├── AbstractFileTransferSpec.groovy │ │ ├── CommandSpec.groovy │ │ ├── DryRunSpec.groovy │ │ ├── ExtensionSpec.groovy │ │ ├── GatewaySpec.groovy │ │ ├── HostAuthenticationSpec.groovy │ │ ├── ParallelSessionsSpec.groovy │ │ ├── PortForwardingSpec.groovy │ │ ├── RetrySpec.groovy │ │ ├── ScpSpec.groovy │ │ ├── ScriptSpec.groovy │ │ ├── SftpRemoveSpec.groovy │ │ ├── SftpSpec.groovy │ │ ├── ShellSpec.groovy │ │ ├── SudoSpec.groovy │ │ ├── TimeoutSpec.groovy │ │ └── UserAuthenticationSpec.groovy │ └── resources │ ├── hostkey_ecdsa-sha2-nistp256 │ ├── hostkey_ecdsa-sha2-nistp256.pub │ ├── hostkey_ecdsa-sha2-nistp256_another.pub │ ├── hostkey_ssh-dss │ ├── hostkey_ssh-dss.pub │ ├── hostkey_ssh-rsa │ ├── hostkey_ssh-rsa.pub │ ├── id_ecdsa │ ├── id_ecdsa.pub │ ├── id_ecdsa_pass │ ├── id_ecdsa_pass.pub │ └── logback.xml └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_size = 2 8 | 9 | [*.{groovy,java,gradle}] 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 2 | changelog: 3 | categories: 4 | - title: Features 5 | labels: 6 | - '*' 7 | exclude: 8 | labels: 9 | - renovate 10 | - refactoring 11 | - title: Refactoring 12 | labels: 13 | - refactoring 14 | - title: Dependencies 15 | labels: 16 | - renovate 17 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>int128/renovate-base", 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | gradle: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 30 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-java@v4 16 | with: 17 | distribution: temurin 18 | java-version: 11 19 | - uses: gradle/actions/wrapper-validation@v4 20 | - uses: gradle/actions/setup-gradle@v4 21 | - run: ./gradlew build --warning-mode all 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - .github/workflows/release.yaml 7 | - gradle/** 8 | - '**/*.gradle' 9 | release: 10 | types: 11 | - created 12 | 13 | jobs: 14 | plugin: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 10 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-java@v4 20 | with: 21 | distribution: temurin 22 | java-version: 11 23 | - uses: gradle/actions/wrapper-validation@v4 24 | - uses: gradle/actions/setup-gradle@v4 25 | - run: ./gradlew validatePlugins 26 | 27 | - run: echo "$GRADLE_PROPERTIES_PUBLISH" >> ~/.gradle/gradle.properties 28 | env: 29 | GRADLE_PROPERTIES_PUBLISH: ${{ secrets.GRADLE_PROPERTIES_PUBLISH }} 30 | - if: github.event_name == 'release' 31 | run: ./gradlew publishPlugins 32 | env: 33 | VERSION: ${{ github.event.release.tag_name }} 34 | 35 | maven: 36 | runs-on: ubuntu-latest 37 | timeout-minutes: 10 38 | permissions: 39 | contents: read 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: actions/setup-java@v4 43 | with: 44 | distribution: temurin 45 | java-version: 11 46 | - uses: gradle/actions/wrapper-validation@v4 47 | - uses: gradle/actions/setup-gradle@v4 48 | - run: ./gradlew sign 49 | env: 50 | VERSION: ${{ github.event.release.tag_name }} 51 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 52 | 53 | - if: github.event_name == 'release' 54 | run: ./gradlew publishToMavenCentral 55 | env: 56 | VERSION: ${{ github.event.release.tag_name }} 57 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 58 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 59 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 60 | 61 | cli: 62 | runs-on: ubuntu-latest 63 | timeout-minutes: 10 64 | permissions: 65 | contents: write 66 | steps: 67 | - uses: actions/checkout@v4 68 | - uses: actions/setup-java@v4 69 | with: 70 | distribution: temurin 71 | java-version: 11 72 | - uses: gradle/actions/wrapper-validation@v4 73 | - uses: gradle/actions/setup-gradle@v4 74 | - run: ./gradlew shadowJar 75 | - run: java -jar groovy-ssh/cli/build/libs/gssh.jar 76 | - run: sha256sum -b groovy-ssh/cli/build/libs/gssh.jar > groovy-ssh/cli/build/libs/gssh.jar.sha256 77 | - if: github.event_name == 'release' 78 | run: gh release upload "$VERSION" groovy-ssh/cli/build/libs/gssh.jar groovy-ssh/cli/build/libs/gssh.jar.sha256 79 | env: 80 | VERSION: ${{ github.event.release.tag_name }} 81 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | 4 | **/.settings 5 | **/bin 6 | .classpath 7 | .project 8 | .vscode/ 9 | 10 | /.idea 11 | /out 12 | /*.iml 13 | /*.ipr 14 | /*.iws 15 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Environment info 4 | Please paste the output of `println ssh.version` e.g. `gradle-ssh-plugin-x.x.x (groovy-ssh-x.x.x, jsch-0.1.x, groovy-2.4.4, java-1.7.0_101)`. 5 | 6 | If it is difficult, describe version of the plugin, JVM and OS. 7 | 8 | 9 | ### Steps to reproduce 10 | 1. 11 | 2. 12 | 3. 13 | 14 | ```groovy 15 | // Paste the snippet of script or logs 16 | ``` 17 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | This adds the feature | fixes the bug... 2 | 3 | 4 | ### Steps to use the feature | verify the fix 5 | 1. 6 | 2. 7 | 3. 8 | 9 | ```groovy 10 | // Paste the snippet 11 | ``` 12 | 13 | 14 | ### Backward compatibility 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle SSH Plugin [![build](https://github.com/int128/gradle-ssh-plugin/actions/workflows/build.yaml/badge.svg)](https://github.com/int128/gradle-ssh-plugin/actions/workflows/build.yaml) 2 | 3 | Gradle SSH Plugin provides SSH facilities such as command execution or file transfer on Gradle. 4 | 5 | https://gradle-ssh-plugin.github.io 6 | 7 | ## Contributions 8 | 9 | This is an open source software licensed under the Apache License Version 2.0. 10 | Feel free to open issues or pull requests. 11 | 12 | ### Development 13 | 14 | Gradle SSH Plugin internally uses [Groovy SSH](https://github.com/int128/groovy-ssh) library. 15 | It depends on [JSch](http://www.jcraft.com/jsch/). 16 | 17 | The document is maintained on the repository of Groovy SSH. 18 | 19 | #### Acceptance Test 20 | 21 | TODO: fix it 22 | 23 | ### Release 24 | 25 | Create a new release in GitHub Releases. 26 | GitHub Actions will publish an artifact to Gradle Plugin Portal. 27 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | afterEvaluate { 3 | tasks.withType(Test) { 4 | finalizedBy ':testReport' 5 | testReport.reportOn binaryResultsDirectory 6 | reports.html.required = false 7 | reports.junitXml.destination = file("${rootProject.buildDir}/test-results") 8 | } 9 | } 10 | } 11 | 12 | task testReport(type: TestReport) { 13 | description 'Generates test report for all projects' 14 | destinationDirectory = file("$buildDir/reports") 15 | } -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | id 'java-gradle-plugin' 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation 'org.spockframework:spock-core:2.3-groovy-3.0' 12 | } 13 | 14 | evaluationDependsOn(':gradle-ssh-plugin:plugin') 15 | 16 | gradlePlugin { 17 | pluginSourceSet project(':gradle-ssh-plugin:plugin').sourceSets.main 18 | } 19 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'base' 3 | id 'org.hidetake.ssh' 4 | } 5 | 6 | import static java.lang.System.getProperty 7 | 8 | remotes { 9 | testServer { 10 | role 'testServers' 11 | host = 'localhost' 12 | port = 22 13 | user = 'tester' 14 | identity = file("$projectDir/keys/id_rsa") 15 | knownHosts = addHostKey(file("$buildDir/known_hosts")) 16 | } 17 | } 18 | 19 | ext.localWorkDirBase = file("$buildDir/tmp") 20 | ext.remoteWorkDirBase = '/tmp/gradle-ssh-plugin.acceptance-test' 21 | 22 | task setup { 23 | doLast { 24 | println "Gradle: ${gradle.gradleVersion}" 25 | println "Java: ${getProperty('java.home')}" 26 | println "Target: ${ssh.version}" 27 | delete localWorkDirBase 28 | localWorkDirBase.mkdirs() 29 | ssh.run { 30 | session(remotes.testServer) { 31 | remove remoteWorkDirBase 32 | execute "mkdir -vp $remoteWorkDirBase" 33 | } 34 | } 35 | } 36 | } 37 | 38 | task cleanup { 39 | doLast { 40 | delete localWorkDirBase 41 | ssh.run { 42 | session(remotes.testServer) { 43 | remove remoteWorkDirBase 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_dsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABswAAAAdzc2gtZH 3 | NzAAAAgQDB5zSCWJEMYMspTanQt0tRR4JhDaDASR+RL84DfQX7Q1XuPG2YpHslB2G0D530 4 | 4xPG7KdbpaTH3zZS8e6WfGKahtSX0+gUZWwpECin/UZdjDiXNz6XJ1ZEoCRLkid+yArXi6 5 | 3BcDcNoPPNwyyMpvR9kNmGprh9G7qUtWDMYNul0wAAABUAjde0V4ohDO7KYPO4drK3AuIm 6 | vfEAAACBAMBFlUuR/RekINTvSbl1+5/iHYv4BULhCSYtTNuOMfidizLIYTDd0uzDxGZwf3 7 | SumhU6j74ew221mBK9C6y0m3v6lcOfa6M+QGrmaI2ciXDSmpxZYdT+RDpMcOZsURAY087F 8 | hxXbLSjCnBzfBfx8vJLOCaQs5qy6HAVm34z8YNeqAAAAgQCWuDOZWIaKghws6Pa6yoyZA9 9 | MMyhgV7HM6KQnAP0XQ+33dRsUVsiCgtgBL/uImkFtOJhrsHEA0pHOG4Cor9Hzyo4Ur57ln 10 | 7kR2IUxhPKm4OyW3meH4gcAVBN1jYnmwZqRgF+7hi6tZNhEzr7VG6N92LpAQ+6Rao4QEAo 11 | m8DnT5yAAAAfDnggvs54IL7AAAAAdzc2gtZHNzAAAAgQDB5zSCWJEMYMspTanQt0tRR4Jh 12 | DaDASR+RL84DfQX7Q1XuPG2YpHslB2G0D5304xPG7KdbpaTH3zZS8e6WfGKahtSX0+gUZW 13 | wpECin/UZdjDiXNz6XJ1ZEoCRLkid+yArXi63BcDcNoPPNwyyMpvR9kNmGprh9G7qUtWDM 14 | YNul0wAAABUAjde0V4ohDO7KYPO4drK3AuImvfEAAACBAMBFlUuR/RekINTvSbl1+5/iHY 15 | v4BULhCSYtTNuOMfidizLIYTDd0uzDxGZwf3SumhU6j74ew221mBK9C6y0m3v6lcOfa6M+ 16 | QGrmaI2ciXDSmpxZYdT+RDpMcOZsURAY087FhxXbLSjCnBzfBfx8vJLOCaQs5qy6HAVm34 17 | z8YNeqAAAAgQCWuDOZWIaKghws6Pa6yoyZA9MMyhgV7HM6KQnAP0XQ+33dRsUVsiCgtgBL 18 | /uImkFtOJhrsHEA0pHOG4Cor9Hzyo4Ur57ln7kR2IUxhPKm4OyW3meH4gcAVBN1jYnmwZq 19 | RgF+7hi6tZNhEzr7VG6N92LpAQ+6Rao4QEAom8DnT5yAAAABQcGgS2uRxbrfvcxPwmL/Mq 20 | JOcO3QAAABVoaWRldGFrZUByYWJiaXQubG9jYWwBAgME 21 | -----END OPENSSH PRIVATE KEY----- 22 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_dsa_key.pub: -------------------------------------------------------------------------------- 1 | ssh-dss AAAAB3NzaC1kc3MAAACBAMHnNIJYkQxgyylNqdC3S1FHgmENoMBJH5EvzgN9BftDVe48bZikeyUHYbQPnfTjE8bsp1ulpMffNlLx7pZ8YpqG1JfT6BRlbCkQKKf9Rl2MOJc3PpcnVkSgJEuSJ37ICteLrcFwNw2g883DLIym9H2Q2YamuH0bupS1YMxg26XTAAAAFQCN17RXiiEM7spg87h2srcC4ia98QAAAIEAwEWVS5H9F6Qg1O9JuXX7n+Idi/gFQuEJJi1M244x+J2LMshhMN3S7MPEZnB/dK6aFTqPvh7DbbWYEr0LrLSbe/qVw59roz5AauZojZyJcNKanFlh1P5EOkxw5mxREBjTzsWHFdstKMKcHN8F/Hy8ks4JpCzmrLocBWbfjPxg16oAAACBAJa4M5lYhoqCHCzo9rrKjJkD0wzKGBXsczopCcA/RdD7fd1GxRWyIKC2AEv+4iaQW04mGuwcQDSkc4bgKiv0fPKjhSvnuWfuRHYhTGE8qbg7JbeZ4fiBwBUE3WNiebBmpGAX7uGLq1k2ETOvtUbo33YukBD7pFqjhAQCibwOdPnI 2 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_ecdsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 3 | 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTb2Qpge+BrGw6SKfi2lTzubUPS5FYg 4 | OGJBOV3IgNGJOP4q+lQZV4KQCq+XgtDsL3OxAOFya/sa3dNI90Sh5ZXoAAAAsOBC60DgQu 5 | tAAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp 6 | +LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlle 7 | gAAAAgQhtKq9f2GGDeivbuZ3tPKWtzabtWWkBBMCP79B1WIhcAAAAVaGlkZXRha2VAcmFi 8 | Yml0LmxvY2FsAQID 9 | -----END OPENSSH PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_ecdsa_key.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp+LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlleg= 2 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_ed25519_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACAg0uc6mInMIv6tF/4rYU5Tn/VBZ0Wp7HSzBqMQDLwPqgAAAJi6uxPeursT 4 | 3gAAAAtzc2gtZWQyNTUxOQAAACAg0uc6mInMIv6tF/4rYU5Tn/VBZ0Wp7HSzBqMQDLwPqg 5 | AAAEDtQZ8yklhUquVEgewNF+kCFYADJ5vOQqrAfqWZ1XpiryDS5zqYicwi/q0X/ithTlOf 6 | 9UFnRansdLMGoxAMvA+qAAAAFWhpZGV0YWtlQHJhYmJpdC5sb2NhbA== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_ed25519_key.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICDS5zqYicwi/q0X/ithTlOf9UFnRansdLMGoxAMvA+q 2 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_rsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAQEAvQA/tTTYSOslnkBwHcQ3dQ+09adHECgRQk0y9+3PZv4EQaN48MzF 4 | hlqjLM8RUfKnoIVp3yHdo0uEZaDYWD5nyePSnOihIV0sx5gOoJdGhzligcQnhhC2EKoBOy 5 | u23NnT7Qx+ObgLxZYD28rsYD5WREp5Xj8Lv7BdQzuH3z+4l0CTOrSYok2dP+InqMBBiR+k 6 | k1UXOqcD6YeipIuzlrhqOceN07zON/dm9RGOY040So3mCKcn0TJ2wYB+bHLkimC6kgaiCy 7 | TWIGI/7yiwTuYDJYhAqCTPaaABoz6k4LdDr9+7mjTADRQseFf7VcKFT7kiSfbrUNSkGxKQ 8 | +Mw/w7EfgQAAA9Cj8Rn7o/EZ+wAAAAdzc2gtcnNhAAABAQC9AD+1NNhI6yWeQHAdxDd1D7 9 | T1p0cQKBFCTTL37c9m/gRBo3jwzMWGWqMszxFR8qeghWnfId2jS4RloNhYPmfJ49Kc6KEh 10 | XSzHmA6gl0aHOWKBxCeGELYQqgE7K7bc2dPtDH45uAvFlgPbyuxgPlZESnlePwu/sF1DO4 11 | ffP7iXQJM6tJiiTZ0/4ieowEGJH6STVRc6pwPph6Kki7OWuGo5x43TvM4392b1EY5jTjRK 12 | jeYIpyfRMnbBgH5scuSKYLqSBqILJNYgYj/vKLBO5gMliECoJM9poAGjPqTgt0Ov37uaNM 13 | ANFCx4V/tVwoVPuSJJ9utQ1KQbEpD4zD/DsR+BAAAAAwEAAQAAAQEAhfWOMi6ReiWJFUCQ 14 | 9tgjgooudcspmC7+BKNZE9dvoI08kRV/3BUXj6HgdBsUKKQ34ZOONcP4JwyYe7vke69Hux 15 | YKKoLL6izzV0jUXUi7iY7H3jgc124yzV7h3oGea6zNBABN2zUyysoIVBnhLlogpOiwW3eO 16 | KUCk6clhBYBRoom/OpCnvx0K6RW2m9rmj9IdSt5XkIqR+e4idP4f4FMIUprX2fUbCtpyvz 17 | 1bC3B19dhLqi1hnQKHw8UAQIUXzwSlgSmbf/KO56Lm4kN4KsIOiG8jjRqe8y2Zz40j3jfP 18 | ERdjh3Gx8opRb2iSZO2N48dltm+iPHe/pSvNGdz1ZfQAAQAAAIEAt+/7p7AI1vKBHtB8ra 19 | iG8ShaFRD32i5rILVI+UEp7NKi48Xm36LLbxgJZwNGsF4DGcimMfdjyHy7J1aB6BleuKUX 20 | iHRQFus26iU6jW7pqSIG9UAEONxO2smv8AjpU9t6oWmWj91osiuykMWcKrnKYgR7tB+BsS 21 | hIVqJagkn1jdwAAACBAOkRQheAz7c8hDnz3nnTBF0fACL8/yH4igChrYA29L1EdKgH1FwW 22 | RZIix6ROxZunYlbbdLhx2XXJnu07ffROVNku6bBK+XIHyrQlrUEU4xmKzwbmh2sBv78T6u 23 | WqmEj/YfIx7aX0G+i5GWfPhKdwvMJI1ZATEv7OVGo2DO6HoFWBAAAAgQDPmP/yfdxCxtWK 24 | GY1MANtFh/b2xWgSPRasbTg3L3LEk2I3WiQPcARTLv9O02ZNrwBSPJ33BF5GEkUcK13WI3 25 | P3ns3YHA031EPwQrF1T0iHN024EkDl0KKUMjobSOeoC1RmXHuJ6Do3lHHNxlvrkIKw3YxV 26 | 92rSZ1v9fq2p3xnKAQAAABVoaWRldGFrZUByYWJiaXQubG9jYWwBAgME 27 | -----END OPENSSH PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/etc/ssh/ssh_host_rsa_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9AD+1NNhI6yWeQHAdxDd1D7T1p0cQKBFCTTL37c9m/gRBo3jwzMWGWqMszxFR8qeghWnfId2jS4RloNhYPmfJ49Kc6KEhXSzHmA6gl0aHOWKBxCeGELYQqgE7K7bc2dPtDH45uAvFlgPbyuxgPlZESnlePwu/sF1DO4ffP7iXQJM6tJiiTZ0/4ieowEGJH6STVRc6pwPph6Kki7OWuGo5x43TvM4392b1EY5jTjRKjeYIpyfRMnbBgH5scuSKYLqSBqILJNYgYj/vKLBO5gMliECoJM9poAGjPqTgt0Ov37uaNMANFCx4V/tVwoVPuSJJ9utQ1KQbEpD4zD/DsR+B 2 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA78p/+WebOE48aQkyU9HLCezHgEXIVs6aXKH0rgBmlnmNIEf7 3 | y8TWdQ7qQT1esexhmGSpCj/p9iSfeWTv0wG7T0H66toUtuB7ZlV3iEDYXSeYBYKZ 4 | b+cABDhl9bfFcuLR5XDBrTGKfibyLqfGIh1uronPUDGhOf90Ng6G5ZQEK9e8g4BF 5 | PXGrUe9BSqorHppTlGmHPSkFmt4GfHeqx7X2gCNR5yulO6x/Wc15no8r0rejYU+4 6 | kNsiTBdBzqXUJpw6sSa+Yznlq4H9E28FtADd0166ixaVHctgwlhdFxDDmi2YSDTF 7 | dcsUoWAFM4am5z9L6CqCr/CGkhv4bSSgFSF5/QIDAQABAoIBAFKaP1t7BU1wJf9I 8 | 271kF71jg5X8c/bzVNl0MQV/vdc4KBVmtqaLOBU6/hdbPLOt6jDE/DY7rizMkOMQ 9 | kkzt28iBwh4E4f3ddqTZ7ENTkzUD3qqHQrP5r1fE1dq/Y5Uf7Y5MOWugFUU/xU2t 10 | HePCn84gSvolHpUMGsxEVNPhGU7AZ8RBXLYi27ha3OWCqusDJJbQEYRO1xm+SV2t 11 | OzybhEEwYTwmNkPC/1pNyRu3zoG1h+tGR2t1yOK1n35ygRiL0lxDHmJ19sRxJ1vn 12 | jKAKU8hnjG1L/uQxVpleOt5TQhIq7wWLL7jO+EMYu5w+iptHGeRs+j/15jY3LFus 13 | V/3v+UECgYEA/MEKUzgl34YN8jMBS3/rzvp+C/4U0nUMyM5+SLa4slYxtL5QbLLu 14 | dPXUTlFSXxUa8OypzM0HqyyWzvXFiQ05Te63wS83rds9/ox6WHAfcwFnkJLIuqCR 15 | N2dNbD0fLV2ixtjNJLsPmVL3upY9/rSYLz9uvw/7/nW/O3AzpVaSbssCgYEA8t7X 16 | i21C4oeWUXJhYnVWWMNqNSoHXsaBMcA5U82aA24cCpoqqRcPpkVYBOlwksGOzJKa 17 | 0rUpaiUknoaqO17o4Q5kKfQYbfGPJ5BnED2iDeVufbyEgfLK0lQX9LNR7Cxtxos6 18 | wSkOvyNllauu6CHCoYSM+EZTiCekAv0GKlgkGVcCgYB9skLAQBwVnUUyPctXELbk 19 | qA4nSKRyRWOmOYrz/mq7xcHScRLt+846vEZo7GhagNR1HD0VbKFzrykQo4kpLzpg 20 | V2dq22CFRZL/FD2D3b7GItyuOVE5/sA5HVaTjZIDrZ1V5lue+Kg5R9mLIUyTbpyA 21 | YrtgqUJYuZXwqUwF3ZfVIQKBgCpxcR+nl4G5Cjbvkz8+nDlk5SGnV6Rjcl58ZkhT 22 | 7O9ehb4AlSX5pr167tfk58xt0QPFNxNNn5AyL4UYqZU4j+AMwMpoIwDLryXN4YUA 23 | EFr3VmjY0htXj8RT99/GmrF4TjLdUAZDo5UZnX4bg7SDedz6KhyVRbHMo6f2CebK 24 | gnx/AoGBAPMFUEa5Tgq7V4XgW6PLHA3SfXj7yNrE72V3aAVkVgkuGAbiOH08cOOF 25 | j9GBq+5NyawyVmVeOe9c/vZG8tPeEFl4550x7n69iCfl7Jaraf1JJF9aEDZprIqE 26 | w6LAtQVE3oH8HvbUEx6dNTMwo5e82tVei8PS1ez6qPfB9CiEns7S 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/keys/id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDvyn/5Z5s4TjxpCTJT0csJ7MeARchWzppcofSuAGaWeY0gR/vLxNZ1DupBPV6x7GGYZKkKP+n2JJ95ZO/TAbtPQfrq2hS24HtmVXeIQNhdJ5gFgplv5wAEOGX1t8Vy4tHlcMGtMYp+JvIup8YiHW6uic9QMaE5/3Q2DobllAQr17yDgEU9catR70FKqisemlOUaYc9KQWa3gZ8d6rHtfaAI1HnK6U7rH9ZzXmejyvSt6NhT7iQ2yJMF0HOpdQmnDqxJr5jOeWrgf0TbwW0AN3TXrqLFpUdy2DCWF0XEMOaLZhINMV1yxShYAUzhqbnP0voKoKv8IaSG/htJKAVIXn9 2 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/run-sshd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | keys="$(dirname "$0")/keys" 4 | 5 | exec docker run --rm -p 22:22 \ 6 | -e "SSH_HOST_DSA_KEY=$(cat $keys/etc/ssh/ssh_host_dsa_key)" \ 7 | -e "SSH_HOST_RSA_KEY=$(cat $keys/etc/ssh/ssh_host_rsa_key)" \ 8 | -e "SSH_HOST_ECDSA_KEY=$(cat $keys/etc/ssh/ssh_host_ecdsa_key)" \ 9 | -e "SSH_HOST_ED25519_KEY=$(cat $keys/etc/ssh/ssh_host_ed25519_key)" \ 10 | -e "SSH_AUTHORIZED_KEYS=$(cat $keys/id_rsa.pub)" \ 11 | --name sshd \ 12 | int128/sshd 13 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/settings.gradle: -------------------------------------------------------------------------------- 1 | include 'spec' 2 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/spec/ExtensionSpec.gradle: -------------------------------------------------------------------------------- 1 | task('should apply the remote file extension') { 2 | doLast { 3 | ssh.run { 4 | settings { 5 | extensions.add eachFile: { String directory, Closure closure -> 6 | sftp { 7 | ls(directory).each(closure) 8 | } 9 | } 10 | } 11 | session(remotes.testServer) { 12 | execute "mkdir -vp $remoteWorkDir" 13 | put text: 1, into: "$remoteWorkDir/a" 14 | put text: 2, into: "$remoteWorkDir/b" 15 | put text: 3, into: "$remoteWorkDir/c" 16 | 17 | eachFile(remoteWorkDir) { 18 | println it.filename 19 | } 20 | } 21 | } 22 | } 23 | } 24 | 25 | task('should apply the extension which accesses to the project') { 26 | doLast { 27 | assert ssh.run { 28 | settings { 29 | extensions << [echoProjectName: { -> 30 | execute "echo ${project.name}" 31 | }] 32 | } 33 | session(remotes.testServer) { 34 | echoProjectName() 35 | } 36 | } == 'spec' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/spec/FileTransferSpec.gradle: -------------------------------------------------------------------------------- 1 | ['sftp', 'scp'].each { method -> 2 | task("should put files of fileTree via $method") { 3 | doLast { 4 | localWorkDir.mkdirs() 5 | file("$localWorkDir/a.dat") << 1 6 | file("$localWorkDir/b.txt") << 2 7 | file("$localWorkDir/c.dat") << 3 8 | 9 | ssh.run { 10 | settings { 11 | fileTransfer = method 12 | } 13 | session(remotes.testServer) { 14 | execute "mkdir -vp $remoteWorkDir" 15 | put from: fileTree(dir: localWorkDir, include: '*.dat'), into: remoteWorkDir 16 | execute "test -f $remoteWorkDir/a.dat" 17 | execute "test ! -f $remoteWorkDir/b.txt" 18 | execute "test -f $remoteWorkDir/c.dat" 19 | } 20 | } 21 | } 22 | } 23 | 24 | task("should get the large file and put it back via $method") { 25 | doLast { 26 | def fileSize = 1024 * 8 27 | localWorkDir.mkdirs() 28 | 29 | ssh.run { 30 | settings { 31 | fileTransfer = method 32 | } 33 | session(remotes.testServer) { 34 | execute "mkdir -vp $remoteWorkDir" 35 | execute "dd if=/dev/zero of=$remoteWorkDir/result bs=1024 count=$fileSize" 36 | get from: "$remoteWorkDir/result", into: localWorkDir 37 | } 38 | } 39 | assert file("$localWorkDir/result").size() == 1024 * fileSize 40 | 41 | ssh.run { 42 | settings { 43 | fileTransfer = method 44 | } 45 | session(remotes.testServer) { 46 | put from: "$localWorkDir/result", into: "$remoteWorkDir/back" 47 | assert execute("wc -c < $remoteWorkDir/back") as int == 1024 * fileSize 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/spec/RemoteSpec.gradle: -------------------------------------------------------------------------------- 1 | remotes { 2 | yetAnotherServer { 3 | role 'testServers' 4 | host = remotes.testServer.host 5 | port = remotes.testServer.port 6 | user = remotes.testServer.user 7 | identity = remotes.testServer.identity 8 | knownHosts = remotes.testServer.knownHosts 9 | } 10 | } 11 | 12 | 13 | task('should execute the command on multiple remotes specified by brace list') { 14 | doLast { 15 | def x = randomInt() 16 | def y = randomInt() 17 | assert ssh.run { 18 | session([remotes.testServer, remotes.yetAnotherServer]) { 19 | execute "expr $x + $y" 20 | } 21 | }.every { it as int == (x + y) } 22 | } 23 | } 24 | 25 | task('should execute the command on multiple remotes specified by arguments') { 26 | doLast { 27 | def x = randomInt() 28 | def y = randomInt() 29 | assert ssh.run { 30 | session(remotes.testServer, remotes.yetAnotherServer) { 31 | execute "expr $x + $y" 32 | } 33 | }.every { it as int == (x + y) } 34 | } 35 | } 36 | 37 | task('should execute the command on multiple remotes specified by the role') { 38 | doLast { 39 | def x = randomInt() 40 | def y = randomInt() 41 | assert ssh.run { 42 | session(remotes.role('testServers')) { 43 | execute "expr $x + $y" 44 | } 45 | }.every { it as int == (x + y) } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/spec/ScriptSpec.gradle: -------------------------------------------------------------------------------- 1 | 2 | task('should execute the script') { 3 | def a 4 | def b 5 | doLast { 6 | def x = randomInt() 7 | def y = randomInt() 8 | ssh.run { 9 | session(remotes.testServer) { 10 | executeScript """\ 11 | #!/bin/sh -xe 12 | mkdir -vp $remoteWorkDir 13 | expr $x + $y > $remoteWorkDir/A 14 | expr $x + `cat $remoteWorkDir/A` > $remoteWorkDir/B 15 | """.stripIndent() 16 | a = get from: "$remoteWorkDir/A" 17 | b = get from: "$remoteWorkDir/B" 18 | } 19 | } 20 | assert a as int == (x + y) 21 | assert b as int == (x + x + y) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/spec/ShellSpec.gradle: -------------------------------------------------------------------------------- 1 | task('should execute the shell') { 2 | doLast { 3 | ssh.run { 4 | session(remotes.testServer) { 5 | shell(interaction: { 6 | when(line: _, from: standardOutput) { 7 | standardInput << 'uname -a' << '\n' 8 | standardInput << 'exit 0' << '\n' 9 | } 10 | }) 11 | } 12 | } 13 | } 14 | } 15 | 16 | task('should write output of the shell to the file') { 17 | doLast { 18 | def x = randomInt() 19 | def y = randomInt() 20 | localWorkDir.mkdirs() 21 | def resultFile = file("$localWorkDir/result") 22 | resultFile.withOutputStream { stream -> 23 | ssh.run { 24 | session(remotes.testServer) { 25 | execute "expr $x + $y", outputStream: stream 26 | } 27 | } 28 | } 29 | assert resultFile.text as int == (x + y) 30 | } 31 | } 32 | 33 | task('should write output of the shell to the standard output') { 34 | doLast { 35 | ssh.run { 36 | session(remotes.testServer) { 37 | shell outputStream: System.out, interaction: { 38 | when(line: _, from: standardOutput) { 39 | standardInput << 'uname -a' << '\n' 40 | standardInput << 'exit 0' << '\n' 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/fixture/spec/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.hidetake.ssh' 2 | 3 | fileTree(projectDir) { 4 | include '*.gradle' 5 | exclude 'build.gradle' 6 | }.each { 7 | apply from: it 8 | } 9 | 10 | task test { 11 | description = "Test all specs in $project" 12 | } 13 | 14 | test.dependsOn tasks.matching { it.name.startsWith 'should ' }.each { spec -> 15 | spec.dependsOn parent.setup 16 | spec.finalizedBy parent.cleanup 17 | 18 | // inject paths 19 | spec.ext.localWorkDir = file("${parent.localWorkDirBase}/${spec.hashCode()}") 20 | spec.ext.remoteWorkDir = "${parent.remoteWorkDirBase}/${spec.hashCode()}" 21 | } 22 | 23 | // inject utility method 24 | ext.randomInt = { int max = 10000 -> (Math.random() * max) as int } 25 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/acceptance-test/src/test/groovy/org/hidetake/gradle/ssh/plugin/AcceptanceSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.gradle.ssh.plugin 2 | 3 | import org.gradle.testkit.runner.GradleRunner 4 | import spock.lang.Specification 5 | import spock.lang.Timeout 6 | import spock.lang.Unroll 7 | 8 | class AcceptanceSpec extends Specification { 9 | 10 | static final List tasks = [] 11 | 12 | // find tasks which starts with `should` 13 | static { 14 | def writer = new StringWriter() 15 | GradleRunner.create() 16 | .withProjectDir(new File('fixture')) 17 | .withPluginClasspath() 18 | .withArguments('-s', ':spec:tasks', '--all') 19 | .forwardStdOutput(writer) 20 | .build() 21 | writer.toString().readLines().findAll { 22 | it =~ /^should / 23 | }.each { 24 | tasks << it 25 | } 26 | } 27 | 28 | @Timeout(10) 29 | @Unroll 30 | def "spec(#task)"() { 31 | given: 32 | def runner = GradleRunner.create() 33 | .withProjectDir(new File('fixture')) 34 | .withPluginClasspath() 35 | .withArguments('-s', ":spec:$task") 36 | .forwardOutput() 37 | 38 | when: 39 | runner.build() 40 | 41 | then: 42 | noExceptionThrown() 43 | 44 | where: 45 | task << tasks 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/plugin/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | id 'groovy-gradle-plugin' 4 | id 'maven-publish' 5 | id 'com.gradle.plugin-publish' version '1.3.1' 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | implementation gradleApi() 14 | implementation project(':groovy-ssh:core') 15 | testImplementation 'junit:junit:4.13.2' 16 | testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' 17 | testImplementation 'cglib:cglib-nodep:3.3.0' 18 | } 19 | 20 | test { 21 | useJUnitPlatform() 22 | } 23 | 24 | group = 'org.hidetake' 25 | version = System.getenv('VERSION') ?: 'SNAPSHOT' 26 | 27 | gradlePlugin { 28 | website = 'https://github.com/int128/gradle-ssh-plugin' 29 | vcsUrl = 'https://github.com/int128/gradle-ssh-plugin' 30 | 31 | plugins { 32 | ssh { 33 | id = 'org.hidetake.ssh' 34 | displayName = 'Gradle SSH Plugin' 35 | description = 'A plugin for remote command execution and file transfer via SSH' 36 | tags.set(['remote', 'ssh', 'deploy']) 37 | implementationClass = 'org.hidetake.gradle.ssh.plugin.SshPlugin' 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/plugin/src/main/groovy/org/hidetake/gradle/ssh/plugin/RemoteContainerExtension.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.gradle.ssh.plugin 2 | 3 | import org.hidetake.groovy.ssh.core.container.RoleAccessible 4 | 5 | /** 6 | * An extension class for {@link org.gradle.api.NamedDomainObjectContainer}. 7 | * 8 | * @author hidetake.org 9 | */ 10 | class RemoteContainerExtension implements RoleAccessible { 11 | } 12 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/plugin/src/main/groovy/org/hidetake/gradle/ssh/plugin/SshPlugin.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.gradle.ssh.plugin 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.gradle.api.NamedDomainObjectContainer 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.hidetake.groovy.ssh.Ssh 8 | import org.hidetake.groovy.ssh.core.Proxy 9 | import org.hidetake.groovy.ssh.core.Remote 10 | 11 | /** 12 | * Main class of Gradle SSH plugin. 13 | * 14 | * @author Hidetake Iwata 15 | */ 16 | @Slf4j 17 | class SshPlugin implements Plugin { 18 | @Override 19 | void apply(Project project) { 20 | project.extensions.ssh = Ssh.newService() 21 | project.extensions.remotes = createRemoteContainer(project) 22 | project.extensions.proxies = createProxyContainer(project) 23 | 24 | project.ssh.settings.logging = 'stdout' 25 | 26 | project.ssh.metaClass.mixin(VersionExtension) 27 | 28 | // TODO 29 | if (project.gradle.gradleVersion =~ /^1\./) { 30 | log.warn('Gradle 1.x support will be removed in the future release. ' + 31 | 'Please see https://github.com/int128/gradle-ssh-plugin/issues/230') 32 | } 33 | 34 | // TODO 35 | if (System.getProperty('java.version') =~ /^1\.6/) { 36 | log.warn('Java 6 support will be removed in the future release. ' + 37 | 'Please see https://github.com/int128/gradle-ssh-plugin/issues/266') 38 | } 39 | } 40 | 41 | private static createRemoteContainer(Project project) { 42 | def remotes = project.container(Remote) 43 | remotes.metaClass.mixin(RemoteContainerExtension) 44 | def parentRemotes = project.parent?.extensions?.findByName('remotes') 45 | if (parentRemotes instanceof NamedDomainObjectContainer) { 46 | remotes.addAll(parentRemotes) 47 | } 48 | remotes 49 | } 50 | 51 | private static createProxyContainer(Project project) { 52 | def proxies = project.container(Proxy) 53 | def parentProxies = project.parent?.extensions?.findByName('proxies') 54 | if (parentProxies instanceof NamedDomainObjectContainer) { 55 | proxies.addAll(parentProxies) 56 | } 57 | proxies 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/plugin/src/main/groovy/org/hidetake/gradle/ssh/plugin/VersionExtension.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.gradle.ssh.plugin 2 | 3 | import org.hidetake.groovy.ssh.Ssh 4 | 5 | /** 6 | * An extension class for {@code project.ssh} instance. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class VersionExtension { 11 | 12 | @Lazy 13 | String version = { 14 | def resourcePath = '/META-INF/gradle-plugins/org.hidetake.ssh.properties' 15 | VersionExtension.getResourceAsStream(resourcePath)?.withStream { stream -> 16 | def properties = new Properties() 17 | properties.load(stream) 18 | def name = properties.getProperty('product.name') 19 | def version = properties.getProperty('product.version') 20 | "$name-$version (" + 21 | "groovy-ssh-${Ssh.release.version}, " + 22 | "jsch-${Ssh.release.jschVersion}, " + 23 | "groovy-${Ssh.release.groovyVersion}, " + 24 | "java-${Ssh.release.javaVersion})" 25 | } 26 | }() 27 | 28 | } 29 | -------------------------------------------------------------------------------- /gradle-ssh-plugin/plugin/src/test/groovy/org/hidetake/gradle/ssh/plugin/ExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.gradle.ssh.plugin 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.testfixtures.ProjectBuilder 5 | import spock.lang.Specification 6 | 7 | class ExtensionSpec extends Specification { 8 | 9 | Project project 10 | 11 | def setup() { 12 | project = ProjectBuilder.builder().withName('extensionSpec').build() 13 | project.with { 14 | apply plugin: 'org.hidetake.ssh' 15 | ssh.settings { 16 | dryRun = true 17 | extensions << [example: { project.name }] 18 | } 19 | remotes { 20 | testServer { 21 | host = 'localhost' 22 | user = 'user' 23 | } 24 | } 25 | } 26 | } 27 | 28 | def "extension should be able to access to the project"() { 29 | when: 30 | project.with { 31 | project.ext.result = ssh.run { 32 | session(remotes.testServer) { 33 | example() 34 | } 35 | } 36 | } 37 | 38 | then: 39 | project.result == 'extensionSpec' 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int128/gradle-ssh-plugin/ddbc83811b1f70ad21c51f821b0a708c7ae06582/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /groovy-ssh/Dockerfile: -------------------------------------------------------------------------------- 1 | from java:8 2 | 3 | volume /usr/src/groovy-ssh 4 | copy . /usr/src/groovy-ssh 5 | run cd /usr/src/groovy-ssh && ./gradlew -g .gradle shadowJar && cp -a cli/build/libs/gssh.jar / 6 | 7 | entrypoint ["java", "-jar", "/gssh.jar"] 8 | -------------------------------------------------------------------------------- /groovy-ssh/README.md: -------------------------------------------------------------------------------- 1 | Groovy SSH [![build](https://github.com/int128/groovy-ssh/actions/workflows/build.yaml/badge.svg)](https://github.com/int128/groovy-ssh/actions/workflows/build.yaml) 2 | ========== 3 | 4 | Groovy SSH is an automation tool based on DSL providing the remote command execution and file transfer. 5 | 6 | https://gradle-ssh-plugin.github.io 7 | 8 | 9 | Contributions 10 | ------------- 11 | 12 | This is an open source software licensed under the Apache License Version 2.0. 13 | Feel free to open issues or pull requests. 14 | 15 | 16 | ### Unit test 17 | 18 | We can run the unit test as follows: 19 | 20 | ```sh 21 | ./gradlew :core:check 22 | ``` 23 | 24 | 25 | ### Server integration test 26 | 27 | We can run the server integration test using Apache MINA SSHD server as follows: 28 | 29 | ```sh 30 | ./gradlew :server-integration-test:check 31 | ``` 32 | 33 | 34 | ### CLI test 35 | 36 | We can run the integration test of CLI as follows: 37 | 38 | ```sh 39 | ./gradlew :cli:check 40 | ``` 41 | 42 | 43 | ### OS integration test 44 | 45 | We can run the OS integration tests using [int128/sshd](https://github.com/int128/docker-sshd) image as follows: 46 | 47 | ```sh 48 | # Run a sshd container 49 | ./os-integration-test/run-sshd.sh 50 | 51 | # Run the tests 52 | ./gradlew :os-integration-test:check 53 | ``` 54 | 55 | 56 | ### Gradle SSH Plugin integration test 57 | 58 | We can run the test with Gradle SSH Plugin. 59 | See `plugin-integration/run-plugin-integration-test.sh` for details. 60 | 61 | If you are planning to release with specification change breaking backward compatibility, 62 | create `groovy-ssh-acceptance-test` branch on Gradle SSH Plugin to pass the acceptance test. 63 | -------------------------------------------------------------------------------- /groovy-ssh/cli/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | id 'com.github.johnrengelman.shadow' version '7.1.2' 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation project(':groovy-ssh:core') 12 | implementation 'ch.qos.logback:logback-classic:1.5.18' 13 | implementation 'org.codehaus.groovy:groovy-cli-commons:3.0.25' 14 | 15 | testImplementation project(':groovy-ssh:server-integration-test') 16 | testImplementation 'org.apache.sshd:sshd-core:2.2.0' 17 | testImplementation platform("org.spockframework:spock-bom:2.3-groovy-3.0") 18 | testImplementation "org.spockframework:spock-core" 19 | testImplementation "org.spockframework:spock-junit4" 20 | testRuntimeOnly 'ch.qos.logback:logback-classic:1.5.18' 21 | } 22 | 23 | test { 24 | mustRunAfter ':groovy-ssh:server-integration-test:check' 25 | useJUnitPlatform() 26 | } 27 | 28 | jar { 29 | manifest { 30 | attributes 'Main-Class': 'org.hidetake.groovy.ssh.Main' 31 | } 32 | } 33 | 34 | shadowJar { 35 | archiveBaseName = 'gssh' 36 | archiveVersion = '' 37 | archiveClassifier = '' 38 | } 39 | -------------------------------------------------------------------------------- /groovy-ssh/cli/gssh-example.groovy: -------------------------------------------------------------------------------- 1 | ssh.remotes { 2 | tester { 3 | host = 'localhost' 4 | port = 22 5 | user = 'tester' 6 | identity = new File('os-integration-test/etc/ssh/id_rsa') 7 | knownHosts = addHostKey(new File('cli/build/known_hosts')) 8 | } 9 | } 10 | 11 | ssh.run { 12 | session(ssh.remotes.tester) { 13 | execute 'uname -a' 14 | } 15 | } 16 | 17 | assert new File('cli/build/known_hosts').readLines().any { line -> 18 | line.startsWith('localhost ssh-rsa') 19 | } 20 | -------------------------------------------------------------------------------- /groovy-ssh/cli/src/main/groovy/org/hidetake/groovy/ssh/Main.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh 2 | 3 | import ch.qos.logback.classic.Level 4 | import groovy.cli.commons.CliBuilder 5 | import groovy.util.logging.Slf4j 6 | import org.hidetake.groovy.ssh.core.Service 7 | 8 | /** 9 | * CLI main class. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | @Slf4j 14 | class Main { 15 | static void main(String[] args) { 16 | def cli = new CliBuilder( 17 | usage: '[option...] [-e script-text] [script-filename | --stdin] [script-args...]', 18 | width: 120 19 | ) 20 | cli.h longOpt: 'help', 'Shows this help message.' 21 | cli.q longOpt: 'quiet', 'Set log level to warn.' 22 | cli.i longOpt: 'info', 'Set log level to info. (default)' 23 | cli.d longOpt: 'debug', 'Set log level to debug.' 24 | cli._ longOpt: 'stdin', 'Specify standard input as a source.' 25 | cli.e args: 1, 'Specify a command line script.' 26 | cli.n longOpt: 'dry-run', 'Do a dry run without connections.' 27 | cli._ longOpt: 'version', 'Shows version.' 28 | cli.s longOpt: 'stacktrace', 'Print out the stacktrace for the exception.' 29 | 30 | def options = cli.parse(args) 31 | if (!options || options.h) { 32 | cli.usage() 33 | } else if (options.version) { 34 | println Ssh.release 35 | } else { 36 | Runtime.instance.logback(level: logLevel(options)) 37 | 38 | if (!options.s) { 39 | Thread.currentThread().uncaughtExceptionHandler = { Thread t, Throwable e -> 40 | log.error("Error: $e") 41 | log.info('Run with -s or --stacktrace option to get the stack trace') 42 | } 43 | } 44 | 45 | def shell = newShellWith(options) 46 | def extra = options.arguments() 47 | 48 | if (options.e) { shell.run(options.e as String, 'script.groovy', extra) } 49 | else if (options.stdin) { shell.run(System.in.newReader(), 'script.groovy', extra) } 50 | else if (!extra.empty) { shell.run(new File(extra.head()), extra.tail()) } 51 | else { cli.usage() } 52 | } 53 | } 54 | 55 | private static logLevel(options) { 56 | if (options.d) { Level.DEBUG } 57 | else if (options.i) { Level.INFO } 58 | else if (options.q) { Level.WARN } 59 | else { Level.INFO } 60 | } 61 | 62 | private static newShellWith(options) { 63 | def shell = Ssh.newShell() 64 | def service = shell.getVariable('ssh') as Service 65 | service.metaClass.runtime = Runtime.instance 66 | service.metaClass.version = Ssh.release.toString() 67 | if (options.n) { 68 | service.settings { 69 | dryRun = true 70 | } 71 | } 72 | shell 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /groovy-ssh/cli/src/main/groovy/org/hidetake/groovy/ssh/Runtime.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh 2 | 3 | import ch.qos.logback.classic.Level 4 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder 5 | import ch.qos.logback.core.ConsoleAppender 6 | import org.slf4j.Logger 7 | import org.slf4j.LoggerFactory 8 | 9 | /** 10 | * Runtime info. 11 | * 12 | * @author Hidetake Iwata 13 | */ 14 | @Singleton 15 | class Runtime { 16 | 17 | /** 18 | * Configure logback. 19 | * @param settings 20 | * level: log level ({@link String} or {@link Level}), 21 | * pattern: format ({@link String}) 22 | */ 23 | void logback(Map settings) { 24 | def root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) 25 | assert root instanceof ch.qos.logback.classic.Logger 26 | def originalLevel = root.level 27 | 28 | def encoder = new PatternLayoutEncoder() 29 | encoder.context = root.loggerContext 30 | encoder.pattern = settings.pattern ?: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %msg%n' 31 | encoder.start() 32 | 33 | def appender = new ConsoleAppender() 34 | appender.context = root.loggerContext 35 | appender.encoder = encoder 36 | appender.name = 'console' 37 | appender.start() 38 | 39 | root.loggerContext.reset() 40 | root.addAppender(appender) 41 | 42 | switch (settings.level) { 43 | case String: 44 | root.level = Level.toLevel(settings.level as String) 45 | break 46 | case Level: 47 | root.level = settings.level 48 | break 49 | default: 50 | root.level = originalLevel 51 | break 52 | } 53 | } 54 | 55 | /** 56 | * Path to self Groovy SSH JAR, or null if it is unknown 57 | */ 58 | @Lazy 59 | File jar = { 60 | def url = Runtime.getResource("/${Runtime.name.replace('.', '/')}.class") 61 | if (url.protocol == 'jar') { 62 | def m = url.file =~ /^file:(.+?)!/ 63 | if (m) { 64 | def jarFile = new File(m.group(1)) 65 | assert jarFile.exists() 66 | jarFile 67 | } else { 68 | null 69 | } 70 | } else { 71 | null 72 | } 73 | }() 74 | 75 | } 76 | -------------------------------------------------------------------------------- /groovy-ssh/cli/src/test/groovy/org/hidetake/groovy/ssh/MainDryRunSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh 2 | 3 | import com.jcraft.jsch.JSchException 4 | import org.hidetake.groovy.ssh.test.server.SshServerMock 5 | import org.junit.ClassRule 6 | import org.junit.rules.TemporaryFolder 7 | import spock.lang.Shared 8 | import spock.lang.Specification 9 | import spock.lang.Unroll 10 | 11 | import static org.hidetake.groovy.ssh.test.server.FilenameUtils.toUnixPath 12 | 13 | class MainDryRunSpec extends Specification { 14 | 15 | @Shared 16 | String script 17 | 18 | ByteArrayOutputStream stdoutBuffer 19 | 20 | PrintStream stdout 21 | 22 | @Shared @ClassRule 23 | TemporaryFolder temporaryFolder 24 | 25 | def setupSpec() { 26 | int port = SshServerMock.pickUpFreePort() 27 | def knownHostsFile = temporaryFolder.newFile() << "[localhost]:${port} dummy" 28 | script = """\ 29 | ssh.run { 30 | session( 31 | host: 'localhost', 32 | port: ${port}, 33 | knownHosts: new File('${toUnixPath(knownHostsFile.path)}'), 34 | user: 'someuser', 35 | password: 'somepassword' 36 | ) { 37 | execute('somecommand') { println 'Q6zLyqR1MKANtYJ4' } 38 | } 39 | } 40 | """ 41 | } 42 | 43 | def setup() { 44 | stdout = System.out 45 | stdoutBuffer = new ByteArrayOutputStream() 46 | System.out = new PrintStream(stdoutBuffer) 47 | } 48 | 49 | def cleanup() { 50 | System.out = stdout 51 | } 52 | 53 | def "script should fail due to connection refused"() { 54 | when: 55 | Main.main '-e', script 56 | 57 | then: 58 | JSchException e = thrown() 59 | e.cause instanceof ConnectException 60 | e.cause.message.startsWith 'Connection refused' 61 | } 62 | 63 | @Unroll 64 | def "flag #flag should enable dry run"() { 65 | when: 66 | Main.main flag, '-e', script 67 | 68 | then: 69 | stdoutBuffer.toString('UTF-8').contains('Q6zLyqR1MKANtYJ4') 70 | 71 | where: 72 | flag << ['--dry-run', '-n'] 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /groovy-ssh/cli/src/test/resources/hostkey_dsa: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIBugIBAAKBgQDr7tBpE/LrkghfUbe4issLb3tcLTZlpCDxC2Vy1f8E4vkpua8R 3 | eSBSJxuXSzaLnObhGc4s5y38/mR+BS+OLCW2vH57OTXShbINlGF8Y5okjL7tc0eo 4 | owHzrDyJ1U0UeBBCkmmCPJjZna4xNGnakKXkBRRLheHMCpd+X/3HExwO+wIVAIRI 5 | nG3eitk3sM4zhEihAvlsX0R/AoGAJC3mvwWmOmgM+djKPHpdSPx+gRFFV6nIGeay 6 | dJnJZfDpZ9UEVhFGBVIIj7TVHMA8WeXLdEaIZvYfy1sECWTWhJE0aXYzHVYx/N5C 7 | GixHH0oYSPGtmbfRbTJ/itoHHbBlZAMN2mhdiRD/hgmUndmUiNjOfjAzGfSPoTSo 8 | 1eFI3QICgYBRt25Cp4EQ0zX48BLKhRylZ7IgcBi7CvzHNUgvRfQJ78b/7CG10pYn 9 | PjRs4OqM0YkccklGrOXafFwTa+iK8PsBU/4IAy4XnJ5XStE7FrGEfVhlyOr+bi6C 10 | pjXk3opqJjt+5ImXSE6+bZ8JMt86B626Na+wba7QqU6NR/6viRxXRwIUUKFC8L/H 11 | id4VH959BWtteLkCxng= 12 | -----END DSA PRIVATE KEY----- -------------------------------------------------------------------------------- /groovy-ssh/cli/src/test/resources/hostkey_dsa.pub: -------------------------------------------------------------------------------- 1 | ssh-dss AAAAB3NzaC1kc3MAAACBAOvu0GkT8uuSCF9Rt7iKywtve1wtNmWkIPELZXLV/wTi+Sm5rxF5IFInG5dLNouc5uEZziznLfz+ZH4FL44sJba8fns5NdKFsg2UYXxjmiSMvu1zR6ijAfOsPInVTRR4EEKSaYI8mNmdrjE0adqQpeQFFEuF4cwKl35f/ccTHA77AAAAFQCESJxt3orZN7DOM4RIoQL5bF9EfwAAAIAkLea/BaY6aAz52Mo8el1I/H6BEUVXqcgZ5rJ0mcll8Oln1QRWEUYFUgiPtNUcwDxZ5ct0Rohm9h/LWwQJZNaEkTRpdjMdVjH83kIaLEcfShhI8a2Zt9FtMn+K2gcdsGVkAw3aaF2JEP+GCZSd2ZSI2M5+MDMZ9I+hNKjV4UjdAgAAAIBRt25Cp4EQ0zX48BLKhRylZ7IgcBi7CvzHNUgvRfQJ78b/7CG10pYnPjRs4OqM0YkccklGrOXafFwTa+iK8PsBU/4IAy4XnJ5XStE7FrGEfVhlyOr+bi6CpjXk3opqJjt+5ImXSE6+bZ8JMt86B626Na+wba7QqU6NR/6viRxXRw== 2 | -------------------------------------------------------------------------------- /groovy-ssh/core/bin/.gitignore: -------------------------------------------------------------------------------- 1 | /main/ 2 | /test/ 3 | -------------------------------------------------------------------------------- /groovy-ssh/core/build.gradle: -------------------------------------------------------------------------------- 1 | import com.vanniktech.maven.publish.SonatypeHost 2 | 3 | plugins { 4 | id 'java-library' 5 | id 'groovy' 6 | id "com.vanniktech.maven.publish" version "0.32.0" 7 | } 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | api localGroovy() 15 | api 'com.github.mwiede:jsch:2.27.0' 16 | api 'org.slf4j:slf4j-api:2.0.17' 17 | 18 | testImplementation platform("org.spockframework:spock-bom:2.3-groovy-3.0") 19 | testImplementation "org.spockframework:spock-core" 20 | testImplementation "org.spockframework:spock-junit4" 21 | testRuntimeOnly 'cglib:cglib-nodep:3.3.0' 22 | testRuntimeOnly 'org.objenesis:objenesis:3.4' 23 | testRuntimeOnly 'ch.qos.logback:logback-classic:1.5.18' 24 | } 25 | 26 | description = 'Groovy SSH library' 27 | group = 'org.hidetake' 28 | version = System.getenv('VERSION') ?: 'SNAPSHOT' 29 | 30 | test { 31 | useJUnitPlatform() 32 | } 33 | 34 | processResources { 35 | filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: ['version': project.version]) 36 | } 37 | 38 | mavenPublishing { 39 | publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) 40 | signAllPublications() 41 | pom { 42 | name = parent.name 43 | description = project.description 44 | url = 'https://github.com/int128/gradle-ssh-plugin' 45 | licenses { 46 | license { 47 | name = 'Apache-2.0' 48 | url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' 49 | } 50 | } 51 | developers { 52 | developer { 53 | id = 'int128' 54 | name = 'Hidetake Iwata' 55 | url = 'https://github.com/int128' 56 | } 57 | } 58 | scm { 59 | url = 'https://github.com/int128/gradle-ssh-plugin' 60 | connection = 'scm:git:https://github.com/int128/gradle-ssh-plugin' 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/Release.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh 2 | 3 | import com.jcraft.jsch.JSch 4 | 5 | /** 6 | * Release metadata. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class Release { 11 | 12 | private final bundle = ResourceBundle.getBundle(Release.class.name) 13 | 14 | final String name = bundle.getString('product.name') 15 | final String version = bundle.getString('product.version') 16 | 17 | final String javaVersion = System.getProperty('java.version') 18 | final String groovyVersion = GroovySystem.version 19 | final String jschVersion = JSch.VERSION 20 | 21 | @Override 22 | String toString() { 23 | "$name-$version (java-$javaVersion, groovy-$groovyVersion, jsch-$jschVersion)" 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/Ssh.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh 2 | 3 | import groovy.transform.CompileStatic 4 | import org.hidetake.groovy.ssh.core.Service 5 | 6 | /** 7 | * Entry point of Groovy SSH library. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @CompileStatic 12 | class Ssh { 13 | /** 14 | * Create an instance of {@link Service}. 15 | */ 16 | static Service newService() { 17 | new Service() 18 | } 19 | 20 | /** 21 | * Create a {@link GroovyShell} object to run a Groovy script. 22 | */ 23 | static GroovyShell newShell() { 24 | def binding = new Binding() 25 | binding.variables.ssh = newService() 26 | new GroovyShell(binding) 27 | } 28 | 29 | /** 30 | * Return the release metadata. 31 | */ 32 | @Lazy 33 | static Release release = { new Release() }() 34 | 35 | } 36 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/AddHostKey.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | class AddHostKey { 4 | 5 | final File knownHostsFile 6 | 7 | def AddHostKey(File knownHostsFile1) { 8 | knownHostsFile = knownHostsFile1 9 | assert knownHostsFile 10 | } 11 | 12 | @Override 13 | String toString() { 14 | "addHostKey($knownHostsFile)" 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/AllowAnyHosts.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | @Singleton 4 | class AllowAnyHosts { 5 | @Override 6 | String toString() { 7 | 'allowAnyHosts' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/Connection.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import com.jcraft.jsch.ChannelExec 4 | import com.jcraft.jsch.ChannelSftp 5 | import com.jcraft.jsch.ChannelShell 6 | import com.jcraft.jsch.Session 7 | import groovy.util.logging.Slf4j 8 | import org.hidetake.groovy.ssh.core.Remote 9 | import org.hidetake.groovy.ssh.session.forwarding.LocalPortForwardSettings 10 | import org.hidetake.groovy.ssh.session.forwarding.RemotePortForwardSettings 11 | 12 | /** 13 | * A connected SSH connection. 14 | * 15 | * @author Hidetake Iwata 16 | */ 17 | @Slf4j 18 | class Connection { 19 | final Remote remote 20 | final Session session 21 | 22 | /** 23 | * Constructor 24 | * @param remote1 25 | * @param session1 connected session 26 | * @return an instance 27 | */ 28 | def Connection(Remote remote1, Session session1) { 29 | remote = remote1 30 | session = session1 31 | assert remote 32 | assert session 33 | } 34 | 35 | /** 36 | * Create an execution channel. 37 | * 38 | * @return a channel 39 | */ 40 | ChannelExec createExecutionChannel() { 41 | session.openChannel('exec') as ChannelExec 42 | } 43 | 44 | /** 45 | * Create a shell channel. 46 | * 47 | * @param operationSettings 48 | * @return a channel 49 | */ 50 | ChannelShell createShellChannel() { 51 | session.openChannel('shell') as ChannelShell 52 | } 53 | 54 | /** 55 | * Create a SFTP channel. 56 | * 57 | * @return a channel 58 | */ 59 | ChannelSftp createSftpChannel() { 60 | session.openChannel('sftp') as ChannelSftp 61 | } 62 | 63 | /** 64 | * Set up local port forwarding. 65 | * 66 | * @param settings 67 | * @return local port 68 | */ 69 | int forwardLocalPort(LocalPortForwardSettings settings) { 70 | session.setPortForwardingL(settings.bind, settings.port, settings.host, settings.hostPort) 71 | } 72 | 73 | /** 74 | * Set up remote port forwarding. 75 | * 76 | * @param settings 77 | */ 78 | void forwardRemotePort(RemotePortForwardSettings settings) { 79 | session.setPortForwardingR(settings.bind, settings.port, settings.host, settings.hostPort) 80 | } 81 | 82 | /** 83 | * Cleanup the connection and all channels. 84 | */ 85 | void close() { 86 | session.disconnect() 87 | log.info("Disconnected from $remote") 88 | } 89 | 90 | @Override 91 | String toString() { 92 | "Connection[$remote]" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/ConnectionSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.Remote 5 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 6 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 7 | 8 | /** 9 | * Settings for establishing the SSH connection. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | trait ConnectionSettings implements UserAuthenticationSettings, HostAuthenticationSettings, ProxyConnectionSettings { 14 | /** 15 | * Gateway host. 16 | * This may be null. 17 | */ 18 | Remote gateway 19 | 20 | /** 21 | * Both connection timeout and socket read timeout in seconds. 22 | */ 23 | Integer timeoutSec 24 | 25 | /** 26 | * Retry count for connecting to a host. 27 | */ 28 | Integer retryCount 29 | 30 | /** 31 | * Interval time in seconds between retries. 32 | */ 33 | Integer retryWaitSec 34 | 35 | /** 36 | * Interval time in seconds between keep-alive packets. 37 | */ 38 | Integer keepAliveSec 39 | 40 | 41 | @EqualsAndHashCode 42 | static class With implements ConnectionSettings, ToStringProperties { 43 | def With() {} 44 | def With(ConnectionSettings... sources) { 45 | SettingsHelper.mergeProperties(this, sources) 46 | } 47 | 48 | static final ConnectionSettings DEFAULT = new ConnectionSettings.With( 49 | user: null, 50 | authentications: ['publickey', 'keyboard-interactive', 'password'], 51 | password: null, 52 | identity: null, 53 | passphrase: null, 54 | gateway: null, 55 | proxy: null, 56 | agent: false, 57 | knownHosts: new File("${System.properties['user.home']}/.ssh/known_hosts"), 58 | timeoutSec: 0, 59 | retryCount: 0, 60 | retryWaitSec: 0, 61 | keepAliveSec: 60, 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/HostAuthenticationSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | trait HostAuthenticationSettings { 4 | 5 | /** 6 | * Known hosts file. 7 | * This can be a {@link File}, {@link List} or {@link #allowAnyHosts}. 8 | */ 9 | def knownHosts 10 | 11 | /** 12 | * Represents that strict host key checking is turned off and any host is allowed. 13 | * @see #knownHosts 14 | */ 15 | final allowAnyHosts = AllowAnyHosts.instance 16 | 17 | /** 18 | * Represents that a host key is automatically appended to the known hosts file. 19 | * @param knownHostsFile 20 | * @return 21 | * @see #knownHosts 22 | */ 23 | AddHostKey addHostKey(File knownHostsFile) { 24 | new AddHostKey(knownHostsFile) 25 | } 26 | 27 | /** 28 | * Hides constant from result of {@link #toString()}. 29 | */ 30 | def toString__allowAnyHosts() {} 31 | 32 | } 33 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/JSchLogger.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import com.jcraft.jsch.JSch 4 | import com.jcraft.jsch.Logger 5 | import groovy.util.logging.Slf4j 6 | 7 | /** 8 | * A logger which bridges JSch and SLF4J. 9 | * It does not redirect DEBUG log because it is too detail and verbose. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | @Singleton 14 | @Slf4j 15 | class JSchLogger implements Logger { 16 | private static final ThreadLocal enabledInCurrentThread = new ThreadLocal<>() 17 | 18 | static void setEnabledInCurrentThread(boolean enabled) { 19 | JSch.logger = JSchLogger.instance 20 | enabledInCurrentThread.set(enabled) 21 | log.debug("${enabled ? 'Enabled' : 'Disabled'} JSch logging on ${Thread.currentThread()}") 22 | } 23 | 24 | @Override 25 | boolean isEnabled(int logLevel) { 26 | if (enabledInCurrentThread.get()) { 27 | switch (logLevel) { 28 | case INFO: return log.isDebugEnabled() 29 | case WARN: return log.isInfoEnabled() 30 | case ERROR: return log.isWarnEnabled() 31 | case FATAL: return log.isErrorEnabled() 32 | default: return false 33 | } 34 | } else { 35 | false 36 | } 37 | } 38 | 39 | @Override 40 | void log(int logLevel, String message) { 41 | switch (logLevel) { 42 | case INFO: 43 | log.debug("[jsch] $message") 44 | break 45 | case WARN: 46 | log.info("[jsch] $message") 47 | break 48 | case ERROR: 49 | log.warn("[jsch] $message") 50 | break 51 | case FATAL: 52 | log.error("[jsch] $message") 53 | break 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/ProxyConnection.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import com.jcraft.jsch.* 4 | import groovy.util.logging.Slf4j 5 | import org.hidetake.groovy.ssh.core.Remote 6 | 7 | import static org.hidetake.groovy.ssh.core.ProxyType.SOCKS 8 | 9 | @Slf4j 10 | trait ProxyConnection { 11 | 12 | void validateProxyConnection(ProxyConnectionSettings settings, Remote remote) { 13 | if (settings.proxy) { 14 | def validator = new ProxyValidator(settings.proxy) 15 | if (validator.error()) { 16 | throw new IllegalArgumentException(validator.error()) 17 | } 18 | if (validator.warnings()) { 19 | validator.warnings().each { warning -> log.info(warning) } 20 | } 21 | } 22 | } 23 | 24 | void configureProxyConnection(JSch jsch, Session session, Remote remote, ProxyConnectionSettings settings) { 25 | if (settings.proxy) { 26 | if (settings.proxy.type == SOCKS) { 27 | if (settings.proxy.socksVersion == 5) { 28 | def proxy = new ProxySOCKS5(settings.proxy.host, settings.proxy.port) 29 | proxy.setUserPasswd(settings.proxy.user, settings.proxy.password) 30 | session.proxy = proxy 31 | log.debug("Using SOCKS5 proxy for $remote: $settings.proxy") 32 | } else { 33 | def proxy = new ProxySOCKS4(settings.proxy.host, settings.proxy.port) 34 | proxy.setUserPasswd(settings.proxy.user, settings.proxy.password) 35 | session.proxy = proxy 36 | log.debug("Using SOCKS4 proxy for $remote: $settings.proxy") 37 | } 38 | } else { 39 | def proxy = new ProxyHTTP(settings.proxy.host, settings.proxy.port) 40 | proxy.setUserPasswd(settings.proxy.user, settings.proxy.password) 41 | session.proxy = proxy 42 | log.debug("Using HTTP proxy for $remote: $settings.proxy") 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/ProxyConnectionSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import org.hidetake.groovy.ssh.core.Proxy 4 | 5 | trait ProxyConnectionSettings { 6 | 7 | /** 8 | * Proxy configuration for connecting to a host. 9 | * This may be null. 10 | */ 11 | Proxy proxy 12 | 13 | } 14 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/ProxyValidator.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import org.hidetake.groovy.ssh.core.Proxy 4 | import org.hidetake.groovy.ssh.core.ProxyType 5 | 6 | import static org.hidetake.groovy.ssh.core.ProxyType.SOCKS 7 | 8 | /** 9 | * Basic validation and defaults for proxied connections created by {@link ConnectionManager}. 10 | * 11 | * @author mlipper 12 | * 13 | */ 14 | class ProxyValidator { 15 | protected static final SOCKS_DEFAULT_VERSION = 5 16 | protected static final SOCKS_SUPPORTED_VERSIONS = 4..5 17 | 18 | private final Proxy proxy 19 | private final Map report 20 | 21 | ProxyValidator(Proxy proxy1) { 22 | this.proxy = proxy1 23 | this.report = [error:null,warnings:[]] 24 | createReport() 25 | } 26 | 27 | String error() { report.error } 28 | 29 | List warnings() { report.warnings ?: null } 30 | 31 | private void createReport() { 32 | validateProxyType() 33 | ensureSocksVersion() 34 | checkCredentials() 35 | } 36 | 37 | private void validateProxyType() { 38 | if(!ProxyType.values().contains(proxy.type)) { 39 | report.error = "Unsupported ProxyType ${proxy.type}. Supported types: ${ProxyType.collect {"$it"}.join(', ')}." 40 | } 41 | } 42 | 43 | private void checkCredentials() { 44 | // DefaultConnectionManager ignores authentication credentials when 45 | // creating proxy server connections unless both proxy.user and 46 | // proxy.password are set 47 | if(proxy.user && !proxy.password) { 48 | addWarning("proxy.user is set but proxy.password is null. Credentials are ignored for proxy '${proxy.name}'") 49 | } 50 | if(!proxy.user && proxy.password) { 51 | addWarning("proxy.password is set but proxy.user is null. Credentials are ignored for proxy '${proxy.name}'") 52 | } 53 | } 54 | 55 | private void ensureSocksVersion() { 56 | def v = proxy.socksVersion 57 | if(SOCKS == proxy.type && !SOCKS_SUPPORTED_VERSIONS.contains(v)) { 58 | if(v == 0) { 59 | addWarning("Using SOCKS v$SOCKS_DEFAULT_VERSION since proxy.socksVersion is not set.") 60 | } else { 61 | addWarning("Using SOCKS v$SOCKS_DEFAULT_VERSION since proxy.socksVersion is set to ${proxy.socksVersion} which is not supported by this implementation.") 62 | } 63 | proxy.socksVersion = SOCKS_DEFAULT_VERSION 64 | } 65 | } 66 | 67 | private void addWarning(String message) { 68 | report.warnings.add(message) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/UserAuthentication.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import org.hidetake.groovy.ssh.core.Remote 4 | 5 | import com.jcraft.jsch.AgentIdentityRepository 6 | import com.jcraft.jsch.IdentityRepository 7 | import com.jcraft.jsch.JSch 8 | import com.jcraft.jsch.SSHAgentConnector 9 | import com.jcraft.jsch.Session 10 | 11 | import groovy.util.logging.Slf4j 12 | 13 | @Slf4j 14 | trait UserAuthentication { 15 | 16 | void validateUserAuthentication(UserAuthenticationSettings settings, Remote remote) { 17 | assert settings.user, "user must be given ($remote)" 18 | assert settings.identity instanceof File || settings.identity instanceof String || settings.identity == null, 19 | "identity must be a File, String or null ($remote)" 20 | } 21 | 22 | void configureUserAuthentication(JSch jsch, Session session, Remote remote, UserAuthenticationSettings settings) { 23 | session.setConfig('PreferredAuthentications', settings.authentications.join(',')) 24 | 25 | if (settings.password) { 26 | session.password = settings.password 27 | log.debug("Using password authentication for $remote") 28 | } 29 | 30 | if (settings.agent) { 31 | // Use agent authentication using https://github.com/mwiede/jsch/issues/65#issuecomment-913051572 32 | IdentityRepository irepo = new AgentIdentityRepository(new SSHAgentConnector()) 33 | jsch.identityRepository = irepo 34 | log.debug("Using SSH agent authentication for $remote") 35 | } else { 36 | jsch.identityRepository = null /* null means the default repository */ 37 | jsch.removeAllIdentity() 38 | if (settings.identity) { 39 | final identity = settings.identity 40 | if (identity instanceof File) { 41 | jsch.addIdentity(identity.path, settings.passphrase as String) 42 | log.debug("Using public key authentication for $remote: $identity.path") 43 | } else if (identity instanceof String) { 44 | jsch.addIdentity("identity-${identity.hashCode()}", identity.bytes, null, settings.passphrase?.bytes) 45 | log.debug("Using public key authentication for $remote") 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/connection/UserAuthenticationSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | trait UserAuthenticationSettings { 4 | /** 5 | * Remote user. 6 | */ 7 | String user 8 | 9 | /** 10 | * Authentication methods. 11 | */ 12 | List authentications 13 | 14 | /** 15 | * Password. 16 | * Leave as null if the password authentication is not needed. 17 | */ 18 | String password 19 | 20 | /** 21 | * Hides credential from result of {@link #toString()}. 22 | */ 23 | def toString__password() { '...' } 24 | 25 | /** 26 | * Identity key file for public-key authentication. 27 | * This must be a {@link File}, {@link String} or null. 28 | * Leave as null if the public key authentication is not needed. 29 | */ 30 | def identity 31 | 32 | /** 33 | * {@link #toString()} formatter to hide credential. 34 | */ 35 | def toString__identity() { identity instanceof File ? identity : '...' } 36 | 37 | /** 38 | * Pass-phrase for the identity key. 39 | * This may be null. 40 | */ 41 | String passphrase 42 | 43 | /** 44 | * Hides credential from result of {@link #toString()}. 45 | */ 46 | def toString__passphrase() { '...' } 47 | 48 | def plus__passphrase(UserAuthenticationSettings prior) { 49 | if (prior.identity == null) { 50 | if (identity == null) { 51 | null 52 | } else { 53 | passphrase 54 | } 55 | } else { 56 | prior.passphrase 57 | } 58 | } 59 | 60 | /** 61 | * Use agent flag. 62 | * If true, Putty Agent or ssh-agent will be used to authenticate. 63 | */ 64 | Boolean agent 65 | } 66 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/ParallelSessionsException.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core 2 | 3 | import java.util.concurrent.ForkJoinTask 4 | 5 | /** 6 | * An exception for parallel sessions. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class ParallelSessionsException extends Exception { 11 | final List tasks 12 | final List causes 13 | 14 | def ParallelSessionsException(String message, List tasks1) { 15 | super(message, firstCause(tasks1)) 16 | tasks = tasks1 17 | causes = tasks*.exception.findAll() 18 | } 19 | 20 | private static firstCause(List tasks) { 21 | tasks.find { task -> task.completedAbnormally }.exception 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/Proxy.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core 2 | 3 | /** 4 | * Represents a connection proxy to use when establishing a {@link org.hidetake.groovy.ssh.connection.Connection}. 5 | * An instance of this class be shared by multiple {@link Remote}s. 6 | * 7 | * @author mlipper 8 | * 9 | */ 10 | class Proxy { 11 | /** 12 | * Adds all type of the {@link ProxyType}, 13 | * in order to omit import in a build script. 14 | */ 15 | static { 16 | ProxyType.values().each { proxyType -> 17 | Proxy.metaClass[proxyType.name()] = proxyType 18 | } 19 | } 20 | 21 | /** 22 | * Name of this instance. 23 | */ 24 | final String name 25 | 26 | def Proxy(String name1) { 27 | name = name1 28 | assert name 29 | } 30 | 31 | /** 32 | * Proxy protocol type 33 | */ 34 | ProxyType type 35 | 36 | /** 37 | * SOCKS protocol version. 38 | * This should be set when using ProxyType.SOCKS. 39 | */ 40 | int socksVersion 41 | 42 | /** 43 | * Port. 44 | */ 45 | int port 46 | 47 | /** 48 | * Proxy host. 49 | */ 50 | String host 51 | 52 | /** 53 | * Proxy user. 54 | * This may be null. 55 | */ 56 | String user 57 | 58 | /** 59 | * Proxy password. 60 | * This may be null. 61 | */ 62 | String password 63 | 64 | String toString() { 65 | "$name [$host:$port]" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/ProxyType.java: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core; 2 | 3 | /** 4 | * Proxy type. 5 | * Implemented as Java native enum for Gradle 1.x compatibility. 6 | * 7 | * @author mlipper 8 | * @author Hidetake Iwata 9 | */ 10 | public enum ProxyType { 11 | HTTP, 12 | SOCKS 13 | } 14 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/Remote.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.CompositeSettings 5 | 6 | import java.util.concurrent.atomic.AtomicInteger 7 | 8 | /** 9 | * Represents a remote host. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | @EqualsAndHashCode(includes = 'name') 14 | class Remote implements CompositeSettings { 15 | /** 16 | * Name of this instance. 17 | */ 18 | final String name 19 | 20 | def Remote(String name1) { 21 | name = name1 22 | assert name 23 | } 24 | 25 | def Remote(Map settingsMap) { 26 | name = settingsMap.name ?: "Remote${sequenceForAutoNaming.incrementAndGet()}" 27 | settingsMap.findAll { key, value -> 28 | key != 'name' 29 | }.each { key, value -> 30 | setProperty(key, value) 31 | } 32 | } 33 | 34 | private static final sequenceForAutoNaming = new AtomicInteger() 35 | 36 | /** 37 | * Remote host. 38 | */ 39 | String host 40 | 41 | /** 42 | * Port. 43 | */ 44 | int port = 22 45 | 46 | /** 47 | * Roles. 48 | */ 49 | final Set roles = [] 50 | 51 | /** 52 | * Add the role. 53 | * @param role 54 | */ 55 | void role(String role) { 56 | assert role != null, 'role should be set' 57 | roles.add(role) 58 | } 59 | 60 | String toString() { 61 | "$name[$host:$port]" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/container/Container.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.container 2 | 3 | import static org.hidetake.groovy.ssh.util.Utility.callWithDelegate 4 | 5 | /** 6 | * A container. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | trait Container implements Map { 11 | /** 12 | * Add an item. 13 | * The item must have name property. 14 | * If this map already contains an item with same name, it will be overwritten. 15 | * 16 | * @param item 17 | * @return true if this map did not already contain the same name 18 | */ 19 | boolean add(T item) { 20 | assert item.name instanceof String 21 | put(item.name as String, item) ? false : true 22 | } 23 | 24 | /** 25 | * Add items. 26 | * Each item must have name property. 27 | * If this map already contains an item with same name, it will be overwritten. 28 | * 29 | * @param items 30 | */ 31 | void addAll(Collection items) { 32 | putAll(items.collectEntries { item -> 33 | assert item.name instanceof String 34 | [(item.name): item] 35 | }) 36 | } 37 | 38 | /** 39 | * Create an item and add it. 40 | * If this map already contains an item with same name, it will be overwritten. 41 | * 42 | * @param name 43 | * @param closure 44 | * @return item 45 | */ 46 | T create(String name, Closure closure) { 47 | assert name 48 | assert getContainerElementType() instanceof Class 49 | T namedObject = getContainerElementType().newInstance(name) 50 | callWithDelegate(closure, namedObject) 51 | add(namedObject) 52 | namedObject 53 | } 54 | 55 | /** 56 | * Type of the container element. 57 | */ 58 | abstract Class getContainerElementType() 59 | } 60 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/container/ContainerBuilder.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.container 2 | 3 | /** 4 | * An evaluator of a closure which contains named objects. 5 | * 6 | * @param < T > type of the named object 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class ContainerBuilder { 11 | private final Container container 12 | 13 | def ContainerBuilder(Container container1) { 14 | container = container1 15 | } 16 | 17 | T methodMissing(String name, args) { 18 | assert args instanceof Object[] 19 | 20 | try { 21 | assert args.length == 1 22 | 23 | def configurationClosure = args[0] 24 | assert configurationClosure instanceof Closure 25 | 26 | def containerClosure = configurationClosure.delegate 27 | assert containerClosure instanceof Closure 28 | 29 | def delegateOfContainerClosure = containerClosure.delegate 30 | assert !container.getContainerElementType().isInstance(delegateOfContainerClosure) 31 | } catch (AssertionError ignore) { 32 | throw new MissingMethodException(name, ContainerBuilder, args) 33 | } 34 | 35 | container.create(name, args[0] as Closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/container/ProxyContainer.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.container 2 | 3 | import org.hidetake.groovy.ssh.core.Proxy 4 | 5 | import java.util.concurrent.ConcurrentSkipListMap 6 | 7 | /** 8 | * A container of proxies. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | class ProxyContainer extends ConcurrentSkipListMap implements Container { 13 | final Class containerElementType = Proxy 14 | } 15 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/container/RemoteContainer.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.container 2 | 3 | import org.hidetake.groovy.ssh.core.Remote 4 | 5 | import java.util.concurrent.ConcurrentSkipListMap 6 | 7 | /** 8 | * A container of remote hosts. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | class RemoteContainer extends ConcurrentSkipListMap implements Container, RoleAccessible { 13 | final Class containerElementType = Remote 14 | } 15 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/container/RoleAccessible.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.container 2 | 3 | import org.hidetake.groovy.ssh.core.Remote 4 | 5 | /** 6 | * Provides role access for remote hosts. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | trait RoleAccessible { 11 | /** 12 | * Find remote hosts associated with given roles. 13 | * 14 | * @param roles one or more roles 15 | * @return remote hosts associated with given roles 16 | */ 17 | Collection role(String... roles) { 18 | assert roles, 'At least one role must be given' 19 | getAsRemoteCollection().findAll { it.roles.any { it in roles } } 20 | } 21 | 22 | /** 23 | * Find remote hosts associated with all given roles. 24 | * 25 | * @param roles one or more roles 26 | * @return remote hosts associated with all given roles 27 | */ 28 | Collection allRoles(String... roles) { 29 | assert roles, 'At least one role must be given' 30 | getAsRemoteCollection().findAll { it.roles.containsAll(roles) } 31 | } 32 | 33 | private Collection getAsRemoteCollection() { 34 | if (this instanceof Map) { 35 | (this as Map).values() 36 | } else { 37 | (this as Collection) 38 | } 39 | } 40 | 41 | static interface RoleAccessor { 42 | /** 43 | * Find remote hosts associated with given role. 44 | * 45 | * @param name a role 46 | * @return remote hosts associated with given roles 47 | */ 48 | Collection getAt(String name) 49 | } 50 | 51 | final RoleAccessor role = [getAt: { String name -> role(name) }] as RoleAccessor 52 | } 53 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/settings/CompositeSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.settings 2 | 3 | import org.hidetake.groovy.ssh.connection.ConnectionSettings 4 | import org.hidetake.groovy.ssh.operation.CommandSettings 5 | import org.hidetake.groovy.ssh.operation.ShellSettings 6 | import org.hidetake.groovy.ssh.session.SessionSettings 7 | import org.hidetake.groovy.ssh.session.execution.SudoSettings 8 | import org.hidetake.groovy.ssh.session.transfer.FileTransferSettings 9 | 10 | /** 11 | * Represents overall settings configurable in 12 | * {@link org.hidetake.groovy.ssh.core.Service#settings} and 13 | * {@link org.hidetake.groovy.ssh.core.RunHandler#settings}. 14 | * 15 | * @author Hidetake Iwata 16 | */ 17 | trait CompositeSettings implements 18 | ConnectionSettings, 19 | SessionSettings, 20 | CommandSettings, 21 | ShellSettings, 22 | SudoSettings, 23 | FileTransferSettings 24 | { 25 | static class With implements CompositeSettings, ToStringProperties { 26 | def With() {} 27 | def With(CompositeSettings... sources) { 28 | SettingsHelper.mergeProperties(this, sources) 29 | } 30 | 31 | static final CompositeSettings DEFAULT = new CompositeSettings.With() 32 | static { 33 | SettingsHelper.mergeProperties(DEFAULT, 34 | ConnectionSettings.With.DEFAULT, 35 | SessionSettings.With.DEFAULT, 36 | CommandSettings.With.DEFAULT, 37 | ShellSettings.With.DEFAULT, 38 | SudoSettings.With.DEFAULT, 39 | FileTransferSettings.With.DEFAULT, 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/settings/GlobalSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.settings 2 | 3 | /** 4 | * A global settings. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | class GlobalSettings extends CompositeSettings.With { 9 | } 10 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/settings/LoggingMethod.java: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.settings; 2 | 3 | /** 4 | * Logging method type. 5 | * Implemented as Java native enum for Gradle 1.x compatibility. 6 | * 7 | * @author Hidetake Iwata 8 | */ 9 | public enum LoggingMethod { 10 | /** 11 | * Log is sent to SLF4J. 12 | */ 13 | slf4j, 14 | 15 | /** 16 | * Log is sent to standard output or error. 17 | */ 18 | stdout, 19 | 20 | /** 21 | * Logging is turned off. 22 | */ 23 | none 24 | } 25 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/settings/PerServiceSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.settings 2 | 3 | /** 4 | * Per-service settings. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | class PerServiceSettings extends CompositeSettings.With { 9 | } 10 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/settings/ToStringProperties.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.settings 2 | 3 | /** 4 | * A trait for overriding {@link Object#toString()} to show properties of this object. 5 | * This may help debug by user friendly log. 6 | * 7 | * @author Hidetake Iwata 8 | */ 9 | trait ToStringProperties { 10 | /** 11 | * Returns a string representation of this settings. 12 | * Class should implements method toString__propertyName 13 | * to exclude or customize property representation. 14 | * See test for details of specification. 15 | * 16 | * @returns string representation of this settings 17 | */ 18 | @Override 19 | String toString() { 20 | '{' + SettingsHelper.computePropertiesToString(this).collect { key, value -> 21 | "$key=${value.toString()}" 22 | }.join(', ') + '}' 23 | } 24 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/core/type/InputStreamValue.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core.type 2 | 3 | /** 4 | * A value object for 5 | * {@link org.hidetake.groovy.ssh.operation.CommandSettings#inputStream}, 6 | * {@link org.hidetake.groovy.ssh.operation.ShellSettings#inputStream}, 7 | * {@link org.hidetake.groovy.ssh.session.execution.SudoSettings#inputStream}. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | class InputStreamValue { 12 | 13 | private final value 14 | 15 | def InputStreamValue(def value1) { 16 | if (value == null || 17 | value instanceof InputStream || 18 | value instanceof byte[] || 19 | value instanceof String || 20 | value instanceof File) { 21 | value = value1 22 | } else { 23 | throw new IllegalArgumentException("inputStream must be InputStream, byte[], String or File: $value1") 24 | } 25 | } 26 | 27 | boolean asBoolean() { 28 | value != null 29 | } 30 | 31 | InputStreamValue rightShift(OutputStream stream) { 32 | if (value instanceof InputStream) { 33 | stream << value 34 | } else if (value instanceof byte[]) { 35 | stream << value 36 | } else if (value instanceof String) { 37 | stream << value 38 | } else if (value instanceof File) { 39 | value.withInputStream { 40 | stream << it 41 | } 42 | } 43 | this 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/Context.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | /** 6 | * A context class of stream interaction. 7 | * This should be pushed into the stack and replaced on rule matched. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @Slf4j 12 | class Context { 13 | 14 | private final List rules 15 | 16 | def Context(List rules1) { 17 | rules = rules1 18 | } 19 | 20 | MatchResult match(Stream stream, Buffer buffer) { 21 | rules.findResult { rule -> 22 | rule.match(stream, buffer) 23 | } 24 | } 25 | 26 | @Override 27 | String toString() { 28 | "${Context.simpleName}$rules" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/InteractionException.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | /** 4 | * An exception thrown if one or more exceptions occurred while stream interaction. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | class InteractionException extends IOException { 9 | final List exceptions 10 | 11 | def InteractionException(Throwable exception) { 12 | super("Error while stream interaction: $exception", exception) 13 | this.exceptions = [exception] 14 | } 15 | 16 | def InteractionException(Throwable... exceptions) { 17 | super("Error while stream interaction: $exceptions") 18 | this.exceptions = exceptions 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/InteractionHandler.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | /** 4 | * A handler of the interaction closure. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | class InteractionHandler { 9 | /** 10 | * Wildcard for condition expression. 11 | */ 12 | static final _ = Wildcard.instance 13 | 14 | static final standardOutput = Stream.StandardOutput 15 | 16 | static final standardError = Stream.StandardError 17 | 18 | /** 19 | * A standard input for the remote command. 20 | */ 21 | final OutputStream standardInput 22 | 23 | final List when = [] 24 | boolean popContext = false 25 | 26 | def InteractionHandler(OutputStream standardInput1) { 27 | standardInput = standardInput1 28 | assert standardInput 29 | } 30 | 31 | /** 32 | * Declare an interaction rule. 33 | * 34 | * @param condition see {@link StreamRule} and {@link BufferRule} for details 35 | * @param action closure called with result ({@link String}, {@link java.util.regex.Matcher} or {@code byte[]}) when condition is satisfied 36 | */ 37 | void when(Map condition, Closure action) { 38 | assert condition, 'at least one rule must be given' 39 | assert action, 'closure must be given' 40 | when.add(new Rule(condition, action)) 41 | } 42 | 43 | /** 44 | * Pop context stack of {@link Processor}. 45 | * This should not be used with {@link #when(java.util.Map, groovy.lang.Closure)}. 46 | */ 47 | void popContext() { 48 | popContext = true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/Listener.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | /** 6 | * A listener of lines and partial strings from streams. 7 | * It notifies events to {@link Processor}s on start, received bytes and end of stream. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @Slf4j 12 | class Listener { 13 | private final List processors = [] 14 | 15 | void add(Processor processor) { 16 | processors.add(processor) 17 | } 18 | 19 | void start(Stream stream) { 20 | processors*.start(stream) 21 | } 22 | 23 | void receive(Stream stream, byte[] bytes, int length) { 24 | processors*.receive(stream, bytes, length) 25 | } 26 | 27 | void end(Stream stream) { 28 | processors*.end(stream) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/MatchResult.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | import java.util.regex.Matcher 4 | 5 | class MatchResult { 6 | 7 | private final Rule rule 8 | private final E result 9 | 10 | def MatchResult(Rule rule1, E result1) { 11 | rule = rule1 12 | result = result1 13 | } 14 | 15 | def getActionWithResult() { 16 | rule.action.curry(result) 17 | } 18 | 19 | def getResultAsString() { 20 | if (result instanceof Matcher) { 21 | result.group() 22 | } else if (result instanceof byte[]) { 23 | "byte[$result.length]" 24 | } else { 25 | result.toString() 26 | } 27 | } 28 | 29 | @Override 30 | String toString() { 31 | "$rule -> $resultAsString" 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/Receiver.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | import java.util.concurrent.atomic.AtomicInteger 6 | 7 | /** 8 | * A receiver thread reading lines from the stream. 9 | * It notifies events to {@link Listener} on received bytes. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | @Slf4j 14 | class Receiver implements Runnable { 15 | static final READ_BUFFER_SIZE = 1024 * 1024 16 | 17 | private static final sequenceForNaming = new AtomicInteger() 18 | 19 | final Stream stream 20 | final List pipes = [] 21 | 22 | private final Listener listener 23 | private final InputStream inputStream 24 | private final int id = sequenceForNaming.incrementAndGet() 25 | 26 | def Receiver(Listener listener1, Stream stream1, InputStream inputStream1) { 27 | listener = listener1 28 | stream = stream1 29 | inputStream = inputStream1 30 | assert listener 31 | assert stream 32 | assert inputStream 33 | } 34 | 35 | @Override 36 | void run() { 37 | try { 38 | log.trace("Started receiver $this") 39 | try { 40 | readStream() 41 | } catch (InterruptedIOException e) { 42 | log.debug("Interrupted receiver $this", e) 43 | } finally { 44 | inputStream.close() 45 | } 46 | } finally { 47 | log.trace("Finished receiver $this") 48 | } 49 | } 50 | 51 | private void readStream() { 52 | listener.start(stream) 53 | 54 | def readBuffer = new byte[READ_BUFFER_SIZE] 55 | while (!Thread.currentThread().interrupted) { 56 | log.trace("Waiting for $stream") 57 | def readLength = inputStream.read(readBuffer) 58 | if (readLength < 0) { 59 | log.trace("Reached end of stream on $stream") 60 | break 61 | } 62 | 63 | log.trace("Received $readLength bytes from $stream") 64 | pipes*.write(readBuffer, 0, readLength) 65 | listener.receive(stream, readBuffer, readLength) 66 | } 67 | 68 | listener.end(stream) 69 | } 70 | 71 | @Override 72 | String toString() { 73 | "${Receiver.simpleName}-${id}[${stream}]" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/Rule.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | 5 | /** 6 | * A rule of interaction with the stream. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | @EqualsAndHashCode 11 | class Rule { 12 | 13 | final Map condition 14 | 15 | final StreamRule streamRule 16 | final BufferRule bufferRule 17 | 18 | final Closure action 19 | 20 | def Rule(Map condition1, Closure action1) { 21 | condition = condition1 22 | action = action1 23 | 24 | streamRule = StreamRule.Factory.create(condition.from) 25 | bufferRule = BufferRule.Factory.create(condition) 26 | } 27 | 28 | MatchResult match(Stream stream, Buffer buffer) { 29 | if (streamRule.matches(stream)) { 30 | def matchResult = bufferRule.match(buffer) 31 | if (matchResult != null) { 32 | new MatchResult(this, matchResult) 33 | } else { 34 | null 35 | } 36 | } else { 37 | null 38 | } 39 | } 40 | 41 | String toString() { 42 | "${Rule.simpleName}${condition}" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/Stream.java: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction; 2 | 3 | /** 4 | * Stream type. 5 | * Implemented as Java native enum for Gradle 1.x compatibility. 6 | * 7 | * @author Hidetake Iwata 8 | */ 9 | public enum Stream { 10 | StandardOutput, 11 | StandardError 12 | } 13 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/StreamRule.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | interface StreamRule { 4 | boolean matches(Stream stream) 5 | 6 | static final anyRule = [matches: { Stream s -> true }] as StreamRule 7 | static final standardOutputRule = [matches: { Stream s -> s == Stream.StandardOutput }] as StreamRule 8 | static final standardErrorRule = [matches: { Stream s -> s == Stream.StandardError }] as StreamRule 9 | 10 | static class Factory { 11 | static StreamRule create(fromParameter) { 12 | switch (fromParameter) { 13 | case null: return anyRule 14 | case Wildcard: return anyRule 15 | case Stream.StandardOutput: return standardOutputRule 16 | case Stream.StandardError: return standardErrorRule 17 | default: throw new IllegalArgumentException("parameter must be Wildcard or Stream: from=$fromParameter") 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/interaction/Wildcard.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | /** 4 | * Represents a wildcard. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | @Singleton 9 | class Wildcard { 10 | String toString() { 11 | '_' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/CommandSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.LoggingMethod 5 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 6 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 7 | 8 | /** 9 | * Settings for {@link Command}. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | trait CommandSettings { 14 | /** 15 | * Ignores the exit status of the command or shell. 16 | */ 17 | Boolean ignoreError 18 | 19 | /** 20 | * PTY allocation flag. 21 | * If true, PTY will be allocated on command execution. 22 | */ 23 | Boolean pty 24 | 25 | /** 26 | * Use agentForwarding flag. 27 | * If true, agent will be forwarded to remote host. 28 | */ 29 | Boolean agentForwarding 30 | 31 | /** 32 | * A logging method of the remote command or shell. 33 | */ 34 | LoggingMethod logging 35 | 36 | /** 37 | * An input stream to send to the standard input. 38 | * This should be a {@link InputStream}, {@code byte[]}, {@link String} or {@link File}. 39 | */ 40 | def inputStream 41 | 42 | /** 43 | * An output stream to receive from the standard output. 44 | */ 45 | OutputStream outputStream 46 | 47 | /** 48 | * An output stream to receive from the standard error. 49 | */ 50 | OutputStream errorStream 51 | 52 | /** 53 | * Encoding of input and output stream. 54 | */ 55 | String encoding 56 | 57 | /** 58 | * Stream interaction. 59 | * @see org.hidetake.groovy.ssh.interaction.InteractionHandler 60 | */ 61 | Closure interaction 62 | 63 | /** 64 | * Timeout for the command channel to be connected in seconds. 65 | * @see org.hidetake.groovy.ssh.connection.ConnectionSettings#timeoutSec 66 | */ 67 | Integer timeoutSec 68 | 69 | 70 | @EqualsAndHashCode 71 | static class With implements CommandSettings, ToStringProperties { 72 | def With() {} 73 | def With(CommandSettings... sources) { 74 | SettingsHelper.mergeProperties(this, sources) 75 | } 76 | 77 | static final CommandSettings DEFAULT = new CommandSettings.With( 78 | ignoreError: false, 79 | pty: false, 80 | agentForwarding: false, 81 | logging: LoggingMethod.slf4j, 82 | encoding: 'UTF-8', 83 | timeoutSec: 0, 84 | ) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/DryRunOperation.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import org.hidetake.groovy.ssh.interaction.InteractionHandler 4 | 5 | class DryRunOperation implements Operation { 6 | @Override 7 | int execute() { 8 | 0 9 | } 10 | 11 | @Override 12 | void addInteraction(@DelegatesTo(InteractionHandler) Closure closure) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/DryRunOperations.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.hidetake.groovy.ssh.core.Remote 5 | import org.hidetake.groovy.ssh.session.forwarding.LocalPortForwardSettings 6 | import org.hidetake.groovy.ssh.session.forwarding.RemotePortForwardSettings 7 | import org.hidetake.groovy.ssh.session.transfer.FileTransferSettings 8 | 9 | /** 10 | * Dry-run implementation of {@link Operations}. 11 | * 12 | * @author Hidetake Iwata 13 | */ 14 | @Slf4j 15 | class DryRunOperations implements Operations { 16 | final Remote remote 17 | 18 | def DryRunOperations(Remote remote1) { 19 | remote = remote1 20 | assert remote 21 | } 22 | 23 | @Override 24 | Operation shell(ShellSettings settings) { 25 | log.info("Executing shell on $remote") 26 | new DryRunOperation() 27 | } 28 | 29 | @Override 30 | Operation command(CommandSettings settings, String commandLine) { 31 | log.info("Executing command on $remote: $commandLine") 32 | new DryRunOperation() 33 | } 34 | 35 | @Override 36 | int forwardLocalPort(LocalPortForwardSettings settings) { 37 | log.info("Requesting local port forwarding on $remote with ${new LocalPortForwardSettings.With(settings)}") 38 | 0 39 | } 40 | 41 | @Override 42 | void forwardRemotePort(RemotePortForwardSettings settings) { 43 | log.info("Requesting remote port forwarding on $remote with ${new RemotePortForwardSettings.With(settings)}") 44 | } 45 | 46 | @Override 47 | def T sftp(FileTransferSettings settings, @DelegatesTo(SftpOperations) Closure closure) { 48 | log.info("Requesting SFTP subsystem on $remote") 49 | null 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/Operation.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import org.hidetake.groovy.ssh.interaction.InteractionHandler 4 | 5 | /** 6 | * An operation such as a command or shell execution. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | interface Operation { 11 | /** 12 | * Execute the operation. 13 | * 14 | * @return exit status 15 | */ 16 | int execute() 17 | 18 | /** 19 | * Adds an interaction. 20 | * 21 | * @param closure definition of interaction 22 | */ 23 | void addInteraction(@DelegatesTo(InteractionHandler) Closure closure) 24 | } 25 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/Operations.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import org.hidetake.groovy.ssh.core.Remote 4 | import org.hidetake.groovy.ssh.session.forwarding.LocalPortForwardSettings 5 | import org.hidetake.groovy.ssh.session.forwarding.RemotePortForwardSettings 6 | import org.hidetake.groovy.ssh.session.transfer.FileTransferSettings 7 | 8 | /** 9 | * An aggregate of core SSH operations. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | interface Operations { 14 | Remote getRemote() 15 | 16 | Operation shell(ShellSettings settings) 17 | 18 | Operation command(CommandSettings settings, String commandLine) 19 | 20 | int forwardLocalPort(LocalPortForwardSettings settings) 21 | 22 | void forwardRemotePort(RemotePortForwardSettings settings) 23 | 24 | def T sftp(FileTransferSettings settings, @DelegatesTo(SftpOperations) Closure closure) 25 | } 26 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/SftpException.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import com.jcraft.jsch.SftpException as JschSftpException 4 | 5 | /** 6 | * Represents SFTP error. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class SftpException extends IOException { 11 | final SftpError error 12 | 13 | def SftpException(String contextMessage, JschSftpException cause) { 14 | this(contextMessage, cause, SftpError.find(cause.id)) 15 | } 16 | 17 | def SftpException(String contextMessage, JschSftpException cause, SftpError error) { 18 | super("$contextMessage: (${error.name()}: ${error.message}): ${cause.message}", cause) 19 | this.error = error 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/SftpProgress.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import com.jcraft.jsch.SftpProgressMonitor 4 | import groovy.transform.CompileStatic 5 | import org.hidetake.groovy.ssh.util.FileTransferProgress 6 | 7 | /** 8 | * A bridge class between {@link SftpProgressMonitor} and {@link FileTransferProgress}. 9 | * 10 | * Use {@link CompileStatic} to prevent {@link IncompatibleClassChangeError} on Gradle 1.x. 11 | * 12 | * @author Hidetake Iwata 13 | */ 14 | @CompileStatic 15 | class SftpProgress extends FileTransferProgress implements SftpProgressMonitor { 16 | 17 | def SftpProgress(Closure notifier) { 18 | super(notifier) 19 | } 20 | 21 | @Override 22 | void init(int op, String src, String dest, long max) { 23 | reset(max) 24 | } 25 | 26 | @Override 27 | boolean count(long count) { 28 | report(count) 29 | true 30 | } 31 | 32 | @Override 33 | void end() { 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/operation/ShellSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.operation 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.LoggingMethod 5 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 6 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 7 | 8 | /** 9 | * Settings for {@link Shell}. 10 | * 11 | * @author Hidetake Iwata 12 | */ 13 | trait ShellSettings { 14 | /** 15 | * Ignores the exit status of the command or shell. 16 | */ 17 | Boolean ignoreError 18 | 19 | /** 20 | * PTY allocation flag. 21 | * If true, PTY will be allocated on command execution. 22 | */ 23 | Boolean pty 24 | 25 | /** 26 | * Use agentForwarding flag. 27 | * If true, agent will be forwarded to remote host. 28 | */ 29 | Boolean agentForwarding 30 | 31 | /** 32 | * A logging method of the remote command or shell. 33 | */ 34 | LoggingMethod logging 35 | 36 | /** 37 | * An input stream to send to the standard input. 38 | * This should be a {@link InputStream}, {@code byte[]}, {@link String} or {@link File}. 39 | */ 40 | def inputStream 41 | 42 | /** 43 | * An output stream to receive from the standard output. 44 | */ 45 | OutputStream outputStream 46 | 47 | /** 48 | * Encoding of input and output stream. 49 | */ 50 | String encoding 51 | 52 | /** 53 | * Stream interaction. 54 | * @see org.hidetake.groovy.ssh.interaction.InteractionHandler 55 | */ 56 | Closure interaction 57 | 58 | /** 59 | * Timeout for the shell channel to be connected in seconds. 60 | * @see org.hidetake.groovy.ssh.connection.ConnectionSettings#timeoutSec 61 | */ 62 | Integer timeoutSec 63 | 64 | 65 | @EqualsAndHashCode 66 | static class With implements ShellSettings, ToStringProperties { 67 | def With() {} 68 | def With(ShellSettings... sources) { 69 | SettingsHelper.mergeProperties(this, sources) 70 | } 71 | 72 | static final ShellSettings DEFAULT = new ShellSettings.With( 73 | ignoreError: false, 74 | pty: false, 75 | agentForwarding: false, 76 | logging: LoggingMethod.slf4j, 77 | encoding: 'UTF-8', 78 | timeoutSec: 0, 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/BadExitStatusException.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | /** 6 | * An exception class thrown if the remote command returns bad exit status. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | @CompileStatic 11 | class BadExitStatusException extends RuntimeException { 12 | final int exitStatus 13 | 14 | def BadExitStatusException(String message, int exitStatus) { 15 | super(message) 16 | this.exitStatus = exitStatus 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/Session.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import groovy.util.logging.Slf4j 5 | import org.hidetake.groovy.ssh.core.Remote 6 | 7 | /** 8 | * A session. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | @Slf4j 13 | @EqualsAndHashCode 14 | class Session { 15 | 16 | final Remote remote 17 | final Closure closure 18 | 19 | def Session(Remote remote1, Closure closure1) { 20 | remote = remote1 21 | closure = closure1 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/SessionExtension.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import org.hidetake.groovy.ssh.core.Remote 4 | import org.hidetake.groovy.ssh.core.settings.CompositeSettings 5 | import org.hidetake.groovy.ssh.operation.Operations 6 | import org.hidetake.groovy.ssh.operation.SftpOperations 7 | 8 | /** 9 | * A base trait of session extensions. 10 | * Session extensions must apply this trait. 11 | * 12 | * @author Hidetake Iwata 13 | */ 14 | trait SessionExtension { 15 | /** 16 | * Returns remote host for the current session. 17 | * 18 | * @return the remote host 19 | */ 20 | abstract Remote getRemote() 21 | 22 | /** 23 | * Perform SFTP operations. 24 | * 25 | * @param closure closure for {@link org.hidetake.groovy.ssh.operation.SftpOperations} 26 | * @return result of the closure 27 | */ 28 | abstract def T sftp(@DelegatesTo(SftpOperations) Closure closure) 29 | 30 | /** 31 | * Return the current {@link Operations}. 32 | * Only for DSL extensions, do not use from the script. 33 | */ 34 | abstract Operations getOperations() 35 | 36 | /** 37 | * Return the settings with default, global, per-service and per-remote. 38 | * Only for DSL extensions, do not use from the script. 39 | */ 40 | abstract CompositeSettings getMergedSettings() 41 | } 42 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/SessionExtensions.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import org.hidetake.groovy.ssh.session.execution.* 4 | import org.hidetake.groovy.ssh.session.forwarding.PortForward 5 | import org.hidetake.groovy.ssh.session.transfer.FileGet 6 | import org.hidetake.groovy.ssh.session.transfer.FilePut 7 | import org.hidetake.groovy.ssh.session.transfer.SftpRemove 8 | 9 | import groovy.util.logging.Slf4j 10 | 11 | /** 12 | * A set of extensions to be shipped as default. 13 | * 14 | * @author Hidetake Iwata 15 | */ 16 | @Slf4j 17 | trait SessionExtensions implements Command, BackgroundCommand, Script, 18 | Shell, Sudo, FileGet, FilePut, SftpRemove, PortForward { 19 | 20 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/SessionHandler.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import org.hidetake.groovy.ssh.core.Remote 4 | import org.hidetake.groovy.ssh.core.settings.CompositeSettings 5 | import org.hidetake.groovy.ssh.core.settings.GlobalSettings 6 | import org.hidetake.groovy.ssh.core.settings.PerServiceSettings 7 | import org.hidetake.groovy.ssh.operation.Operations 8 | import org.hidetake.groovy.ssh.operation.SftpOperations 9 | 10 | import groovy.util.logging.Slf4j 11 | 12 | /** 13 | * A handler of {@link org.hidetake.groovy.ssh.core.RunHandler#session(Remote, groovy.lang.Closure)}. 14 | * 15 | * @author Hidetake Iwata 16 | */ 17 | @Slf4j 18 | class SessionHandler implements SessionExtensions { 19 | final Operations operations 20 | 21 | /** 22 | * Settings with default, global, per-service and per-remote. 23 | */ 24 | final CompositeSettings mergedSettings 25 | 26 | static def create(Operations operations, GlobalSettings globalSettings, PerServiceSettings perServiceSettings) { 27 | def handler = new SessionHandler(operations, globalSettings, perServiceSettings) 28 | handler.mergedSettings.extensions.inject(handler) { applied, extension -> 29 | if (extension instanceof Class) { 30 | log.debug("Applying extension: $extension") 31 | applied.withTraits(extension) 32 | } else if (extension instanceof Map) { 33 | extension.each { String name, Closure implementation -> 34 | log.debug("Applying extension method: $name") 35 | applied.metaClass[name] = implementation 36 | } 37 | applied 38 | } else { 39 | log.error("Ignored unknown extension: $extension") 40 | applied 41 | } 42 | } 43 | } 44 | 45 | private def SessionHandler(Operations operations1, GlobalSettings globalSettings, PerServiceSettings perServiceSettings) { 46 | operations = operations1 47 | mergedSettings = new CompositeSettings.With( 48 | CompositeSettings.With.DEFAULT, 49 | globalSettings, 50 | perServiceSettings, 51 | operations.remote) 52 | } 53 | 54 | @Override 55 | Remote getRemote() { 56 | operations.remote 57 | } 58 | 59 | @Override 60 | def T sftp(@DelegatesTo(SftpOperations) Closure closure) { 61 | assert closure, 'closure must be given' 62 | operations.sftp(mergedSettings, closure) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/SessionSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 5 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 6 | 7 | /** 8 | * Settings for {@link org.hidetake.groovy.ssh.session.SessionHandler}s. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | trait SessionSettings { 13 | /** 14 | * Dry-run flag. 15 | */ 16 | Boolean dryRun 17 | 18 | /** 19 | * JSch logging flag. 20 | */ 21 | Boolean jschLog 22 | 23 | /** 24 | * Extensions for {@link org.hidetake.groovy.ssh.session.SessionHandler}. 25 | */ 26 | List extensions = [] 27 | 28 | /** 29 | * Do not show if it is empty or null. 30 | */ 31 | def toString__extensions() { extensions ? extensions : null } 32 | 33 | def plus__extensions(SessionSettings prior) { 34 | assert prior.extensions instanceof List 35 | extensions + prior.extensions 36 | } 37 | 38 | 39 | @EqualsAndHashCode 40 | static class With implements SessionSettings, ToStringProperties { 41 | def With() {} 42 | def With(SessionSettings... sources) { 43 | SettingsHelper.mergeProperties(this, sources) 44 | } 45 | 46 | static final SessionSettings DEFAULT = new SessionSettings.With( 47 | dryRun: false, 48 | jschLog: false, 49 | extensions: [], 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/SessionTask.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import groovy.util.logging.Slf4j 5 | import org.hidetake.groovy.ssh.connection.ConnectionManager 6 | import org.hidetake.groovy.ssh.connection.JSchLogger 7 | import org.hidetake.groovy.ssh.core.settings.CompositeSettings 8 | import org.hidetake.groovy.ssh.core.settings.GlobalSettings 9 | import org.hidetake.groovy.ssh.core.settings.PerServiceSettings 10 | import org.hidetake.groovy.ssh.operation.DefaultOperations 11 | import org.hidetake.groovy.ssh.operation.DryRunOperations 12 | 13 | import java.util.concurrent.Callable 14 | 15 | import static org.hidetake.groovy.ssh.util.Utility.callWithDelegate 16 | 17 | /** 18 | * A session with global and per-service settings. 19 | * 20 | * @author Hidetake Iwata 21 | */ 22 | @Slf4j 23 | @EqualsAndHashCode 24 | class SessionTask implements Callable { 25 | 26 | final Session session 27 | final GlobalSettings globalSettings 28 | final PerServiceSettings perServiceSettings 29 | 30 | def SessionTask(Session session1, GlobalSettings globalSettings1, PerServiceSettings perServiceSettings1) { 31 | session = session1 32 | globalSettings = globalSettings1 33 | perServiceSettings = perServiceSettings1 34 | } 35 | 36 | @Override 37 | def T call() { 38 | log.debug("Using per-remote settings: ${new CompositeSettings.With(session.remote)}") 39 | def settings = new SessionSettings.With( 40 | CompositeSettings.With.DEFAULT, 41 | globalSettings, 42 | perServiceSettings, 43 | session.remote) 44 | JSchLogger.enabledInCurrentThread = settings.jschLog 45 | if (settings.dryRun) { 46 | dryRun() 47 | } else { 48 | wetRun() 49 | } 50 | } 51 | 52 | private T dryRun() { 53 | def operations = new DryRunOperations(session.remote) 54 | def sessionHandler = SessionHandler.create(operations, globalSettings, perServiceSettings) 55 | callWithDelegate(session.closure, sessionHandler) 56 | } 57 | 58 | private T wetRun() { 59 | def manager = new ConnectionManager(globalSettings, perServiceSettings) 60 | try { 61 | def connection = manager.connect(session.remote) 62 | def operations = new DefaultOperations(connection) 63 | def sessionHandler = SessionHandler.create(operations, globalSettings, perServiceSettings) 64 | callWithDelegate(session.closure, sessionHandler) 65 | } finally { 66 | manager.close() 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/BackgroundCommand.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | /** 6 | * Provides the non-blocking command execution. 7 | * Each method returns immediately and executes the commandLine concurrently. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @Slf4j 12 | trait BackgroundCommand implements Command { 13 | @Deprecated 14 | void executeBackground(HashMap map = [:], String commandLine) { 15 | log.warn("Deprecated: executeBackground is no longer supported. Use execute instead.") 16 | execute(map, commandLine) 17 | } 18 | 19 | @Deprecated 20 | void executeBackground(HashMap map = [:], List commandLineArgs) { 21 | log.warn("Deprecated: executeBackground is no longer supported. Use execute instead.") 22 | execute(map, commandLineArgs) 23 | } 24 | 25 | @Deprecated 26 | void executeBackground(HashMap map = [:], String commandLine, Closure callback) { 27 | log.warn("Deprecated: executeBackground is no longer supported. Use execute instead.") 28 | execute(map, commandLine, callback) 29 | } 30 | 31 | @Deprecated 32 | void executeBackground(HashMap map = [:], List commandLineArgs, Closure callback) { 33 | log.warn("Deprecated: executeBackground is no longer supported. Use execute instead.") 34 | execute(map, commandLineArgs, callback) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/Command.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import org.codehaus.groovy.tools.Utilities 4 | import org.hidetake.groovy.ssh.operation.CommandSettings 5 | import org.hidetake.groovy.ssh.operation.Operations 6 | import org.hidetake.groovy.ssh.session.BadExitStatusException 7 | import org.hidetake.groovy.ssh.session.SessionExtension 8 | 9 | import groovy.util.logging.Slf4j 10 | 11 | /** 12 | * Provides the blocking command execution. 13 | * Each method blocks until channel is closed. 14 | * 15 | * @author Hidetake Iwata 16 | */ 17 | @Slf4j 18 | trait Command implements SessionExtension { 19 | void execute(HashMap map = [:], String commandLine, Closure callback) { 20 | assert callback, 'callback must be given' 21 | callback.call(execute(map, commandLine)) 22 | } 23 | 24 | void execute(HashMap map = [:], List commandLineArgs, Closure callback) { 25 | assert callback, 'callback must be given' 26 | callback.call(execute(map, commandLineArgs)) 27 | } 28 | 29 | String execute(HashMap map = [:], String commandLine) { 30 | assert commandLine, 'commandLine must be given' 31 | assert map != null, 'map must not be null' 32 | def settings = new CommandSettings.With(mergedSettings, new CommandSettings.With(map)) 33 | Helper.execute(operations, settings, commandLine) 34 | } 35 | 36 | String execute(HashMap map = [:], List commandLineArgs) { 37 | assert commandLineArgs, 'commandLineArgs must be given' 38 | assert map != null, 'map must not be null' 39 | execute(map, Escape.escape(commandLineArgs)) 40 | } 41 | 42 | static class Helper { 43 | static execute(Operations operations, CommandSettings settings, String commandLine) { 44 | def operation = operations.command(settings, commandLine) 45 | 46 | def lines = [] as List 47 | operation.addInteraction { 48 | when(line: _, from: standardOutput) { String line -> 49 | lines.add(line) 50 | } 51 | } 52 | 53 | def exitStatus = operation.execute() 54 | if (exitStatus != 0 && !settings.ignoreError) { 55 | throw new BadExitStatusException("Command returned exit status $exitStatus: $commandLine", exitStatus) 56 | } 57 | lines.join(Utilities.eol()) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/Escape.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | /** 4 | * Shell escape utility. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | class Escape { 9 | 10 | /** 11 | * Escape command arguments. 12 | * This method quotes each argument with single-quote. 13 | * @param arguments 14 | * @return 15 | */ 16 | static String escape(List arguments) { 17 | arguments.collect { /'${it.replaceAll(~/'/, /'\\''/)}'/ }.join(/ /) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/Script.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | /** 6 | * An extension class for script execution. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | @Slf4j 11 | trait Script implements Command { 12 | 13 | String executeScript(HashMap settings = [:], String script) { 14 | assert script, 'script must be given' 15 | execute(Helper.createSettings(settings, script), Helper.guessInterpreter(script)) 16 | } 17 | 18 | void executeScript(HashMap settings = [:], String script, Closure callback) { 19 | assert script, 'script must be given' 20 | execute(Helper.createSettings(settings, script), Helper.guessInterpreter(script), callback) 21 | } 22 | 23 | String executeScript(HashMap settings = [:], File script) { 24 | assert script, 'script must be given' 25 | execute(Helper.createSettings(settings, script), Helper.guessInterpreter(script)) 26 | } 27 | 28 | void executeScript(HashMap settings = [:], File script, Closure callback) { 29 | assert script, 'script must be given' 30 | execute(Helper.createSettings(settings, script), Helper.guessInterpreter(script), callback) 31 | } 32 | 33 | static class Helper { 34 | static HashMap createSettings(HashMap settings, def script) { 35 | if (settings.inputStream) { 36 | throw new IllegalArgumentException("executeScript does not work with inputStream: $settings") 37 | } 38 | [:] << settings << [inputStream: script] as HashMap 39 | } 40 | 41 | static String guessInterpreter(String script) { 42 | script.find(~/^#!.+/) { m -> m.substring(2) } ?: '/bin/sh' 43 | } 44 | 45 | static String guessInterpreter(File script) { 46 | script.withReader { reader -> 47 | reader.readLine().find(~/^#!.+/) { m -> m.substring(2) } 48 | } ?: '/bin/sh' 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/Shell.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import org.hidetake.groovy.ssh.operation.ShellSettings 4 | import org.hidetake.groovy.ssh.session.BadExitStatusException 5 | import org.hidetake.groovy.ssh.session.SessionExtension 6 | 7 | /** 8 | * Provides the shell execution. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | trait Shell implements SessionExtension { 13 | /** 14 | * Performs a shell operation. 15 | * This method blocks until channel is closed. 16 | * 17 | * @param map shell settings 18 | * @return output value of the command 19 | */ 20 | void shell(HashMap map) { 21 | assert map != null, 'map must not be null' 22 | 23 | def settings = new ShellSettings.With(mergedSettings, new ShellSettings.With(map)) 24 | def operation = operations.shell(settings) 25 | 26 | def exitStatus = operation.execute() 27 | if (exitStatus != 0 && !settings.ignoreError) { 28 | throw new BadExitStatusException("Shell returned exit status $exitStatus", exitStatus) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/Sudo.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import org.hidetake.groovy.ssh.session.SessionExtension 4 | 5 | /** 6 | * An extension class of sudo command execution. 7 | * Each method performs a sudo operation, explicitly providing password for the sudo user. 8 | * It blocks until channel is closed. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | trait Sudo implements SessionExtension { 13 | 14 | String executeSudo(HashMap settings = [:], String commandLine) { 15 | assert commandLine, 'commandLine must be given' 16 | assert settings != null, 'settings must not be null' 17 | def helper = new SudoHelper(operations, mergedSettings, new SudoHelper.SudoCommandSettings(settings)) 18 | helper.execute(commandLine) 19 | } 20 | 21 | String executeSudo(HashMap settings = [:], List commandLineArgs) { 22 | executeSudo(settings, Escape.escape(commandLineArgs)) 23 | } 24 | 25 | void executeSudo(HashMap settings = [:], String commandLine, Closure callback) { 26 | assert commandLine, 'commandLine must be given' 27 | assert callback, 'callback must be given' 28 | assert settings != null, 'settings must not be null' 29 | callback.call(executeSudo(settings, commandLine)) 30 | } 31 | 32 | void executeSudo(HashMap settings = [:], List commandLineArgs, Closure callback) { 33 | executeSudo(settings, Escape.escape(commandLineArgs), callback) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/SudoException.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import groovy.transform.CompileStatic 4 | import org.hidetake.groovy.ssh.core.Remote 5 | 6 | /** 7 | * An exception class thrown if sudo authentication failed. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @CompileStatic 12 | class SudoException extends RuntimeException { 13 | 14 | final Remote remote 15 | 16 | def SudoException(Remote remote, String message) { 17 | super("Failed sudo authentication on $remote: $message") 18 | this.remote = remote 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/execution/SudoSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 5 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 6 | 7 | trait SudoSettings { 8 | /** 9 | * Sudo password. 10 | */ 11 | String sudoPassword 12 | 13 | /** 14 | * Sudo executable path. 15 | */ 16 | String sudoPath 17 | 18 | /** 19 | * An input stream to send to the standard input. 20 | * @see org.hidetake.groovy.ssh.operation.CommandSettings#inputStream 21 | */ 22 | def inputStream 23 | 24 | 25 | @EqualsAndHashCode 26 | static class With implements SudoSettings, ToStringProperties { 27 | def With() {} 28 | def With(SudoSettings... sources) { 29 | SettingsHelper.mergeProperties(this, sources) 30 | } 31 | 32 | static final SudoSettings DEFAULT = new SudoSettings.With( 33 | sudoPassword: null, 34 | sudoPath: 'sudo', 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/forwarding/LocalPortForwardSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.forwarding 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 5 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 6 | 7 | /** 8 | * Settings for the local port forwarding. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | trait LocalPortForwardSettings { 13 | /** 14 | * Local port to bind. Defaults to 0 (allocate free port). 15 | */ 16 | Integer port 17 | 18 | /** 19 | * Local host to bind. Defaults to localhost. 20 | */ 21 | String bind 22 | 23 | /** 24 | * Remote port to connect. (Mandatory) 25 | */ 26 | Integer hostPort 27 | 28 | /** 29 | * Remote host to connect. Default to localhost of the remote host. 30 | */ 31 | String host 32 | 33 | 34 | @EqualsAndHashCode 35 | static class With implements LocalPortForwardSettings, ToStringProperties { 36 | def With() {} 37 | def With(LocalPortForwardSettings... sources) { 38 | SettingsHelper.mergeProperties(this, sources) 39 | } 40 | 41 | static final DEFAULT = new With(port: 0, bind: '127.0.0.1', host: '127.0.0.1') 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/forwarding/PortForward.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.forwarding 2 | 3 | import org.hidetake.groovy.ssh.session.SessionExtension 4 | 5 | /** 6 | * An extension class of port forwarding. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | trait PortForward implements SessionExtension { 11 | 12 | /** 13 | * Forwards local port to remote port. 14 | * 15 | * @param settings {@see LocalPortForwardingSettings} 16 | * @return local port 17 | */ 18 | int forwardLocalPort(HashMap settings) { 19 | assert settings != null, 'settings must not be null' 20 | def merged = new LocalPortForwardSettings.With(LocalPortForwardSettings.With.DEFAULT, new LocalPortForwardSettings.With(settings)) 21 | assert merged.hostPort, 'remote port must be given' 22 | operations.forwardLocalPort(merged) 23 | } 24 | 25 | /** 26 | * Forwards remote port to local port. 27 | * 28 | * @param settings {@see RemotePortForwardingSettings} 29 | */ 30 | void forwardRemotePort(HashMap settings) { 31 | assert settings != null, 'settings must not be null' 32 | def merged = new RemotePortForwardSettings.With(RemotePortForwardSettings.With.DEFAULT, new RemotePortForwardSettings.With(settings)) 33 | assert merged.hostPort, 'local port must be given' 34 | assert merged.port, 'remote port must be given' 35 | operations.forwardRemotePort(merged) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/forwarding/RemotePortForwardSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.forwarding 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 5 | import org.hidetake.groovy.ssh.core.settings.ToStringProperties 6 | 7 | /** 8 | * Settings for the remote port forwarding. 9 | * 10 | * @author Hidetake Iwata 11 | */ 12 | trait RemotePortForwardSettings { 13 | /** 14 | * Local port to connect. (Mandatory) 15 | */ 16 | Integer hostPort 17 | 18 | /** 19 | * Local host to connect. Defaults to localhost. 20 | */ 21 | String host 22 | 23 | /** 24 | * Remote port to bind. (Mandatory) 25 | */ 26 | Integer port 27 | 28 | /** 29 | * Remote host to bind. Default to localhost of the remote host. 30 | */ 31 | String bind 32 | 33 | 34 | @EqualsAndHashCode 35 | static class With implements RemotePortForwardSettings, ToStringProperties { 36 | def With() {} 37 | def With(RemotePortForwardSettings... sources) { 38 | SettingsHelper.mergeProperties(this, sources) 39 | } 40 | 41 | static final DEFAULT = new With(bind: '127.0.0.1', host: '127.0.0.1') 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/FileTransferMethod.java: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer; 2 | 3 | /** 4 | * File transfer method type. 5 | * Implemented as Java native enum for Gradle 1.x compatibility. 6 | * 7 | * @author Hidetake Iwata 8 | */ 9 | public enum FileTransferMethod { 10 | /** 11 | * Transfer via SFTP channel 12 | */ 13 | sftp, 14 | 15 | /** 16 | * Transfer via SCP command 17 | */ 18 | scp 19 | } 20 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/FileTransferSettings.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import org.hidetake.groovy.ssh.core.settings.SettingsHelper 5 | 6 | trait FileTransferSettings { 7 | 8 | /** 9 | * File transfer method such as SFTP or SCP. 10 | */ 11 | FileTransferMethod fileTransfer 12 | 13 | /** 14 | * Timeout for the SFTP or command channel to be connected in seconds. 15 | * @see org.hidetake.groovy.ssh.connection.ConnectionSettings#timeoutSec 16 | */ 17 | Integer timeoutSec 18 | 19 | 20 | @EqualsAndHashCode 21 | static class With implements FileTransferSettings { 22 | def With() {} 23 | def With(FileTransferSettings... sources) { 24 | SettingsHelper.mergeProperties(this, sources) 25 | } 26 | 27 | static FileTransferSettings DEFAULT = new FileTransferSettings.With( 28 | fileTransfer: FileTransferMethod.sftp, 29 | timeoutSec: 0, 30 | ) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/get/FileReceiver.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.get 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | @Slf4j 6 | class FileReceiver implements WritableReceiver { 7 | 8 | final File destination 9 | 10 | @Lazy 11 | private recreateAtFirst = { 12 | if (destination.exists()) { 13 | destination.delete() 14 | } 15 | destination.withWriter { writer -> writer.flush() } 16 | ({}) 17 | }() 18 | 19 | def FileReceiver(File destination1) { 20 | destination = destination1 21 | assert !destination.directory 22 | } 23 | 24 | @Override 25 | void write(byte[] bytes) { 26 | recreateAtFirst() 27 | log.trace("Writing $bytes.length bytes into file: $destination") 28 | destination.append(bytes) 29 | } 30 | 31 | @Override 32 | String toString() { 33 | destination.path 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/get/Provider.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.get 2 | /** 3 | * An interface of file GET provider. 4 | * 5 | * @author Hidetake Iwata 6 | */ 7 | interface Provider { 8 | 9 | void get(String remotePath, RecursiveReceiver receiver) 10 | 11 | void get(String remotePath, FileReceiver receiver) 12 | 13 | void get(String remotePath, StreamReceiver receiver) 14 | 15 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/get/RecursiveReceiver.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.get 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | @Slf4j 6 | class RecursiveReceiver { 7 | 8 | final File destination 9 | 10 | private final Closure filter 11 | private final ArrayDeque directoryStack 12 | 13 | def RecursiveReceiver(File destination1, Closure filter1) { 14 | destination = destination1 15 | filter = filter1 16 | directoryStack = [destination] 17 | assert destination.directory 18 | } 19 | 20 | /** 21 | * Called when the remote file is found. 22 | * 23 | * @param name 24 | * @return file 25 | */ 26 | File createFile(String name) { 27 | def directory = directoryStack.getLast() 28 | def file = new File(directory, name) 29 | if (!filter || filter.call(file)) { 30 | if (!directory.exists()) { 31 | directory.mkdirs() 32 | } else if (file.exists()) { 33 | file.delete() 34 | } 35 | file.createNewFile() 36 | file 37 | } else { 38 | null 39 | } 40 | } 41 | 42 | /** 43 | * Called when it entered into the remote directory. 44 | * 45 | * @param name 46 | */ 47 | void enterDirectory(String name) { 48 | def directory = new File(directoryStack.getLast(), name) 49 | directoryStack.addLast(directory) 50 | if (!filter) { 51 | directory.mkdirs() 52 | } 53 | } 54 | 55 | /** 56 | * Called when it left from the remote directory. 57 | */ 58 | void leaveDirectory() { 59 | directoryStack.removeLast() 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/get/ScpException.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.get 2 | 3 | /** 4 | * Represents SCP error. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | class ScpException extends IOException { 9 | def ScpException(String message) { 10 | super(message) 11 | } 12 | 13 | def ScpException(String message, Throwable cause) { 14 | super(message, cause) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/get/StreamReceiver.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.get 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | @Slf4j 6 | class StreamReceiver implements WritableReceiver { 7 | 8 | final OutputStream stream 9 | 10 | def StreamReceiver(OutputStream stream1) { 11 | stream = stream1 12 | } 13 | 14 | @Override 15 | void write(byte[] bytes) { 16 | log.trace("Writing $bytes.length bytes into the stream") 17 | stream.write(bytes) 18 | } 19 | 20 | @Override 21 | String toString() { 22 | stream.class.simpleName 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/get/WritableReceiver.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.get 2 | 3 | interface WritableReceiver { 4 | 5 | void write(byte[] bytes) 6 | 7 | } 8 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/put/EnterDirectory.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.put 2 | 3 | import groovy.transform.ToString 4 | 5 | @ToString 6 | class EnterDirectory { 7 | final String name 8 | 9 | def EnterDirectory(String name1) { 10 | name = name1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/put/LeaveDirectory.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.put 2 | 3 | import groovy.transform.ToString 4 | 5 | @Singleton 6 | @ToString 7 | class LeaveDirectory { 8 | } 9 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/put/Provider.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.put 2 | 3 | /** 4 | * An interface of file PUT provider. 5 | * 6 | * @author Hidetake Iwata 7 | */ 8 | interface Provider { 9 | 10 | void put(Instructions instructions) 11 | 12 | } -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/session/transfer/put/StreamContent.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.transfer.put 2 | 3 | import groovy.transform.ToString 4 | 5 | @ToString 6 | class StreamContent { 7 | final String name 8 | final InputStream stream 9 | 10 | def StreamContent(String name1, InputStream stream1) { 11 | name = name1 12 | stream = stream1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/util/ManagedBlocking.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.util 2 | 3 | import java.util.concurrent.ForkJoinPool 4 | 5 | /** 6 | * A convenient class of {@link ForkJoinPool.ManagedBlocker}. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class ManagedBlocking { 11 | /** 12 | * Wait until condition is satisfied. 13 | * 14 | * @param intervalMillis polling interval 15 | * @param condition returns true if polling is completed 16 | */ 17 | static void until(long intervalMillis = 100L, Closure condition) { 18 | ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker() { 19 | @Override 20 | boolean block() throws InterruptedException { 21 | if (!isReleasable()) { 22 | sleep(intervalMillis) 23 | } 24 | isReleasable() 25 | } 26 | 27 | @Override 28 | boolean isReleasable() { 29 | condition.call() 30 | } 31 | }) 32 | } 33 | 34 | /** 35 | * Wait given time. 36 | * @param millis 37 | */ 38 | static void sleep(long millis) { 39 | def started = System.currentTimeMillis() 40 | until { 41 | (System.currentTimeMillis() - started) > millis 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/groovy/org/hidetake/groovy/ssh/util/Utility.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.util 2 | 3 | import com.jcraft.jsch.JSchException 4 | import groovy.util.logging.Slf4j 5 | 6 | /** 7 | * Provides utility methods. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @Slf4j 12 | class Utility { 13 | static T callWithDelegate(Closure closure, delegate, ... arguments) { 14 | def cloned = closure.clone() as Closure 15 | cloned.resolveStrategy = Closure.DELEGATE_FIRST 16 | cloned.delegate = delegate 17 | cloned.call(*arguments) 18 | } 19 | 20 | /** 21 | * Curry a method with self for recursive. 22 | * 23 | * @param closure 24 | * @return curried closure 25 | */ 26 | static Closure currySelf(Closure closure) { 27 | def curried 28 | curried = closure.curry { 29 | closure.call(curried) 30 | } 31 | } 32 | 33 | /** 34 | * Execute the closure with retrying. 35 | * This method catches only {@link com.jcraft.jsch.JSchException}s. 36 | * 37 | * @param retryCount 38 | * @param retryWaitSec 39 | * @param closure 40 | */ 41 | static T retry(int retryCount, int retryWaitSec, Closure closure) { 42 | assert closure != null, 'closure should be set' 43 | if (retryCount > 0) { 44 | try { 45 | closure() 46 | } catch (JSchException e) { 47 | log.warn("Retrying: ${e.getClass().name}: ${e.localizedMessage}") 48 | ManagedBlocking.sleep(retryWaitSec * 1000L) 49 | retry(retryCount - 1, retryWaitSec, closure) 50 | } 51 | } else { 52 | closure() 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/main/resources/org/hidetake/groovy/ssh/Release.properties: -------------------------------------------------------------------------------- 1 | product.name=groovy-ssh 2 | product.version=@version@ 3 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/SshClassSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh 2 | 3 | import spock.lang.Specification 4 | 5 | class SshClassSpec extends Specification { 6 | 7 | def "version property should be the product version"() { 8 | expect: 9 | Ssh.release.version.matches(/@version@|SNAPSHOT|[0-9\.]+/) 10 | } 11 | 12 | def "name property should be the product name"() { 13 | expect: 14 | !Ssh.release.name.empty 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/connection/HostKeyRepositorySpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.connection 2 | 3 | import com.jcraft.jsch.HostKey 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | class HostKeyRepositorySpec extends Specification { 8 | 9 | @Unroll 10 | def "comparing of JSch HostKey and raw host and port"() { 11 | given: 12 | def hostKey = new HostKey(jhost, HostKey.SSHRSA ,null) 13 | 14 | expect: 15 | HostKeyRepository.compare( hostKey, host, port ) == expected 16 | 17 | where: 18 | jhost | host | port || expected 19 | 'simple' | 'simple' | 22 || true 20 | 'notsimple' | 'simple' | 22 || false 21 | '[simple]:4321' | 'simple' | 4321 || true 22 | 'simple,[notsimple]:4321' | 'simple' | 22 || true 23 | 'simple,[notsimple]:4321' | 'simple' | 4321 || false 24 | 'simple,[notsimple]:4321' | 'notsimple' | 4321 || true 25 | 'simple,[notsimple]:4321,more' | 'more' | 22 || true 26 | 'simple,[notsimple]:4321,more' | 'notsimple' | 4321 || true 27 | 28 | '|1|c2FsdA==|Ip5vCJkMOpZGFriXFS4Jiw1khnY=' | 'hashme' | 22 || true 29 | '|1|c2FsdA==|Ip5vCJkMOpZGFriXFS4Jiw1khnY=' | 'hashme' | 4321 || false 30 | '|1|c2FsdHk=|4/+CSFSbcjz5uEJcO5B77hM70RM=' | 'hashmeport' | 4321 || true 31 | '|1|c2FsdHk=|4/+CSFSbcjz5uEJcO5B77hM70RM=' | 'hashmeport' | 22 || false 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/core/ProxySpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core 2 | 3 | import spock.lang.Specification 4 | 5 | import static org.hidetake.groovy.ssh.core.ProxyType.SOCKS 6 | 7 | class ProxySpec extends Specification { 8 | 9 | def "result of toString() does not contain password"() { 10 | given: 11 | def proxy = new Proxy('theProxy') 12 | proxy.host = 'theHost' 13 | proxy.user = 'theUser' 14 | proxy.password = 'thePassword' 15 | proxy.type = SOCKS 16 | 17 | when: 18 | def result = proxy.toString() 19 | 20 | then: 21 | !result.contains('thePassword') 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/core/RemoteSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.core 2 | 3 | import spock.lang.Specification 4 | 5 | class RemoteSpec extends Specification { 6 | 7 | def "remote should be instantiated with settings"() { 8 | given: 9 | def remote = new Remote(name: 'testServer', user: 'admin', host: '1.2.3.4') 10 | 11 | expect: 12 | remote.name == 'testServer' 13 | remote.user == 'admin' 14 | remote.host == '1.2.3.4' 15 | } 16 | 17 | def "name should be generated if name of settings is not given"() { 18 | given: 19 | def remote = new Remote(user: 'admin', host: '1.2.3.4') 20 | 21 | expect: 22 | remote.name =~ /^Remote\d+$/ 23 | remote.user == 'admin' 24 | remote.host == '1.2.3.4' 25 | } 26 | 27 | def "result of toString() does not contain any credential"() { 28 | given: 29 | def remote = new Remote('theRemote') 30 | remote.user = 'theUser' 31 | remote.password = 'thePassword' 32 | remote.identity = new File('theIdentity') 33 | remote.passphrase = 'thePassphrase' 34 | 35 | when: 36 | def result = remote.toString() 37 | 38 | then: 39 | !result.contains('thePassword') 40 | !result.contains('thePassphrase') 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/interaction/InteractionHandlerSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.interaction 2 | 3 | import spock.lang.Specification 4 | 5 | class InteractionHandlerSpec extends Specification { 6 | 7 | def 'property _ should be the wildcard'() { 8 | expect: 9 | InteractionHandler._ instanceof Wildcard 10 | } 11 | 12 | def 'property standardOutput should be a stream kind'() { 13 | expect: 14 | InteractionHandler.standardOutput instanceof Stream 15 | } 16 | 17 | def 'property standardError should be a stream kind'() { 18 | expect: 19 | InteractionHandler.standardError instanceof Stream 20 | } 21 | 22 | def 'property standardInput should be an output stream given by constructor'() { 23 | given: 24 | def standardInputMock = Mock(OutputStream) 25 | def interactionHandler = new InteractionHandler(standardInputMock) 26 | 27 | expect: 28 | interactionHandler.standardInput == standardInputMock 29 | } 30 | 31 | def 'rules should be empty at first'() { 32 | given: 33 | def interactionHandler = new InteractionHandler(Mock(OutputStream)) 34 | 35 | expect: 36 | interactionHandler.when == [] 37 | } 38 | 39 | def 'when() should add an interaction rule'() { 40 | given: 41 | def interactionHandler = new InteractionHandler(Mock(OutputStream)) 42 | 43 | when: 44 | interactionHandler.when(line: 'value') {} 45 | 46 | then: 47 | interactionHandler.when.size() == 1 48 | interactionHandler.when[0].condition == [line: 'value'] 49 | } 50 | 51 | def 'when() should add interaction rules'() { 52 | given: 53 | def interactionHandler = new InteractionHandler(Mock(OutputStream)) 54 | 55 | when: 56 | interactionHandler.when(line: 'value1') {} 57 | interactionHandler.when(partial: 'value3') {} 58 | 59 | then: 60 | interactionHandler.when.size() == 2 61 | interactionHandler.when[0].condition == [line: 'value1'] 62 | interactionHandler.when[1].condition == [partial: 'value3'] 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/session/SessionHandlerSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import org.hidetake.groovy.ssh.core.settings.GlobalSettings 4 | import org.hidetake.groovy.ssh.core.settings.PerServiceSettings 5 | import org.hidetake.groovy.ssh.operation.Operations 6 | import spock.lang.Specification 7 | 8 | class SessionHandlerSpec extends Specification { 9 | 10 | def defaultSessionHandler 11 | Operations operations 12 | 13 | def setup() { 14 | operations = Mock(Operations) 15 | defaultSessionHandler = SessionHandler.create(operations, new GlobalSettings(), new PerServiceSettings()) 16 | } 17 | 18 | def "sftp should return value of the closure"() { 19 | given: 20 | def closure = Mock(Closure) 21 | 22 | when: 23 | def result = defaultSessionHandler.with { 24 | sftp(closure) 25 | } 26 | 27 | then: 28 | 1 * operations.sftp(_, closure) >> 'something' 29 | 30 | then: 31 | result == 'something' 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/session/SessionSettingsSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class SessionSettingsSpec extends Specification { 7 | 8 | @Unroll 9 | def "plus should be merge extensions as list"() { 10 | given: 11 | def settings1 = new SessionSettings.With(extensions: extensions1) 12 | def settings2 = new SessionSettings.With(extensions: extensions2) 13 | 14 | when: 15 | def settings = new SessionSettings.With(settings1, settings2) 16 | 17 | then: 18 | settings.extensions == expected 19 | 20 | where: 21 | extensions1 | extensions2 | expected 22 | [] | [] | [] 23 | ['a'] | [] | ['a'] 24 | [] | ['b'] | ['b'] 25 | ['a'] | ['b'] | ['a', 'b'] 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /groovy-ssh/core/src/test/groovy/org/hidetake/groovy/ssh/session/execution/EscapeSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.session.execution 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class EscapeSpec extends Specification { 7 | 8 | @Unroll 9 | def "escape() should return escaped string when #args"() { 10 | when: 11 | def escaped = Escape.escape(args) 12 | 13 | then: 14 | escaped == expected 15 | 16 | where: 17 | args | expected 18 | [] | '' 19 | [''] | /''/ 20 | [/hello/, /!"#$%&'()*+,-.\/:;<=>?@[\]^_`{|}~/] | /'hello' '!"#$%&'\''()*+,-.\/:;<=>?@[\]^_`{|}~'/ 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /groovy-ssh/docs/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.asciidoctor.jvm.convert' version '4.0.4' 3 | } 4 | 5 | asciidoctor { 6 | sources { 7 | include 'index.adoc' 8 | include 'example-script.adoc' 9 | } 10 | } -------------------------------------------------------------------------------- /groovy-ssh/docs/src/docs/asciidoc/example-script.adoc: -------------------------------------------------------------------------------- 1 | = Example 2 | :doctype: article 3 | :source-highlighter: coderay 4 | 5 | 6 | include::version-loader.adoc[] 7 | 8 | 9 | [source,groovy,subs="+attributes"] 10 | ---- 11 | // build.gradle 12 | 13 | plugins { 14 | id 'org.hidetake.ssh' version '{gradle-ssh-version}' 15 | } 16 | 17 | remotes { 18 | webServer { 19 | host = '192.168.1.101' 20 | user = 'jenkins' 21 | identity = file('id_rsa') 22 | } 23 | } 24 | 25 | task deploy { 26 | doLast { 27 | ssh.run { 28 | session(remotes.webServer) { 29 | put from: 'example.war', into: '/webapps' 30 | execute 'sudo service tomcat restart' 31 | } 32 | } 33 | } 34 | } 35 | ---- 36 | -------------------------------------------------------------------------------- /groovy-ssh/docs/src/docs/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Gradle SSH Plugin Document 2 | Hidetake Iwata 3 | :doctype: book 4 | :source-highlighter: coderay 5 | :toc: right 6 | :sectnums: 7 | :sectanchors: 8 | :icons: font 9 | 10 | 11 | include::version-loader.adoc[] 12 | 13 | 14 | This document explains Gradle SSH Plugin {gradle-ssh-version} and Groovy SSH {groovy-ssh-version}. 15 | 16 | 17 | include::introduction.adoc[] 18 | 19 | include::getting-started.adoc[] 20 | 21 | include::user-guide.adoc[] 22 | 23 | include::migration-guide.adoc[] 24 | 25 | 26 | ++++ 27 | 28 | ++++ 29 | -------------------------------------------------------------------------------- /groovy-ssh/docs/src/docs/asciidoc/introduction.adoc: -------------------------------------------------------------------------------- 1 | = Introduction 2 | 3 | == What is Gradle SSH Plugin 4 | 5 | Gradle SSH Plugin is a https://gradle.org/[Gradle] plugin which provides SSH facilities such as command execution or file transfer for continuous delivery. 6 | It internally uses the library of Groovy SSH. 7 | 8 | 9 | == What is Groovy SSH 10 | 11 | Groovy SSH is an automation tool which provides SSH facilities such as command execution or file transfer. 12 | It is provided as the executable JAR `gssh.jar` and the library `groovy-ssh-{groovy-ssh-version}.jar`. 13 | -------------------------------------------------------------------------------- /groovy-ssh/docs/src/docs/asciidoc/version-loader.adoc: -------------------------------------------------------------------------------- 1 | :groovy-ssh-version: pass:[x.y.z] 2 | :gradle-ssh-version: pass:[x.y.z] 3 | 4 | ++++ 5 | 6 | 15 | ++++ 16 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/bin/.gitignore: -------------------------------------------------------------------------------- 1 | /test/ 2 | /main/ 3 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation project(':groovy-ssh:core') 11 | 12 | implementation 'junit:junit:4.13.2' 13 | implementation 'org.spockframework:spock-core:2.3-groovy-3.0' 14 | } 15 | 16 | test { 17 | mustRunAfter ':groovy-ssh:server-integration-test:check' 18 | } 19 | 20 | task startSshAgent(type: Exec) { 21 | commandLine 'ssh-agent' 22 | standardOutput = new ByteArrayOutputStream() 23 | doLast { 24 | standardOutput.toString().eachMatch(~/(.+?)=(.+?);/) { all, k, v -> 25 | assert k in ['SSH_AGENT_PID', 'SSH_AUTH_SOCK'] 26 | [test, stopSshAgent]*.environment(k, v) 27 | } 28 | } 29 | } 30 | 31 | task stopSshAgent(type: Exec) { 32 | commandLine 'ssh-agent', '-k' 33 | } 34 | 35 | test.dependsOn startSshAgent 36 | test.finalizedBy stopSshAgent 37 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/id_ecdsa: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIIBaAIBAQQg2d776WhdQFfLXan9bhU/bcSaf6HZAv27Js851vDZMImggfowgfcC 3 | AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// 4 | MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr 5 | vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE 6 | axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W 7 | K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 8 | YyVRAgEBoUQDQgAESZT7K4plmvEATrWFhPWybRw4ptSJ7l3rXNc/Dh6GBaLDJli0 9 | SYf6kOaH8xl/EHpbxBTE6P8KVJga1uesO4WbVg== 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/id_ecdsa.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmU+yuKZZrxAE61hYT1sm0cOKbUie5d61zXPw4ehgWiwyZYtEmH+pDmh/MZfxB6W8QUxOj/ClSYGtbnrDuFm1Y= 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA78p/+WebOE48aQkyU9HLCezHgEXIVs6aXKH0rgBmlnmNIEf7 3 | y8TWdQ7qQT1esexhmGSpCj/p9iSfeWTv0wG7T0H66toUtuB7ZlV3iEDYXSeYBYKZ 4 | b+cABDhl9bfFcuLR5XDBrTGKfibyLqfGIh1uronPUDGhOf90Ng6G5ZQEK9e8g4BF 5 | PXGrUe9BSqorHppTlGmHPSkFmt4GfHeqx7X2gCNR5yulO6x/Wc15no8r0rejYU+4 6 | kNsiTBdBzqXUJpw6sSa+Yznlq4H9E28FtADd0166ixaVHctgwlhdFxDDmi2YSDTF 7 | dcsUoWAFM4am5z9L6CqCr/CGkhv4bSSgFSF5/QIDAQABAoIBAFKaP1t7BU1wJf9I 8 | 271kF71jg5X8c/bzVNl0MQV/vdc4KBVmtqaLOBU6/hdbPLOt6jDE/DY7rizMkOMQ 9 | kkzt28iBwh4E4f3ddqTZ7ENTkzUD3qqHQrP5r1fE1dq/Y5Uf7Y5MOWugFUU/xU2t 10 | HePCn84gSvolHpUMGsxEVNPhGU7AZ8RBXLYi27ha3OWCqusDJJbQEYRO1xm+SV2t 11 | OzybhEEwYTwmNkPC/1pNyRu3zoG1h+tGR2t1yOK1n35ygRiL0lxDHmJ19sRxJ1vn 12 | jKAKU8hnjG1L/uQxVpleOt5TQhIq7wWLL7jO+EMYu5w+iptHGeRs+j/15jY3LFus 13 | V/3v+UECgYEA/MEKUzgl34YN8jMBS3/rzvp+C/4U0nUMyM5+SLa4slYxtL5QbLLu 14 | dPXUTlFSXxUa8OypzM0HqyyWzvXFiQ05Te63wS83rds9/ox6WHAfcwFnkJLIuqCR 15 | N2dNbD0fLV2ixtjNJLsPmVL3upY9/rSYLz9uvw/7/nW/O3AzpVaSbssCgYEA8t7X 16 | i21C4oeWUXJhYnVWWMNqNSoHXsaBMcA5U82aA24cCpoqqRcPpkVYBOlwksGOzJKa 17 | 0rUpaiUknoaqO17o4Q5kKfQYbfGPJ5BnED2iDeVufbyEgfLK0lQX9LNR7Cxtxos6 18 | wSkOvyNllauu6CHCoYSM+EZTiCekAv0GKlgkGVcCgYB9skLAQBwVnUUyPctXELbk 19 | qA4nSKRyRWOmOYrz/mq7xcHScRLt+846vEZo7GhagNR1HD0VbKFzrykQo4kpLzpg 20 | V2dq22CFRZL/FD2D3b7GItyuOVE5/sA5HVaTjZIDrZ1V5lue+Kg5R9mLIUyTbpyA 21 | YrtgqUJYuZXwqUwF3ZfVIQKBgCpxcR+nl4G5Cjbvkz8+nDlk5SGnV6Rjcl58ZkhT 22 | 7O9ehb4AlSX5pr167tfk58xt0QPFNxNNn5AyL4UYqZU4j+AMwMpoIwDLryXN4YUA 23 | EFr3VmjY0htXj8RT99/GmrF4TjLdUAZDo5UZnX4bg7SDedz6KhyVRbHMo6f2CebK 24 | gnx/AoGBAPMFUEa5Tgq7V4XgW6PLHA3SfXj7yNrE72V3aAVkVgkuGAbiOH08cOOF 25 | j9GBq+5NyawyVmVeOe9c/vZG8tPeEFl4550x7n69iCfl7Jaraf1JJF9aEDZprIqE 26 | w6LAtQVE3oH8HvbUEx6dNTMwo5e82tVei8PS1ez6qPfB9CiEns7S 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDvyn/5Z5s4TjxpCTJT0csJ7MeARchWzppcofSuAGaWeY0gR/vLxNZ1DupBPV6x7GGYZKkKP+n2JJ95ZO/TAbtPQfrq2hS24HtmVXeIQNhdJ5gFgplv5wAEOGX1t8Vy4tHlcMGtMYp+JvIup8YiHW6uic9QMaE5/3Q2DobllAQr17yDgEU9catR70FKqisemlOUaYc9KQWa3gZ8d6rHtfaAI1HnK6U7rH9ZzXmejyvSt6NhT7iQ2yJMF0HOpdQmnDqxJr5jOeWrgf0TbwW0AN3TXrqLFpUdy2DCWF0XEMOaLZhINMV1yxShYAUzhqbnP0voKoKv8IaSG/htJKAVIXn9 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/id_rsa_pass: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,849C5178C9A9417A297428134CEAA4A5 4 | 5 | WhtJT/g5q1Sz27hvJXOa9RWRakWYj3Ndi2/dpxWdXaWxoNaZkJr3xxqeegG797ze 6 | gLlQlPFZRJU9KGS3+JxLxV9lyRr16hHllWtg1gOOMniYMafoabK2lxOYzLsc2jUq 7 | 5B1XqKDgwHfI9Jg1dCvZsyac+c6vv7BkzrwVVZj92C4IZl/YtmWKkNXOEbyJKNoq 8 | ONGoHV3DhVQbTK1qELMyh+H7CQCZdLPjwSn5X88x/wQeSpTvy3oOdcYwYXDeQfoM 9 | n87yxnc4Rw/pOj2HL0SXqx+FAzi3HW3ZIqL0JDfXhJPnSVTukkKhPsx1lljSgQcz 10 | ERTHlU2M+arXfUK+nnyw5wdqiTmXG2mhjGhN1wSfNZnlFTZDYAUVMlZ4I4WnlFJ2 11 | CyzlI1cP5heNS/+Pa5dDvTyeHOG/ciOeyAtn4F/lqv3muayjKLG2dIq6xni4yNZp 12 | /ZrAkH2zsHtBB7yzrRi7/Nh6n3YezTS/yR7kqlTKEByr2JccXdmUTNf1H42H+iOH 13 | 4qekj7Qo/qOo1YwANKomkci6zEf6/EUDVvyl0P8GGh/0X6QI2o12ZOLVW4m+TD+Z 14 | IB4Nfl8QHTTfD6rkJUgIAjdqxY5nBhaFRKwsMLJx4Vstx4LIO5qHlRTYXViIt2DC 15 | eBmvxvJ5p9pRXidt6r02JcXckJ+LxWWZDViS8RPG1qsYxpCgwGcBrPMc3MZKAHxu 16 | /V61PzTdZ3e61Us7touIm6cRfa48gC0vOK86pLbAd1GyJxm/F5GwsuPNdYguw5TV 17 | sLnTPZEehdDmxadsU5fjwjbf5+85LmLj/tRKvtM10avhv/Z4lIjF8AEv16I9kzKE 18 | rd+F/sj2jpVXH3alkuCc5GCh8Pk/LTSothvElfwneiNDsQloROaW3Lz8RFOalkvs 19 | qxc8p4VrHkotp5Y96exNqS6xmojTCuX5r8ZIdmJZMo4DRWbQtNHdk2tJwlLIxTIH 20 | KjQHzBHLoeSGJOZjLbrk6y7m+9LZvkb6HksiYksGQ6WBxcEn/UYZssGHkmvo/UrN 21 | OvMSzUP091zkA23t6LtCPH00VXBTvNWWKPtPE+ySJEmu2cyG8Y7bGqV58SYM/aKs 22 | +zzfngYnqdg06D3V1r1tnoKWwEdNua/rYOEIeu5zvzMUAQDqPc6Tfjj1aBsAtTJv 23 | vTEWWIrtrC8Edjj3dOF4S0fZNtD4mqzfx4n4elFjvJq9u6cC+Ull36vSe2oINRiF 24 | DSpT+3MZDMapqaDdxSqJ/waKRrkUzXivSJZ1o4reu/TMtB5WdL8839PNURoQFNpK 25 | lqDxB7YzuqAgQugF1NtjEOPYaR5KLBO0NjSSa/AqQ8LIAEBUDbmX7+Dl5xfXX1gk 26 | NLtZ9sccXPoKONn0V8Q0NuCvquci1M3pdMNtXXXrfQDgRfaWOev2zB+62tuaab1n 27 | DOB91WXGPREceJqX56W035BKt+5m3dDyQHX12pL3vD0uWW222nSF3Bm0rFPjZQuV 28 | 1so1D/HSnUifLbAKoICmSpwhF62S5Ygfb5RKbqP5sp24Q3jtt/v0Bc+YuOCaSOR7 29 | yQSEEaV4SNdjPIOR+9qe1qlGfCAqQRxrVfKTP5k1Yp4Hhf+jF0AQQx2I4Ctox0Qf 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/id_rsa_pass.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1xN7CzvOi8IOUqXs+y7BvCwr52enPvcaObR5ymVMZKwPiXXqNKOnvQsKv23JxSOfoHS7WeWqTCkxrawsTSB6ZO7qy6AHxF1qa26KGuwkgbAa7wZIAQtrAH/li04jllxDKYdcriq76poVVvK76q6ASxAy76eboWDrABBXhNWD1pHZj5pE0vKpdGRnAqe/vWIkkmJLy5cVwcrKz8RThtTYv1w5Wuzp2z4W7+O9oFXvYr76lztxLSMtgGcoM6hRUdn+CfBP4aN8LBr0MnvAcQec2aZUOEBFl8q73ZRQ10SmZciIaLEBI/MrB0Oa/TY48/FcQjwaFmQdyp2AIG/tQdzmx your_email@example.com 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/known_hosts: -------------------------------------------------------------------------------- 1 | localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp+LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlleg= 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_dsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABswAAAAdzc2gtZH 3 | NzAAAAgQDB5zSCWJEMYMspTanQt0tRR4JhDaDASR+RL84DfQX7Q1XuPG2YpHslB2G0D530 4 | 4xPG7KdbpaTH3zZS8e6WfGKahtSX0+gUZWwpECin/UZdjDiXNz6XJ1ZEoCRLkid+yArXi6 5 | 3BcDcNoPPNwyyMpvR9kNmGprh9G7qUtWDMYNul0wAAABUAjde0V4ohDO7KYPO4drK3AuIm 6 | vfEAAACBAMBFlUuR/RekINTvSbl1+5/iHYv4BULhCSYtTNuOMfidizLIYTDd0uzDxGZwf3 7 | SumhU6j74ew221mBK9C6y0m3v6lcOfa6M+QGrmaI2ciXDSmpxZYdT+RDpMcOZsURAY087F 8 | hxXbLSjCnBzfBfx8vJLOCaQs5qy6HAVm34z8YNeqAAAAgQCWuDOZWIaKghws6Pa6yoyZA9 9 | MMyhgV7HM6KQnAP0XQ+33dRsUVsiCgtgBL/uImkFtOJhrsHEA0pHOG4Cor9Hzyo4Ur57ln 10 | 7kR2IUxhPKm4OyW3meH4gcAVBN1jYnmwZqRgF+7hi6tZNhEzr7VG6N92LpAQ+6Rao4QEAo 11 | m8DnT5yAAAAfDnggvs54IL7AAAAAdzc2gtZHNzAAAAgQDB5zSCWJEMYMspTanQt0tRR4Jh 12 | DaDASR+RL84DfQX7Q1XuPG2YpHslB2G0D5304xPG7KdbpaTH3zZS8e6WfGKahtSX0+gUZW 13 | wpECin/UZdjDiXNz6XJ1ZEoCRLkid+yArXi63BcDcNoPPNwyyMpvR9kNmGprh9G7qUtWDM 14 | YNul0wAAABUAjde0V4ohDO7KYPO4drK3AuImvfEAAACBAMBFlUuR/RekINTvSbl1+5/iHY 15 | v4BULhCSYtTNuOMfidizLIYTDd0uzDxGZwf3SumhU6j74ew221mBK9C6y0m3v6lcOfa6M+ 16 | QGrmaI2ciXDSmpxZYdT+RDpMcOZsURAY087FhxXbLSjCnBzfBfx8vJLOCaQs5qy6HAVm34 17 | z8YNeqAAAAgQCWuDOZWIaKghws6Pa6yoyZA9MMyhgV7HM6KQnAP0XQ+33dRsUVsiCgtgBL 18 | /uImkFtOJhrsHEA0pHOG4Cor9Hzyo4Ur57ln7kR2IUxhPKm4OyW3meH4gcAVBN1jYnmwZq 19 | RgF+7hi6tZNhEzr7VG6N92LpAQ+6Rao4QEAom8DnT5yAAAABQcGgS2uRxbrfvcxPwmL/Mq 20 | JOcO3QAAABVoaWRldGFrZUByYWJiaXQubG9jYWwBAgME 21 | -----END OPENSSH PRIVATE KEY----- 22 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_dsa_key.pub: -------------------------------------------------------------------------------- 1 | ssh-dss AAAAB3NzaC1kc3MAAACBAMHnNIJYkQxgyylNqdC3S1FHgmENoMBJH5EvzgN9BftDVe48bZikeyUHYbQPnfTjE8bsp1ulpMffNlLx7pZ8YpqG1JfT6BRlbCkQKKf9Rl2MOJc3PpcnVkSgJEuSJ37ICteLrcFwNw2g883DLIym9H2Q2YamuH0bupS1YMxg26XTAAAAFQCN17RXiiEM7spg87h2srcC4ia98QAAAIEAwEWVS5H9F6Qg1O9JuXX7n+Idi/gFQuEJJi1M244x+J2LMshhMN3S7MPEZnB/dK6aFTqPvh7DbbWYEr0LrLSbe/qVw59roz5AauZojZyJcNKanFlh1P5EOkxw5mxREBjTzsWHFdstKMKcHN8F/Hy8ks4JpCzmrLocBWbfjPxg16oAAACBAJa4M5lYhoqCHCzo9rrKjJkD0wzKGBXsczopCcA/RdD7fd1GxRWyIKC2AEv+4iaQW04mGuwcQDSkc4bgKiv0fPKjhSvnuWfuRHYhTGE8qbg7JbeZ4fiBwBUE3WNiebBmpGAX7uGLq1k2ETOvtUbo33YukBD7pFqjhAQCibwOdPnI 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_ecdsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 3 | 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTb2Qpge+BrGw6SKfi2lTzubUPS5FYg 4 | OGJBOV3IgNGJOP4q+lQZV4KQCq+XgtDsL3OxAOFya/sa3dNI90Sh5ZXoAAAAsOBC60DgQu 5 | tAAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp 6 | +LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlle 7 | gAAAAgQhtKq9f2GGDeivbuZ3tPKWtzabtWWkBBMCP79B1WIhcAAAAVaGlkZXRha2VAcmFi 8 | Yml0LmxvY2FsAQID 9 | -----END OPENSSH PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_ecdsa_key.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp+LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlleg= 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_ed25519_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACAg0uc6mInMIv6tF/4rYU5Tn/VBZ0Wp7HSzBqMQDLwPqgAAAJi6uxPeursT 4 | 3gAAAAtzc2gtZWQyNTUxOQAAACAg0uc6mInMIv6tF/4rYU5Tn/VBZ0Wp7HSzBqMQDLwPqg 5 | AAAEDtQZ8yklhUquVEgewNF+kCFYADJ5vOQqrAfqWZ1XpiryDS5zqYicwi/q0X/ithTlOf 6 | 9UFnRansdLMGoxAMvA+qAAAAFWhpZGV0YWtlQHJhYmJpdC5sb2NhbA== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_ed25519_key.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICDS5zqYicwi/q0X/ithTlOf9UFnRansdLMGoxAMvA+q 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_rsa_key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn 3 | NhAAAAAwEAAQAAAQEAvQA/tTTYSOslnkBwHcQ3dQ+09adHECgRQk0y9+3PZv4EQaN48MzF 4 | hlqjLM8RUfKnoIVp3yHdo0uEZaDYWD5nyePSnOihIV0sx5gOoJdGhzligcQnhhC2EKoBOy 5 | u23NnT7Qx+ObgLxZYD28rsYD5WREp5Xj8Lv7BdQzuH3z+4l0CTOrSYok2dP+InqMBBiR+k 6 | k1UXOqcD6YeipIuzlrhqOceN07zON/dm9RGOY040So3mCKcn0TJ2wYB+bHLkimC6kgaiCy 7 | TWIGI/7yiwTuYDJYhAqCTPaaABoz6k4LdDr9+7mjTADRQseFf7VcKFT7kiSfbrUNSkGxKQ 8 | +Mw/w7EfgQAAA9Cj8Rn7o/EZ+wAAAAdzc2gtcnNhAAABAQC9AD+1NNhI6yWeQHAdxDd1D7 9 | T1p0cQKBFCTTL37c9m/gRBo3jwzMWGWqMszxFR8qeghWnfId2jS4RloNhYPmfJ49Kc6KEh 10 | XSzHmA6gl0aHOWKBxCeGELYQqgE7K7bc2dPtDH45uAvFlgPbyuxgPlZESnlePwu/sF1DO4 11 | ffP7iXQJM6tJiiTZ0/4ieowEGJH6STVRc6pwPph6Kki7OWuGo5x43TvM4392b1EY5jTjRK 12 | jeYIpyfRMnbBgH5scuSKYLqSBqILJNYgYj/vKLBO5gMliECoJM9poAGjPqTgt0Ov37uaNM 13 | ANFCx4V/tVwoVPuSJJ9utQ1KQbEpD4zD/DsR+BAAAAAwEAAQAAAQEAhfWOMi6ReiWJFUCQ 14 | 9tgjgooudcspmC7+BKNZE9dvoI08kRV/3BUXj6HgdBsUKKQ34ZOONcP4JwyYe7vke69Hux 15 | YKKoLL6izzV0jUXUi7iY7H3jgc124yzV7h3oGea6zNBABN2zUyysoIVBnhLlogpOiwW3eO 16 | KUCk6clhBYBRoom/OpCnvx0K6RW2m9rmj9IdSt5XkIqR+e4idP4f4FMIUprX2fUbCtpyvz 17 | 1bC3B19dhLqi1hnQKHw8UAQIUXzwSlgSmbf/KO56Lm4kN4KsIOiG8jjRqe8y2Zz40j3jfP 18 | ERdjh3Gx8opRb2iSZO2N48dltm+iPHe/pSvNGdz1ZfQAAQAAAIEAt+/7p7AI1vKBHtB8ra 19 | iG8ShaFRD32i5rILVI+UEp7NKi48Xm36LLbxgJZwNGsF4DGcimMfdjyHy7J1aB6BleuKUX 20 | iHRQFus26iU6jW7pqSIG9UAEONxO2smv8AjpU9t6oWmWj91osiuykMWcKrnKYgR7tB+BsS 21 | hIVqJagkn1jdwAAACBAOkRQheAz7c8hDnz3nnTBF0fACL8/yH4igChrYA29L1EdKgH1FwW 22 | RZIix6ROxZunYlbbdLhx2XXJnu07ffROVNku6bBK+XIHyrQlrUEU4xmKzwbmh2sBv78T6u 23 | WqmEj/YfIx7aX0G+i5GWfPhKdwvMJI1ZATEv7OVGo2DO6HoFWBAAAAgQDPmP/yfdxCxtWK 24 | GY1MANtFh/b2xWgSPRasbTg3L3LEk2I3WiQPcARTLv9O02ZNrwBSPJ33BF5GEkUcK13WI3 25 | P3ns3YHA031EPwQrF1T0iHN024EkDl0KKUMjobSOeoC1RmXHuJ6Do3lHHNxlvrkIKw3YxV 26 | 92rSZ1v9fq2p3xnKAQAAABVoaWRldGFrZUByYWJiaXQubG9jYWwBAgME 27 | -----END OPENSSH PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/etc/ssh/ssh_host_rsa_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9AD+1NNhI6yWeQHAdxDd1D7T1p0cQKBFCTTL37c9m/gRBo3jwzMWGWqMszxFR8qeghWnfId2jS4RloNhYPmfJ49Kc6KEhXSzHmA6gl0aHOWKBxCeGELYQqgE7K7bc2dPtDH45uAvFlgPbyuxgPlZESnlePwu/sF1DO4ffP7iXQJM6tJiiTZ0/4ieowEGJH6STVRc6pwPph6Kki7OWuGo5x43TvM4392b1EY5jTjRKjeYIpyfRMnbBgH5scuSKYLqSBqILJNYgYj/vKLBO5gMliECoJM9poAGjPqTgt0Ov37uaNMANFCx4V/tVwoVPuSJJ9utQ1KQbEpD4zD/DsR+B 2 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/run-sshd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | exec docker run --rm -p 22:22 \ 4 | -e "SSH_HOST_DSA_KEY=$(cat etc/ssh/ssh_host_dsa_key)" \ 5 | -e "SSH_HOST_RSA_KEY=$(cat etc/ssh/ssh_host_rsa_key)" \ 6 | -e "SSH_HOST_ECDSA_KEY=$(cat etc/ssh/ssh_host_ecdsa_key)" \ 7 | -e "SSH_HOST_ED25519_KEY=$(cat etc/ssh/ssh_host_ed25519_key)" \ 8 | -e "SSH_AUTHORIZED_KEYS=$(cat etc/ssh/id_rsa.pub etc/ssh/id_rsa_pass.pub etc/ssh/id_ecdsa.pub)" \ 9 | --name sshd \ 10 | int128/sshd 11 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/os/FileDivCategory.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | @Category(File) 4 | class FileDivCategory { 5 | 6 | File div(String child) { 7 | new File(this as File, child) 8 | } 9 | 10 | File div(MkdirType type) { 11 | switch (type) { 12 | case MkdirType.DIRECTORY: 13 | assert mkdir() 14 | break 15 | 16 | case MkdirType.DIRECTORIES: 17 | assert mkdirs() 18 | break 19 | 20 | default: 21 | throw new IllegalArgumentException("Unknown mkdir type: $type") 22 | } 23 | this 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/os/Fixture.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.hidetake.groovy.ssh.core.Service 4 | 5 | class Fixture { 6 | 7 | static randomInt(int max = 10000) { 8 | (Math.random() * max) as int 9 | } 10 | 11 | static remoteTmpPath() { 12 | "/tmp/groovy-ssh.os-integration-test.${UUID.randomUUID()}" 13 | } 14 | 15 | static createRemotes(Service service) { 16 | service.remotes { 17 | Default { 18 | host = 'localhost' 19 | port = 22 20 | user = 'tester' 21 | identity = new File("etc/ssh/id_rsa") 22 | knownHosts = addHostKey(new File("build/known_hosts")) 23 | } 24 | } 25 | service.remotes { 26 | DefaultWithECDSAKey { 27 | host = service.remotes.Default.host 28 | port = service.remotes.Default.port 29 | user = service.remotes.Default.user 30 | identity = new File("etc/ssh/id_ecdsa") 31 | knownHosts = service.remotes.Default.knownHosts 32 | } 33 | DefaultWithPassphrase { 34 | host = service.remotes.Default.host 35 | port = service.remotes.Default.port 36 | user = service.remotes.Default.user 37 | knownHosts = service.remotes.Default.knownHosts 38 | 39 | identity = new File("etc/ssh/id_rsa_pass") 40 | passphrase = 'gradle' 41 | } 42 | DefaultWithOpenSSHKnownHosts { 43 | host = service.remotes.Default.host 44 | port = service.remotes.Default.port 45 | user = service.remotes.Default.user 46 | identity = service.remotes.Default.identity 47 | 48 | knownHosts = new File("etc/ssh/known_hosts") 49 | } 50 | DefaultWithAgent { 51 | host = service.remotes.Default.host 52 | port = service.remotes.Default.port 53 | user = service.remotes.Default.user 54 | knownHosts = service.remotes.Default.knownHosts 55 | 56 | agent = true 57 | } 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/os/MkdirType.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | enum MkdirType { 4 | DIRECTORY, 5 | DIRECTORIES 6 | } 7 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/os/SshAgent.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.junit.rules.ExternalResource 5 | 6 | @Slf4j 7 | class SshAgent extends ExternalResource { 8 | 9 | @Override 10 | protected void before() { 11 | removeAll() 12 | } 13 | 14 | @Override 15 | protected void after() { 16 | removeAll() 17 | } 18 | 19 | void add(String keyPath) { 20 | ['chmod', '600', keyPath].execute().waitForProcessOutput(System.out, System.err) 21 | log.info("Adding key to ssh-agent: $keyPath") 22 | ['ssh-add', keyPath].execute().waitForProcessOutput(System.out, System.err) 23 | } 24 | 25 | void removeAll() { 26 | log.info('Remove all keys from ssh-agent') 27 | ['ssh-add', '-D'].execute().waitForProcessOutput(System.out, System.err) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/os/UserManagementExtension.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.hidetake.groovy.ssh.session.SessionExtension 4 | 5 | trait UserManagementExtension implements SessionExtension { 6 | 7 | void recreateUser(String user) { 8 | execute """ 9 | if id "$user"; then 10 | sudo deluser --remove-home $user 11 | fi 12 | sudo adduser -D $user 13 | """, pty: true 14 | } 15 | 16 | void configurePassword(String user, String password) { 17 | execute "sudo passwd $user", pty: true, interaction: Helper.passwordInteraction.curry(password) 18 | } 19 | 20 | static class Helper { 21 | static final passwordInteraction = { password -> 22 | when(partial: ~/.+[Pp]assword: */) { 23 | standardInput << password << '\n' 24 | } 25 | when(line: _) {} 26 | } 27 | } 28 | 29 | void configureAuthorizedKeysAsCurrentUser(String user) { 30 | execute """ 31 | sudo -i -u $user mkdir -m 700 .ssh 32 | sudo -i -u $user touch .ssh/authorized_keys 33 | sudo -i -u $user chmod 600 .ssh/authorized_keys 34 | sudo -i -u $user tee .ssh/authorized_keys < ~/.ssh/authorized_keys > /dev/null 35 | """, pty: true 36 | } 37 | 38 | void configureAuthorizedKeys(String user, String publicKey) { 39 | execute """ 40 | sudo -i -u $user mkdir -m 700 .ssh 41 | sudo -i -u $user touch .ssh/authorized_keys 42 | sudo -i -u $user chmod 600 .ssh/authorized_keys 43 | echo '$publicKey' | sudo -i -u $user tee .ssh/authorized_keys > /dev/null 44 | """, pty: true 45 | } 46 | 47 | void configureSudoers(String content) { 48 | put text: content, into: '/tmp/groovy-ssh-sudoers' 49 | execute """ 50 | sudo chmod 440 /tmp/groovy-ssh-sudoers 51 | sudo chown 0.0 /tmp/groovy-ssh-sudoers 52 | sudo mkdir -p -m 700 /etc/sudoers.d 53 | sudo mv /tmp/groovy-ssh-sudoers /etc/sudoers.d/groovy-ssh-sudoers 54 | """, pty: true 55 | } 56 | 57 | void cleanupSudoers() { 58 | execute 'sudo rm -f /etc/sudoers.d/groovy-ssh-sudoers' 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/os/GatewaySpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.hidetake.groovy.ssh.Ssh 4 | import org.hidetake.groovy.ssh.core.Service 5 | import org.junit.Rule 6 | import org.junit.rules.TemporaryFolder 7 | import spock.lang.Ignore 8 | import spock.lang.Specification 9 | 10 | import static org.hidetake.groovy.ssh.test.os.Fixture.createRemotes 11 | 12 | /** 13 | * Check if gateway access should work with Linux system. 14 | * 15 | * @author Hidetake Iwata 16 | */ 17 | class GatewaySpec extends Specification { 18 | 19 | @Rule 20 | TemporaryFolder temporaryFolder 21 | 22 | Service ssh 23 | 24 | def setup() { 25 | ssh = Ssh.newService() 26 | createRemotes(ssh) 27 | ssh.remotes { 28 | InternalServer { 29 | host = 'groovy-ssh-integration-test-internal-box' 30 | user = ssh.remotes.Default.user 31 | identity = ssh.remotes.Default.identity 32 | gateway = ssh.remotes.Default 33 | } 34 | } 35 | } 36 | 37 | //FIXME: at this time no way to test multi-hop on CircleCI 38 | @Ignore 39 | def "it should connect to target server via gateway server"() { 40 | given: 41 | def knownHostsFile = temporaryFolder.newFile() 42 | 43 | when: 44 | ssh.run { 45 | settings { 46 | knownHosts = addHostKey(knownHostsFile) 47 | } 48 | session(ssh.remotes.InternalServer) { 49 | execute 'hostname' 50 | } 51 | } 52 | 53 | then: 54 | knownHostsFile.text =~ /^groovy-ssh-integration-test-internal-box .+/ 55 | 56 | when: 57 | ssh.run { 58 | settings { 59 | knownHosts = knownHostsFile 60 | } 61 | session(ssh.remotes.InternalServer) { 62 | execute 'hostname' 63 | } 64 | } 65 | 66 | then: 67 | knownHostsFile.text =~ /^groovy-ssh-integration-test-internal-box .+/ 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/os/HostAuthenticationSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.hidetake.groovy.ssh.Ssh 4 | import org.hidetake.groovy.ssh.core.Service 5 | import spock.lang.Specification 6 | 7 | import static org.hidetake.groovy.ssh.test.os.Fixture.createRemotes 8 | import static org.hidetake.groovy.ssh.test.os.Fixture.randomInt 9 | 10 | /** 11 | * Check if host authentication works with real OS environment. 12 | * 13 | * @author Hidetake Iwata 14 | */ 15 | class HostAuthenticationSpec extends Specification { 16 | 17 | Service ssh 18 | 19 | def setup() { 20 | ssh = Ssh.newService() 21 | createRemotes(ssh) 22 | } 23 | 24 | def 'should work with known_hosts generated by OpenSSH'() { 25 | given: 26 | def x = randomInt() 27 | def y = randomInt() 28 | 29 | when: 30 | def r = ssh.run { 31 | session(ssh.remotes.DefaultWithOpenSSHKnownHosts) { 32 | execute "expr $x + $y" 33 | } 34 | } as int 35 | 36 | then: 37 | r == (x + y) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/os/ScpSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.hidetake.groovy.ssh.session.transfer.FileTransferMethod 4 | import spock.lang.Timeout 5 | 6 | /** 7 | * Check if file transfer works with SCP command of OpenSSH. 8 | * 9 | * @author Hidetake Iwata 10 | */ 11 | @Timeout(10) 12 | class ScpSpec extends AbstractFileTransferSpec { 13 | 14 | def setup() { 15 | ssh.settings { 16 | fileTransfer = FileTransferMethod.scp 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/os/ScriptSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.codehaus.groovy.tools.Utilities 4 | import org.hidetake.groovy.ssh.Ssh 5 | import org.hidetake.groovy.ssh.core.Service 6 | import spock.lang.Specification 7 | 8 | import static org.hidetake.groovy.ssh.test.os.Fixture.createRemotes 9 | 10 | class ScriptSpec extends Specification { 11 | 12 | Service ssh 13 | 14 | def setup() { 15 | ssh = Ssh.newService() 16 | createRemotes(ssh) 17 | } 18 | 19 | def 'should execute a script'() { 20 | when: 21 | def actual = ssh.run { 22 | session(ssh.remotes.Default) { 23 | executeScript '''#!/bin/sh -xe 24 | echo 1 25 | echo 2 26 | ''' 27 | } 28 | } 29 | 30 | then: 31 | actual == "1${Utilities.eol()}2" 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/os/SftpSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import static org.hidetake.groovy.ssh.test.os.Fixture.remoteTmpPath 4 | 5 | /** 6 | * Check if file transfer works with SFTP subsystem of OpenSSH. 7 | * 8 | * @author Hidetake Iwata 9 | */ 10 | class SftpSpec extends AbstractFileTransferSpec { 11 | 12 | def 'remove() should delete a directory recursively'() { 13 | given: 14 | def remoteDir = remoteTmpPath() 15 | 16 | when: 17 | ssh.run { 18 | session(ssh.remotes.Default) { 19 | execute "mkdir -vp $remoteDir/foo/bar" 20 | execute "date > $remoteDir/foo/bar/baz" 21 | remove remoteDir 22 | execute "test ! -d $remoteDir" 23 | } 24 | } 25 | 26 | then: 27 | noExceptionThrown() 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/os/ShellSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.os 2 | 3 | import org.hidetake.groovy.ssh.Ssh 4 | import org.hidetake.groovy.ssh.core.Service 5 | import spock.lang.Ignore 6 | import spock.lang.Specification 7 | import spock.lang.Timeout 8 | 9 | import static org.hidetake.groovy.ssh.test.os.Fixture.createRemotes 10 | 11 | @Timeout(10) 12 | class ShellSpec extends Specification { 13 | 14 | Service ssh 15 | 16 | def setup() { 17 | ssh = Ssh.newService() 18 | createRemotes(ssh) 19 | } 20 | 21 | //FIXME: do not work with Alpine 22 | @Ignore 23 | def 'should execute the shell'() { 24 | when: 25 | ssh.run { 26 | session(ssh.remotes.Default) { 27 | shell(interaction: { 28 | when(partial: ~/.*[$%#]\W*/, from: standardOutput) { 29 | standardInput << 'uname -a' << '\n' 30 | 31 | when(partial: ~/.*[$%#]\W*/, from: standardOutput) { 32 | standardInput << 'exit 0' << '\n' 33 | } 34 | when(line: _, from: standardOutput) {} 35 | } 36 | when(line: _, from: standardOutput) {} 37 | }) 38 | } 39 | } 40 | 41 | then: 42 | noExceptionThrown() 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /groovy-ssh/os-integration-test/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /groovy-ssh/plugin-integration/create-branch-for-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | cd "$(dirname $0)/gradle-ssh-plugin" 4 | git reset --hard 5 | 6 | git checkout -b "groovy-ssh-$CIRCLE_TAG" 7 | sed -i -e "s,groovy-ssh:[0-9.]*,groovy-ssh:${CIRCLE_TAG:-SNAPSHOT},g" core/build.gradle 8 | git add . 9 | git commit -m "Groovy SSH $CIRCLE_TAG" -m "https://github.com/int128/groovy-ssh/releases/tag/$CIRCLE_TAG" 10 | git push origin 11 | -------------------------------------------------------------------------------- /groovy-ssh/plugin-integration/run-plugin-integration-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | function checkout_remote_branch () { 4 | local branch_name="$1" 5 | git fetch origin -v "$branch_name:$branch_name" 6 | git checkout "$branch_name" 7 | } 8 | 9 | cd "$(dirname $0)/gradle-ssh-plugin" 10 | git reset --hard 11 | 12 | if checkout_remote_branch groovy-ssh-acceptance-test 13 | then 14 | echo 'Use dedicated branch for specification change breaking backward compatibility' 15 | fi 16 | 17 | sed -i -e "s,groovy-ssh:[0-9.]*,groovy-ssh:${CIRCLE_TAG:-SNAPSHOT},g" core/build.gradle 18 | echo 'repositories.mavenLocal()' >> core/build.gradle 19 | 20 | mkdir -p acceptance-test/fixture/build 21 | cp -av ../../os-integration-test/build/.ssh acceptance-test/fixture/build/.ssh 22 | 23 | ./gradlew -Ptarget.gradle.versions=1.12 :acceptance-test:test 24 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/bin/.gitignore: -------------------------------------------------------------------------------- 1 | /main/ 2 | /test/ 3 | /default/ 4 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation project(':groovy-ssh:core') 11 | implementation 'org.apache.sshd:sshd-core:2.2.0' 12 | implementation 'org.apache.sshd:sshd-sftp:2.2.0' 13 | implementation 'org.apache.sshd:sshd-scp:2.2.0' 14 | 15 | runtimeOnly 'org.bouncycastle:bcpkix-jdk15on:1.70' 16 | 17 | testImplementation 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1' 18 | testImplementation platform("org.spockframework:spock-bom:2.3-groovy-3.0") 19 | testImplementation "org.spockframework:spock-core" 20 | testImplementation "org.spockframework:spock-junit4" 21 | testRuntimeOnly 'ch.qos.logback:logback-classic:1.5.18' 22 | testRuntimeOnly 'cglib:cglib-nodep:3.3.0' 23 | testRuntimeOnly 'org.objenesis:objenesis:3.4' 24 | } 25 | 26 | test { 27 | useJUnitPlatform() 28 | mustRunAfter ':groovy-ssh:core:check' 29 | 30 | if (System.getProperty('os.name') == 'Linux') { 31 | systemProperty 'java.security.egd', 'file:/dev/./urandom' 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/CommandHelper.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.apache.sshd.server.Environment 5 | import org.apache.sshd.server.ExitCallback 6 | import org.apache.sshd.server.command.Command 7 | 8 | import static org.hidetake.groovy.ssh.util.Utility.callWithDelegate 9 | 10 | @Slf4j 11 | class CommandHelper { 12 | 13 | static class CommandContext { 14 | InputStream inputStream 15 | OutputStream outputStream 16 | OutputStream errorStream 17 | ExitCallback exitCallback 18 | Environment environment 19 | } 20 | 21 | static command(int status, @DelegatesTo(CommandContext) Closure interaction = {}) { 22 | def context = new CommandContext() 23 | [setInputStream: { InputStream inputStream -> 24 | context.inputStream = inputStream 25 | }, 26 | setOutputStream: { OutputStream outputStream -> 27 | context.outputStream = outputStream 28 | }, 29 | setErrorStream: { OutputStream errorStream -> 30 | context.errorStream = errorStream 31 | }, 32 | setExitCallback: { ExitCallback callback -> 33 | context.exitCallback = callback 34 | }, 35 | start: { Environment env -> 36 | context.environment = env 37 | Thread.start { 38 | log.debug("[ssh-server-mock] Started interaction thread") 39 | try { 40 | callWithDelegate(interaction, context) 41 | context.exitCallback.onExit(status) 42 | } catch (Throwable t) { 43 | log.error("[ssh-server-mock] Error occurred on interaction thread", t) 44 | context.exitCallback.onExit(-1, t.message) 45 | } 46 | log.debug("[ssh-server-mock] Terminated interaction thread") 47 | } 48 | }, 49 | destroy: { -> 50 | }] as Command 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/FileDivCategory.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | @Category(File) 4 | class FileDivCategory { 5 | 6 | File div(String child) { 7 | new File(this as File, child) 8 | } 9 | 10 | File div(DirectoryType type) { 11 | switch (type) { 12 | case DirectoryType.DIRECTORY: 13 | assert mkdir() 14 | break 15 | 16 | case DirectoryType.DIRECTORIES: 17 | assert mkdirs() 18 | break 19 | 20 | default: 21 | throw new IllegalArgumentException("Unknown directory type: $type") 22 | } 23 | this 24 | } 25 | 26 | static enum DirectoryType { 27 | DIRECTORY, 28 | DIRECTORIES 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/FilenameUtils.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | class FilenameUtils { 4 | 5 | /** 6 | * Convert Windows path to Unix path for SFTP subsystem of Apache SSHD server. 7 | * This method does nothing on Unix platform. 8 | * 9 | * @param path 10 | * @return 11 | */ 12 | static String toUnixPath(String path) { 13 | if (File.separator == '/') { 14 | path 15 | } else { 16 | path.replace(File.separatorChar, '/' as char).replaceFirst(~/(\w):/, '/$1') 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/HostKeyFixture.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider 4 | 5 | class HostKeyFixture { 6 | 7 | static publicKey(String keyType) { 8 | HostKeyFixture.getResourceAsStream("/hostkey_${keyType}.pub").text 9 | } 10 | 11 | static publicKeys(List keyTypes) { 12 | keyTypes.collect { keyType -> publicKey(keyType) } 13 | } 14 | 15 | static keyPairProvider(String... keyTypes) { 16 | keyPairProvider(keyTypes.toList()) 17 | } 18 | 19 | static keyPairProvider(List keyTypes) { 20 | def keyPairProvider = new ClassLoadableResourceKeyPairProvider() 21 | keyPairProvider.resourceLoader = HostKeyFixture.classLoader 22 | keyPairProvider.resources = keyTypes.collect { "hostkey_$it".toString() } 23 | keyPairProvider 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/SshServerMock.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.apache.sshd.common.keyprovider.KeyPairProvider 5 | import org.apache.sshd.server.SshServer 6 | 7 | import static org.apache.sshd.common.keyprovider.KeyPairProvider.ECDSA_SHA2_NISTP256 8 | import static org.hidetake.groovy.ssh.test.server.HostKeyFixture.keyPairProvider 9 | 10 | /** 11 | * A helper class for server-based integration tests. 12 | * 13 | * @author Hidetake Iwata 14 | */ 15 | @Slf4j 16 | class SshServerMock { 17 | 18 | static SshServer setUpLocalhostServer(KeyPairProvider provider = keyPairProvider(ECDSA_SHA2_NISTP256)) { 19 | SshServer.setUpDefaultServer().with { 20 | host = 'localhost' 21 | port = pickUpFreePort() 22 | keyPairProvider = provider 23 | it 24 | } 25 | } 26 | 27 | static int pickUpFreePort() { 28 | def socket = new ServerSocket(0) 29 | def port = socket.localPort 30 | socket.close() 31 | port 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/SudoHelper.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import groovy.transform.Immutable 4 | import groovy.util.logging.Slf4j 5 | 6 | import static org.hidetake.groovy.ssh.util.Utility.callWithDelegate 7 | 8 | @Slf4j 9 | class SudoHelper { 10 | 11 | @Immutable 12 | static class ParsedCommandLine { 13 | final String sudoPath 14 | final String prompt 15 | final String command 16 | 17 | static ParsedCommandLine parse(String commandLine) { 18 | def matcher = commandLine =~ /^(.+?) -S -p '(.+?)' (.+)$/ 19 | assert matcher.matches() 20 | def groups = matcher[0] as List 21 | new ParsedCommandLine(sudoPath: groups[1], prompt: groups[2], command: groups[3]) 22 | } 23 | } 24 | 25 | static sudoCommand(String commandLine, int status, 26 | String expectedSudoPath, String expectedCommand, String expectedPassword, 27 | String lectureMessage = null, 28 | @DelegatesTo(CommandHelper.CommandContext) Closure closure = null) { 29 | CommandHelper.command(status) { 30 | def parsedCommandLine = ParsedCommandLine.parse(commandLine) 31 | assert parsedCommandLine.sudoPath == expectedSudoPath 32 | assert parsedCommandLine.command == expectedCommand 33 | 34 | if (lectureMessage) { 35 | log.debug("[sudo] Sending to standard output: $lectureMessage") 36 | outputStream << lectureMessage << '\n' 37 | } 38 | 39 | log.debug("[sudo] Sending prompt: $parsedCommandLine.prompt") 40 | outputStream << parsedCommandLine.prompt 41 | outputStream.flush() 42 | 43 | log.debug('[sudo] Waiting for password') 44 | def actualPassword = new ByteArrayOutputStream() 45 | for (;;) { 46 | def b = inputStream.read() 47 | if (b == 0x0a || b == 0x0d || b == -1) { 48 | break 49 | } 50 | actualPassword.write(b) 51 | } 52 | 53 | log.debug("[sudo] Got password: $actualPassword") 54 | assert actualPassword.toString() == expectedPassword 55 | 56 | log.debug('[sudo] Sending ACK to the password') 57 | outputStream << '\n' 58 | outputStream.flush() 59 | 60 | if (closure) { 61 | log.debug('[sudo] Starting interaction') 62 | delegate.metaClass.parsedCommandLine = parsedCommandLine 63 | callWithDelegate(closure, delegate) 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/main/groovy/org/hidetake/groovy/ssh/test/server/UserKeyFixture.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | class UserKeyFixture { 4 | 5 | static enum KeyType { 6 | ecdsa, 7 | ecdsa_pass 8 | } 9 | 10 | static privateKey(KeyType keyType = KeyType.ecdsa) { 11 | new File(UserKeyFixture.getResource("/id_$keyType").file) 12 | } 13 | 14 | static publicKey(KeyType keyType = KeyType.ecdsa) { 15 | new File(UserKeyFixture.getResource("/id_${keyType}.pub").file) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/server/DryRunSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import org.hidetake.groovy.ssh.Ssh 4 | import org.hidetake.groovy.ssh.core.Service 5 | import spock.lang.Specification 6 | 7 | class DryRunSpec extends Specification { 8 | 9 | Service ssh 10 | 11 | def setup() { 12 | ssh = Ssh.newService() 13 | ssh.settings { 14 | knownHosts = allowAnyHosts 15 | } 16 | ssh.remotes { 17 | testServer { 18 | host = 'localhost' 19 | user = 'user' 20 | dryRun = true 21 | } 22 | } 23 | } 24 | 25 | 26 | def "dry-run shell should work without server"() { 27 | when: 28 | ssh.run { 29 | session(ssh.remotes.testServer) { 30 | shell(interaction: {}) 31 | } 32 | } 33 | 34 | then: 35 | noExceptionThrown() 36 | } 37 | 38 | def "dry-run shell with options should work without server"() { 39 | when: 40 | ssh.run { 41 | session(ssh.remotes.testServer) { 42 | shell(logging: 'none') 43 | } 44 | } 45 | 46 | then: 47 | noExceptionThrown() 48 | } 49 | 50 | def "dry-run command should work without server"() { 51 | when: 52 | ssh.run { 53 | session(ssh.remotes.testServer) { 54 | execute('ls -l') 55 | } 56 | } 57 | 58 | then: 59 | noExceptionThrown() 60 | } 61 | 62 | def "dry-run command with callback should work without server"() { 63 | given: 64 | def callbackExecuted = false 65 | 66 | when: 67 | ssh.run { 68 | session(ssh.remotes.testServer) { 69 | execute('ls -l') { 70 | callbackExecuted = true 71 | } 72 | } 73 | } 74 | 75 | then: 76 | callbackExecuted 77 | } 78 | 79 | def "dry-run command with options should work without server"() { 80 | when: 81 | ssh.run { 82 | session(ssh.remotes.testServer) { 83 | execute('ls -l', pty: true) 84 | } 85 | } 86 | 87 | then: 88 | noExceptionThrown() 89 | } 90 | 91 | def "dry-run command with options and callback should work without server"() { 92 | given: 93 | def callbackExecuted = false 94 | 95 | when: 96 | ssh.run { 97 | session(ssh.remotes.testServer) { 98 | execute('ls -l', pty: true) { 99 | callbackExecuted = true 100 | } 101 | } 102 | } 103 | 104 | then: 105 | callbackExecuted 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/server/ExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import org.apache.sshd.server.SshServer 4 | import org.apache.sshd.server.auth.password.PasswordAuthenticator 5 | import org.apache.sshd.server.command.CommandFactory 6 | import org.hidetake.groovy.ssh.Ssh 7 | import org.hidetake.groovy.ssh.core.Service 8 | import spock.lang.Shared 9 | import spock.lang.Specification 10 | import spock.util.concurrent.PollingConditions 11 | 12 | import static org.hidetake.groovy.ssh.test.server.CommandHelper.command 13 | 14 | class ExtensionSpec extends Specification { 15 | 16 | @Shared 17 | SshServer server 18 | 19 | Service ssh 20 | 21 | def setupSpec() { 22 | server = SshServerMock.setUpLocalhostServer() 23 | server.passwordAuthenticator = Mock(PasswordAuthenticator) { 24 | authenticate('someuser', 'somepassword', _) >> true 25 | } 26 | server.start() 27 | } 28 | 29 | def cleanupSpec() { 30 | new PollingConditions().eventually { 31 | assert server.activeSessions.empty 32 | } 33 | server.stop() 34 | } 35 | 36 | def setup() { 37 | server.commandFactory = Mock(CommandFactory) 38 | 39 | ssh = Ssh.newService() 40 | ssh.settings { 41 | knownHosts = allowAnyHosts 42 | } 43 | ssh.remotes { 44 | testServer { 45 | host = server.host 46 | port = server.port 47 | user = 'someuser' 48 | password = 'somepassword' 49 | } 50 | } 51 | } 52 | 53 | def "adding map to ssh.settings.extensions should extends DSL"() { 54 | given: 55 | ssh.settings { 56 | extensions.add restartAppServer: { 57 | execute 'sudo service tomcat restart' 58 | } 59 | } 60 | 61 | when: 62 | ssh.run { 63 | session(ssh.remotes.testServer) { 64 | restartAppServer() 65 | } 66 | } 67 | 68 | then: 1 * server.commandFactory.createCommand('sudo service tomcat restart') >> command(0) 69 | } 70 | 71 | def "adding map to settings.extensions should extends DSL"() { 72 | when: 73 | ssh.run { 74 | settings { 75 | extensions.add restartAppServer: { 76 | execute 'sudo service tomcat restart' 77 | } 78 | } 79 | session(ssh.remotes.testServer) { 80 | restartAppServer() 81 | } 82 | } 83 | 84 | then: 1 * server.commandFactory.createCommand('sudo service tomcat restart') >> command(0) 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/server/RetrySpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import com.jcraft.jsch.JSchException 4 | import org.apache.sshd.server.SshServer 5 | import org.apache.sshd.server.auth.password.PasswordAuthenticator 6 | import org.hidetake.groovy.ssh.Ssh 7 | import org.hidetake.groovy.ssh.core.Service 8 | import spock.lang.Shared 9 | import spock.lang.Specification 10 | import spock.lang.Timeout 11 | 12 | @Timeout(10) 13 | class RetrySpec extends Specification { 14 | 15 | @Shared 16 | SshServer server 17 | 18 | Service ssh 19 | 20 | def setupSpec() { 21 | server = SshServerMock.setUpLocalhostServer() 22 | } 23 | 24 | def setup() { 25 | server.passwordAuthenticator = Mock(PasswordAuthenticator) 26 | 27 | ssh = Ssh.newService() 28 | ssh.settings { 29 | knownHosts = allowAnyHosts 30 | } 31 | ssh.remotes { 32 | testServer { 33 | host = server.host 34 | port = server.port 35 | user = 'someuser' 36 | password = 'somepassword' 37 | } 38 | } 39 | } 40 | 41 | 42 | def 'should retry but fail'() { 43 | given: 44 | ssh.settings { 45 | retryWaitSec = 1 46 | retryCount = 1 47 | } 48 | 49 | when: 50 | ssh.run { 51 | session(ssh.remotes.testServer) {} 52 | } 53 | 54 | then: 'connection refused' 55 | JSchException e = thrown() 56 | e.cause instanceof ConnectException 57 | } 58 | 59 | def 'should retry and success'() { 60 | given: 61 | ssh.settings { 62 | retryWaitSec = 2 63 | retryCount = 1 64 | } 65 | 66 | and: 'start SSH server after 1 second' 67 | def thread = Thread.start { 68 | Thread.sleep(1000L) 69 | server.start() 70 | } 71 | 72 | when: 73 | ssh.run { 74 | session(ssh.remotes.testServer) {} 75 | } 76 | 77 | then: 78 | (1.._) * server.passwordAuthenticator.authenticate('someuser', 'somepassword', _) >> true 79 | 80 | cleanup: 81 | thread.join() 82 | server.stop() 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/server/ScpSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import org.apache.sshd.server.scp.ScpCommandFactory 4 | import org.hidetake.groovy.ssh.session.transfer.FileTransferMethod 5 | import spock.lang.Timeout 6 | 7 | @Timeout(10) 8 | class ScpSpec extends AbstractFileTransferSpec { 9 | 10 | def setupSpec() { 11 | server.commandFactory = new ScpCommandFactory() 12 | server.start() 13 | } 14 | 15 | def setup() { 16 | ssh.settings { 17 | fileTransfer = FileTransferMethod.scp 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/groovy/org/hidetake/groovy/ssh/test/server/SftpSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.hidetake.groovy.ssh.test.server 2 | 3 | import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory 4 | import org.hidetake.groovy.ssh.operation.SftpException 5 | 6 | import static org.hidetake.groovy.ssh.test.server.FilenameUtils.toUnixPath 7 | 8 | class SftpSpec extends AbstractFileTransferSpec { 9 | 10 | def setupSpec() { 11 | server.subsystemFactories = [new SftpSubsystemFactory()] 12 | server.start() 13 | } 14 | 15 | 16 | //FIXME: should be in AbstractFileTransferSpec but put here due to bug of Apache SSHD 17 | def "put(dir) should throw IOException if destination does not exist"() { 18 | given: 19 | def sourceDir = temporaryFolder.newFolder('source') 20 | sourceDir / 'file1' << 'Source Content 1' 21 | def destinationDir = temporaryFolder.newFolder('destination') / 'dir1' 22 | assert !destinationDir.exists() 23 | 24 | when: 25 | ssh.run { 26 | session(ssh.remotes.testServer) { 27 | put from: sourceDir, into: toUnixPath(destinationDir.path) 28 | } 29 | } 30 | 31 | then: 32 | IOException e = thrown() 33 | e.message.contains(toUnixPath(destinationDir.path)) 34 | } 35 | 36 | 37 | def "sftp.mkdir() should fail if directory already exists"() { 38 | given: 39 | def folder = temporaryFolder.newFolder() 40 | 41 | when: 42 | ssh.run { 43 | session(ssh.remotes.testServer) { 44 | sftp { 45 | mkdir folder.path 46 | } 47 | } 48 | } 49 | 50 | then: 51 | SftpException e = thrown() 52 | e.message.contains('SFTP MKDIR') 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/hostkey_ecdsa-sha2-nistp256: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 3 | 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTb2Qpge+BrGw6SKfi2lTzubUPS5FYg 4 | OGJBOV3IgNGJOP4q+lQZV4KQCq+XgtDsL3OxAOFya/sa3dNI90Sh5ZXoAAAAsOBC60DgQu 5 | tAAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp 6 | +LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlle 7 | gAAAAgQhtKq9f2GGDeivbuZ3tPKWtzabtWWkBBMCP79B1WIhcAAAAVaGlkZXRha2VAcmFi 8 | Yml0LmxvY2FsAQID 9 | -----END OPENSSH PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/hostkey_ecdsa-sha2-nistp256.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNvZCmB74GsbDpIp+LaVPO5tQ9LkViA4YkE5XciA0Yk4/ir6VBlXgpAKr5eC0Owvc7EA4XJr+xrd00j3RKHlleg= 2 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/hostkey_ecdsa-sha2-nistp256_another.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCg59Lu5tQaXDLtoGRRnEAG7bjV0yykN+PLB3iFVMBmnW24DFBVow955yBhbS7WeICKLG4pGk03XD+YFA0stxds= 2 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/hostkey_ssh-dss: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIIBugIBAAKBgQDr7tBpE/LrkghfUbe4issLb3tcLTZlpCDxC2Vy1f8E4vkpua8R 3 | eSBSJxuXSzaLnObhGc4s5y38/mR+BS+OLCW2vH57OTXShbINlGF8Y5okjL7tc0eo 4 | owHzrDyJ1U0UeBBCkmmCPJjZna4xNGnakKXkBRRLheHMCpd+X/3HExwO+wIVAIRI 5 | nG3eitk3sM4zhEihAvlsX0R/AoGAJC3mvwWmOmgM+djKPHpdSPx+gRFFV6nIGeay 6 | dJnJZfDpZ9UEVhFGBVIIj7TVHMA8WeXLdEaIZvYfy1sECWTWhJE0aXYzHVYx/N5C 7 | GixHH0oYSPGtmbfRbTJ/itoHHbBlZAMN2mhdiRD/hgmUndmUiNjOfjAzGfSPoTSo 8 | 1eFI3QICgYBRt25Cp4EQ0zX48BLKhRylZ7IgcBi7CvzHNUgvRfQJ78b/7CG10pYn 9 | PjRs4OqM0YkccklGrOXafFwTa+iK8PsBU/4IAy4XnJ5XStE7FrGEfVhlyOr+bi6C 10 | pjXk3opqJjt+5ImXSE6+bZ8JMt86B626Na+wba7QqU6NR/6viRxXRwIUUKFC8L/H 11 | id4VH959BWtteLkCxng= 12 | -----END DSA PRIVATE KEY----- -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/hostkey_ssh-dss.pub: -------------------------------------------------------------------------------- 1 | ssh-dss AAAAB3NzaC1kc3MAAACBAOvu0GkT8uuSCF9Rt7iKywtve1wtNmWkIPELZXLV/wTi+Sm5rxF5IFInG5dLNouc5uEZziznLfz+ZH4FL44sJba8fns5NdKFsg2UYXxjmiSMvu1zR6ijAfOsPInVTRR4EEKSaYI8mNmdrjE0adqQpeQFFEuF4cwKl35f/ccTHA77AAAAFQCESJxt3orZN7DOM4RIoQL5bF9EfwAAAIAkLea/BaY6aAz52Mo8el1I/H6BEUVXqcgZ5rJ0mcll8Oln1QRWEUYFUgiPtNUcwDxZ5ct0Rohm9h/LWwQJZNaEkTRpdjMdVjH83kIaLEcfShhI8a2Zt9FtMn+K2gcdsGVkAw3aaF2JEP+GCZSd2ZSI2M5+MDMZ9I+hNKjV4UjdAgAAAIBRt25Cp4EQ0zX48BLKhRylZ7IgcBi7CvzHNUgvRfQJ78b/7CG10pYnPjRs4OqM0YkccklGrOXafFwTa+iK8PsBU/4IAy4XnJ5XStE7FrGEfVhlyOr+bi6CpjXk3opqJjt+5ImXSE6+bZ8JMt86B626Na+wba7QqU6NR/6viRxXRw== 2 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/hostkey_ssh-rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD4sy1raUVm+CygBJWgf/k/DwHTUazElLyVP5wt5K8mjnFjURNTV7OFuKncW87kY+rrOp3bt3BJifLCAb6jutIPQ6DidyBj5rnUPL5+LzBvFji/gY0RhkU7nvZ3uq9JW6xIj+76gVvLFBzk0uBhwz16skHshEHSNVCNJg72Ow8sRwXIcF0ZUCEJHUDWGbVpjBiFU3/t98D3fu/ztxjMPu/anBmIy9z+go9OndFhSatl0sSAijUAW7sUE1B1J6CJILMg77X/O6bFHpPESnjC8bvwSW3AZzLzpDyCTVF7WjkEsaMciwXPCVwcIyf9OALLs/0rGzrAmEuUQgjnUyh7LHYZVj++vsTnE3BNzbfChI9OeBNyVT6pIpv2X5Z1hNOyFYgbJFxNrNgQD50UcIZswqdEWwjYmM99yZDROYNNsEBPdiQHexJI1Vr4xIkiFgOsvbdAx8lwB/PzUQoEi9Auu7Jf339ntOmWSFKSJDP2W84rBw6vXMmEhrPHCxOeOzCSXhFLbJ8hpSW2YkkiYJJYGCfwkMJdh4p01Jh1meVfx7akjFnl/lu5cDC9Csyz7KyUwSCw6ku9dE8ZVoB2QZS2rfOY7sYZ24rBKL9F/+G3SeoTlElxyFMgmk/7bEPLZndF8VdZyIA66mR41lY6wTlMKAFVkV+b058MY5XfwvRKEKDCTw== 2 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/id_ecdsa: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIIBaAIBAQQg2d776WhdQFfLXan9bhU/bcSaf6HZAv27Js851vDZMImggfowgfcC 3 | AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// 4 | MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr 5 | vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE 6 | axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W 7 | K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 8 | YyVRAgEBoUQDQgAESZT7K4plmvEATrWFhPWybRw4ptSJ7l3rXNc/Dh6GBaLDJli0 9 | SYf6kOaH8xl/EHpbxBTE6P8KVJga1uesO4WbVg== 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/id_ecdsa.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmU+yuKZZrxAE61hYT1sm0cOKbUie5d61zXPw4ehgWiwyZYtEmH+pDmh/MZfxB6W8QUxOj/ClSYGtbnrDuFm1Y= 2 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/id_ecdsa_pass: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDXw8+TMm 3 | PD/3Ucw/UBXMWeAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz 4 | dHAyNTYAAABBBOrnnc2zLhs1gnsU5EmpRoZjhVWwPZb/UG04SfxgE6o1aujA5i89L4fHSr 5 | R6Qwkmede88PQb9wzo7pOy7pOhO7MAAADAHSwb0T4JGfAZH2nPL3YV052PfBDMdAkS80qg 6 | 0QMrejOQTMZKz1rGz8BIgqlXXLdxlO/fmUqBbE0L1D5zYfh1aeC3Iqyuuf/FxBFwW4waCL 7 | qidfrGPWS4ASg3huzfuOxgCxJWObk6cYZEXzVrxKCbRxHx8W+Uvr0lsS/pQ5KAuRg+FuDA 8 | 5XZbU+n5tS6U8tcBWbce4GAOMbgTzYZisInJhM57v6pdK++UWK8s2Mj0xPHBpNtd+9Evc5 9 | AYcUruE4iL 10 | -----END OPENSSH PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/id_ecdsa_pass.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOrnnc2zLhs1gnsU5EmpRoZjhVWwPZb/UG04SfxgE6o1aujA5i89L4fHSrR6Qwkmede88PQb9wzo7pOy7pOhO7M= codespace@codespaces-7f1977 2 | -------------------------------------------------------------------------------- /groovy-ssh/server-integration-test/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gradle-ssh-plugin' 2 | include 'groovy-ssh:core' 3 | include 'groovy-ssh:cli' 4 | include 'groovy-ssh:server-integration-test' 5 | include 'groovy-ssh:os-integration-test' 6 | include 'groovy-ssh:docs' 7 | include 'gradle-ssh-plugin:plugin' 8 | include 'gradle-ssh-plugin:acceptance-test' 9 | --------------------------------------------------------------------------------