// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.gateway.connection
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.ParamsGroup
import com.intellij.openapi.util.SystemInfo
import org.jetbrains.annotations.TestOnly
import java.nio.file.Path
open class ScpCommandLine(
remoteHost: String,
remotePath: String,
recursive: Boolean = false,
port: Int? = null
) {
private val sshOptions = ParamsGroup(SSH_OPTIONS)
private val localPaths = ParamsGroup(LOCAL_PATHS)
private val additionalEnvironment = mutableMapOf<String, String>()
// required because this needs to be last
private val remoteTarget = ParamsGroup(REMOTE_TARGET)
protected val additionalOptions = ParamsGroup(ADDITIONAL_OPTIONS)
init {
if (recursive) {
addSshOption("-r")
}
// TODO: requires openssh7.6+, but al2 only ships 7.4 for fips. probably need to manually run ssh-keyscan
// ideally should be accept-new
addSshOption("-o", "StrictHostKeyChecking=no")
addSshOption("-v")
port?.let {
// unlike SSH, port is capitalized
addSshOption("-P", it.toString())
}
remoteTarget.addParameter("$remoteHost:$remotePath")
}
fun addSshOption(vararg option: String): ScpCommandLine = apply {
sshOptions.addParameters(*option)
}
fun addLocalPaths(vararg path: String): ScpCommandLine = apply {
localPaths.addParameters(*path)
}
fun addLocalPaths(vararg path: Path): ScpCommandLine = apply {
addLocalPaths(*path.map { it.toAbsolutePath().toString() }.toTypedArray())
}
fun withEnvironment(map: Map<String, String>) = apply {
additionalEnvironment.putAll(map)
}
@TestOnly
fun knownHostsLocation(location: Path) = apply {
addSshOption("-o", "UserKnownHostsFile=${location.toAbsolutePath()}")
}
fun constructCommandLine(): GeneralCommandLine {
val commandLine = GeneralCommandLine(resolveExecutable())
.withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE)
commandLine.parametersList.apply {
addParamsGroup(sshOptions)
addParamsGroup(additionalOptions)
addParamsGroup(localPaths)
// needs to be last
addParamsGroup(remoteTarget)
}
commandLine.withEnvironment(additionalEnvironment)
return commandLine
}
private fun resolveExecutable(): String {
if (!SystemInfo.isWindows) {
return "scp"
}
// TODO: make sure "Get-WindowsCapability -Online | ? Name -like 'OpenSSH.Client*'" returns "Installed"
return "${System.getenv("SystemRoot")}\\System32\\OpenSSH\\scp.exe"
}
companion object {
private const val SSH_OPTIONS = "sshOptions"
private const val LOCAL_PATHS = "localPaths"
private const val REMOTE_TARGET = "remoteTarget"
private const val ADDITIONAL_OPTIONS = "sshAdditionalOptions"
}
}