Created
January 19, 2016 16:42
-
-
Save 4lex1v/18d9f4b0294d70678d83 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io.alterstack.build | |
import sbt._ | |
import Keys._ | |
import com.typesafe.config.{Config, ConfigFactory} | |
import com.decodified.scalassh.{ | |
Command => SshCommand, | |
CommandResult => SshCommandResult, | |
_ | |
} | |
import BuildUtilz.ConfigUtilz._ | |
import sbt.Configuration | |
object Deployment { | |
/** DEPLOYMENT RESULT */ | |
sealed trait DeployResult { | |
def fold[X](nd: String => X, d: => X): X = this match { | |
case NotDeployed(r) => nd(r) | |
case Deployed => d | |
} | |
} | |
case class NotDeployed(reason: String) extends DeployResult | |
case object Deployed extends DeployResult | |
/** | |
* DEPLOYMENT SCRIPT | |
* @param before - should be executed before the project would be deployed to the server | |
* @param after - should be executed after the project would be deployed to the server | |
*/ | |
case class DeployScripts(before: List[SshCommand], after: List[SshCommand]) | |
object DeployScripts { | |
def fromConfig(config: Config): DeployScripts = { | |
def extract(name: String): List[SshCommand] = { | |
val commands = config getOptStrList name | |
commands.getOrElse(Nil).map(SshCommand(_)) | |
} | |
DeployScripts(extract("before"), extract("after")) | |
} | |
def runScript(scripts: List[SshCommand]) | |
(config: HostConfig) | |
(implicit log: Logger) | |
: Validated[List[Validated[SshCommandResult]]] = { | |
SSH(config.hostName, config) { client => | |
scripts map { script => | |
log.info(s"Executing script: ${ script.command }") | |
client.exec(script) match { | |
case right @ Right(result) => | |
log.success(s"Result: ${ | |
val msg = result.stdOutAsString() | |
if (msg.isEmpty) "no output" | |
else msg.split("\n").take(10).mkString("\n") + "\n\t......" | |
}"); right | |
case left @ Left(ex) => log.error(s"${script.command} resulted to an $ex"); left | |
} | |
} | |
} | |
} | |
} | |
/** | |
* DEPLOYMENT CONFIGURATION for current project | |
*/ | |
case class SshConfig(user: String, host: String, keyfile: File, path: String) | |
object SshConfig { | |
def fromConfig(config: Config): SshConfig = { | |
SshConfig("root", config.getString("host"), file(config.getString("keyfile")), config.getString("path")) | |
} | |
implicit def toHostConfig(conf: SshConfig): HostConfig = { | |
HostConfig(PublicKeyLogin(conf.user, conf.keyfile.getAbsolutePath), conf.host) | |
} | |
implicit def toResolver(conf: SshConfig): Resolver = { | |
val pattern = Patterns("dist/[artifact]/[revision]/[artifact].[ext]") | |
Resolver.ssh(conf.host, conf.host, conf.path)(pattern).as("root", conf.keyfile) | |
} | |
} | |
case class DeployConfig(scripts: DeployScripts, sshConfig: SshConfig) | |
object DeploymentKeys { | |
val Deploy = new Configuration("deploy") describedAs "Configuration for project deployment" | |
/** Build deploy configuration */ | |
val deployConfigFolder = settingKey[File]("Folder where different deployment configurations are stored") | |
val deployProjects = settingKey[List[(ProjectRef, File)]]("List of projects, which can be deployed, and associated configuration file") | |
/** Project deploy configuration */ | |
val deployable = settingKey[Boolean]("Can this project be deployed (has deploy config)") | |
val deployConfigFile = settingKey[Option[File]]("File with deploy configuration for the current project") | |
val deployConfig = settingKey[Option[DeployConfig]]("Deploy configuration for the current project") | |
val deploy = taskKey[DeployResult]("Deploy current project to the production server") | |
} | |
object DeploymentModule { | |
import DeploymentKeys._ | |
import Distribution.DistributionKeys | |
private val rootDir = baseDirectory in ThisBuild | |
lazy val buildDeployConfig = Seq( | |
deployConfigFolder in ThisBuild := rootDir(_ / "configs" / "deploy").value, | |
deployProjects in ThisBuild := resolveProjectsForDeploy.value, | |
publishTo := (deployConfig in Deploy).value.map(_.sshConfig) | |
) | |
lazy val deploymentSettings = inConfig(Deploy) { | |
Seq( | |
deployable := deployProjects.value.contains(thisProjectRef.value), | |
deployConfigFile := currentProjectConfigurationFile.value, | |
deployConfig := currentProjectDeployConfiguration.value, | |
deploy := deployProject.value | |
) | |
} | |
private lazy val currentProjectConfigurationFile = Def.setting { | |
val currentProject = thisProjectRef.value | |
val configs = (deployProjects in ThisBuild).value | |
configs find { _._1 == currentProject } map { _._2 } | |
} | |
private lazy val currentProjectDeployConfiguration = Def.setting { | |
(deployConfigFile in Deploy).value map { configFile => | |
val configuration = ConfigFactory.parseFile(configFile).resolve() | |
val scripts = DeployScripts fromConfig configuration.getConfig("deploy") | |
val sshConfig = SshConfig fromConfig configuration | |
new DeployConfig(scripts, sshConfig) | |
} | |
} | |
private lazy val resolveProjectsForDeploy = Def.setting { | |
val folder = (deployConfigFolder in ThisBuild).value | |
val allProjects = loadedBuild.value.allProjectRefs.map(_._1) | |
val configs = for { | |
file <- folder.listFiles | |
if file.name.endsWith(".conf") | |
name = file.name.takeWhile(_ != '.') | |
project = allProjects find { _.project == name } getOrElse { | |
sys error s"Project $name doesn't exists" | |
} | |
} yield project -> file | |
configs.toList | |
} | |
private lazy val deployProject = Def.task { | |
implicit val log: Logger = streams.value.log | |
val project = thisProject.value | |
def deploy(config: DeployConfig): DeployResult = { | |
import DeployScripts.runScript | |
import config._ | |
def error(msg: String): DeployResult = NotDeployed(msg) | |
log.info(s"Start deploying project: ${ project.id }") | |
val beforeScriptExecutionResult = { | |
log.info("Executing Before deployment scripts") | |
runScript(scripts.before)(sshConfig) | |
} | |
val deployResult: DeployResult = { | |
beforeScriptExecutionResult match { | |
case Left(ex) => error(ex) | |
case Right(_) => | |
Project.runTask(publish in LocalProject(name.value), state.value) match { | |
case Some((_, Value(_))) => Deployed | |
case Some((_, Inc(incomplete))) => | |
(incomplete.directCause, incomplete.message) match { | |
case (Some(ex), _) => error(ex.getMessage) | |
case (_, Some(msg)) => error(msg) | |
case failed => error(s"Failed to deploy with $failed") | |
} | |
case failed => error(s"Failed to deploy with $failed") | |
} | |
} | |
} | |
deployResult.fold[DeployResult](NotDeployed, { | |
log.info("Executing After deployment scripts") | |
runScript(scripts.after)(sshConfig) match { | |
case Left(er) => error(er) | |
case Right(result) => | |
result foreach { commandResult => | |
if (commandResult.isLeft) { | |
log.error(s"Failed ssh command: $commandResult") | |
} | |
} | |
Deployed | |
} | |
}) | |
} | |
val ifNoConfig: DeployResult = NotDeployed("No destination specified, please provide configuration in deployment.conf file") | |
(deployConfig in Deploy).value.fold(ifNoConfig)(deploy) | |
} | |
def distribution(artifName: String) = addArtifact(Artifact(s"$artifName-dist", "zip", "zip"), DistributionKeys.pack) | |
def deploySettings(artifName: String): Seq[Setting[_]] = distribution(artifName) ++ buildDeployConfig ++ deploymentSettings | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment