Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wip/1026 validate task #1124

Merged
merged 13 commits into from
Sep 11, 2018
11 changes: 7 additions & 4 deletions src/main/scala/com/typesafe/sbt/PackagerPlugin.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.typesafe.sbt

import packager._

import debian.DebianPlugin.autoImport.genChanges
import universal.UniversalPlugin.autoImport.{packageXzTarball, packageZipTarball}
import com.typesafe.sbt.packager.Keys.{packageXzTarball, packageZipTarball, validatePackage, validatePackageValidators}
import com.typesafe.sbt.packager.validation.Validation
import sbt._
import sbt.Keys.{name, normalizedName, packageBin}
import sbt.Keys.{name, normalizedName, packageBin, streams}

/**
* == SBT Native Packager Plugin ==
Expand Down Expand Up @@ -99,7 +99,10 @@ object SbtNativePackager extends AutoPlugin {
packageSummary := name.value,
packageName := normalizedName.value,
executableScriptName := normalizedName.value,
maintainerScripts := Map()
maintainerScripts := Map(),
// no validation by default
validatePackageValidators := Seq.empty,
validatePackage := Validation.runAndThrow(validatePackageValidators.value, streams.value.log)
)

object packageArchetype {
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/com/typesafe/sbt/packager/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ object Keys
with archetypes.systemloader.SystemloaderKeys
with archetypes.scripts.BashStartScriptKeys
with archetypes.scripts.BatStartScriptKeys
with validation.ValidationKeys
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.typesafe.sbt.packager.archetypes.TemplateWriter
import com.typesafe.sbt.packager.linux.LinuxPlugin.Users
import com.typesafe.sbt.packager.linux.{LinuxFileMetaData, LinuxPackageMapping, LinuxPlugin, LinuxSymlink}
import com.typesafe.sbt.packager.universal.Archives
import com.typesafe.sbt.packager.validation._
import com.typesafe.sbt.packager.{chmod, Hashing, SettingsHelper}
import sbt.Keys._
import sbt._
Expand Down Expand Up @@ -89,6 +90,11 @@ object DebianPlugin extends AutoPlugin with DebianNativePackaging {
packageDescription in Debian := (packageDescription in Linux).value,
packageSummary in Debian := (packageSummary in Linux).value,
maintainer in Debian := (maintainer in Linux).value,
validatePackageValidators in Debian := Seq(
nonEmptyMappings((linuxPackageMappings in Debian).value.flatMap(_.mappings)),
filesExist((linuxPackageMappings in Debian).value.flatMap(_.mappings)),
checkMaintainer((maintainer in Debian).value, asWarning = false)
),
// override the linux sourceDirectory setting
sourceDirectory in Debian := sourceDirectory.value,
/* ==== Debian configuration settings ==== */
Expand Down
59 changes: 59 additions & 0 deletions src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.typesafe.sbt.packager.universal.UniversalPlugin
import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport.stage
import com.typesafe.sbt.SbtNativePackager.Universal
import com.typesafe.sbt.packager.Compat._
import com.typesafe.sbt.packager.validation._
import com.typesafe.sbt.packager.{MappingsHelper, Stager}

import scala.sys.process.Process
Expand Down Expand Up @@ -154,6 +155,12 @@ object DockerPlugin extends AutoPlugin {
daemonUser := "daemon",
daemonGroup := daemonUser.value,
defaultLinuxInstallLocation := "/opt/docker",
validatePackageValidators := Seq(
nonEmptyMappings((mappings in Docker).value),
filesExist((mappings in Docker).value),
validateExposedPorts(dockerExposedPorts.value, dockerExposedUdpPorts.value),
validateDockerVersion(dockerVersion.value)
),
dockerPackageMappings := MappingsHelper.contentOf(sourceDirectory.value),
dockerGenerateConfig := generateDockerConfig(dockerCommands.value, stagingDirectory.value)
)
Expand Down Expand Up @@ -410,4 +417,56 @@ object DockerPlugin extends AutoPlugin {
log.info("Published image " + tag)
}

private[this] def validateExposedPorts(ports: Seq[Int], udpPorts: Seq[Int]): Validation.Validator = () => {
if (ports.isEmpty && udpPorts.isEmpty) {
List(
ValidationWarning(
description = "There are no exposed ports for your docker image",
howToFix = """| Configure the `dockerExposedPorts` or `dockerExposedUdpPorts` setting. E.g.
|
| // standard tcp ports
| dockerExposedPorts ++= Seq(9000, 9001)
|
| // for udp ports
| dockerExposedUdpPorts += 4444
""".stripMargin
)
)
} else {
List.empty
}
}

private[this] def validateDockerVersion(dockerVersion: Option[DockerVersion]): Validation.Validator = () => {
dockerVersion match {
case Some(_) => List.empty
case None =>
List(
ValidationWarning(
description =
"sbt-native-packager wasn't able to identify the docker version. Some features may not be enabled",
howToFix = """|sbt-native packager tries to parse the `docker version` output. This can fail if
|
| - the output has changed:
| $ docker version --format '{{.Server.Version}}'
|
| - no `docker` executable is available
| $ which docker
|
| - you have not the required privileges to run `docker`
|
|You can display the parsed docker version in the sbt console with:
|
| $ sbt show dockerVersion
|
|As a last resort you could hard code the docker version, but it's not recommended!!
|
| import com.typesafe.sbt.packager.docker.DockerVersion
| dockerVersion := Some(DockerVersion(17, 5, 0, Some("ce"))
""".stripMargin
)
)
}
}

}
8 changes: 7 additions & 1 deletion src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.typesafe.sbt.packager.rpm

import sbt._
import sbt.Keys.{isSnapshot, name, packageBin, sourceDirectory, streams, target, version}
import sbt.Keys._
import java.nio.charset.Charset

import com.typesafe.sbt.SbtNativePackager.Linux
import com.typesafe.sbt.packager.SettingsHelper
import com.typesafe.sbt.packager.Keys._
import com.typesafe.sbt.packager.linux._
import com.typesafe.sbt.packager.Compat._
import com.typesafe.sbt.packager.validation._

/**
* Plugin containing all generic values used for packaging rpms.
Expand Down Expand Up @@ -101,6 +102,11 @@ object RpmPlugin extends AutoPlugin {
executableScriptName in Rpm := (executableScriptName in Linux).value,
rpmDaemonLogFile := s"${(packageName in Linux).value}.log",
daemonStdoutLogFile in Rpm := Some(rpmDaemonLogFile.value),
validatePackageValidators in Rpm := Seq(
nonEmptyMappings((linuxPackageMappings in Rpm).value.flatMap(_.mappings)),
filesExist((linuxPackageMappings in Rpm).value.flatMap(_.mappings)),
checkMaintainer((maintainer in Rpm).value, asWarning = false)
),
// override the linux sourceDirectory setting
sourceDirectory in Rpm := sourceDirectory.value,
packageArchitecture in Rpm := "noarch",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import sbt.Keys._
import Archives._
import com.typesafe.sbt.SbtNativePackager
import com.typesafe.sbt.packager.Keys._
import com.typesafe.sbt.packager.validation._
import com.typesafe.sbt.packager.{SettingsHelper, Stager}
import sbt.Keys.TaskStreams

Expand Down Expand Up @@ -50,7 +51,6 @@ object UniversalPlugin extends AutoPlugin {
// For now, we provide delegates from dist/stage to universal...
dist := (dist in Universal).value,
stage := (stage in Universal).value,
// TODO - New default to naming, is this right?
// TODO - We may need to do this for UniversalSrcs + UnviersalDocs
name in Universal := name.value,
name in UniversalDocs := (name in Universal).value,
Expand Down Expand Up @@ -80,6 +80,7 @@ object UniversalPlugin extends AutoPlugin {
)
) ++ Seq(
sourceDirectory in config := sourceDirectory.value / config.name,
validatePackageValidators in config := validatePackageValidators.value,
target in config := target.value / config.name
)

Expand Down Expand Up @@ -107,25 +108,25 @@ object UniversalPlugin extends AutoPlugin {
inConfig(config)(
Seq(
universalArchiveOptions in packageKey := Nil,
mappings in packageKey := checkMappings(mappings.value),
mappings in packageKey := mappings.value,
packageKey := packager(
target.value,
packageName.value,
(mappings in packageKey).value,
topLevelDirectory.value,
(universalArchiveOptions in packageKey).value
)
),
validatePackageValidators in packageKey := (validatePackageValidators in config).value ++ Seq(
nonEmptyMappings((mappings in packageKey).value),
filesExist((mappings in packageKey).value),
checkMaintainer((maintainer in packageKey).value, asWarning = true)
),
validatePackage in packageKey := Validation
.runAndThrow(validatePackageValidators.in(config, packageKey).value, streams.value.log),
packageKey := packageKey.dependsOn(validatePackage in packageKey).value
)
)

/** check that all mapped files actually exist */
private[this] def checkMappings(mappings: Seq[(File, String)]): Seq[(File, String)] =
mappings collect {
case (f, p) =>
if (f.exists) (f, p)
else sys.error("Mapped file " + f + " does not exist.")
}

/** Finds all sources in a source directory. */
private[this] def findSources(sourceDir: File): Seq[(File, String)] =
((PathFinder(sourceDir) ** AllPassFilter) --- sourceDir).pair(file => IO.relativize(sourceDir, file))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.typesafe.sbt.packager.validation

import sbt.Logger

/**
* Validation result.
*
* @param errors all errors that were found during the validation
* @param warnings all warnings that were found during the validation
*/
final case class Validation(errors: List[ValidationError], warnings: List[ValidationWarning])

object Validation {

/**
* A validator is a function that returns a list of validation results.
*
*
* @example Usually a validator is a function that captures some setting or task value, e.g.
* {{{
* validatePackageValidators += {
* val universalMappings = (mappings in Universal).value
* () => {
* if (universalMappings.isEmpty) List(ValidationError(...)) else List.empt
* }
* }
* }}}
*
* The `validation` package object contains various standard validators.
*
*/
type Validator = () => List[ValidationResult]

/**
*
* @param validators a list of validators that produce a `Validation` result
* @return aggregated result of all validator function
*/
def apply(validators: Seq[Validator]): Validation = validators.flatMap(_.apply()).foldLeft(Validation(Nil, Nil)) {
case (validation, error: ValidationError) => validation.copy(errors = validation.errors :+ error)
case (validation, warning: ValidationWarning) => validation.copy(warnings = validation.warnings :+ warning)
}

/**
* Runs a list of validators and throws an exception after printing all
* errors and warnings with the provided logger.
*
* @param validators a list of validators that produce the validation result
* @param log used to print errors and warnings
*/
def runAndThrow(validators: Seq[Validator], log: Logger): Unit = {
val Validation(errors, warnings) = apply(validators)

warnings.zipWithIndex.foreach {
case (warning, i) =>
log.warn(s"[${i + 1}] ${warning.description}")
log.warn(warning.howToFix)
}

errors.zipWithIndex.foreach {
case (error, i) =>
log.error(s"[${i + 1}] ${error.description}")
log.error(error.howToFix)
}

if (errors.nonEmpty) {
sys.error(s"${errors.length} error(s) found")
}

log.success("All package validations passed")
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.typesafe.sbt.packager.validation

import sbt._

trait ValidationKeys {

/**
* A task that implements various validations for a format.
* Example usage:
* - `sbt universal:packageBin::validatePackage`
* - `sbt debian:packageBin::validatePackage`
*
*
* Each format should implement it's own validate.
* Implemented in #1026
*/
val validatePackage = taskKey[Unit]("validates the package configuration")

val validatePackageValidators = taskKey[Seq[Validation.Validator]]("validator functions")
}

object ValidationKeys extends ValidationKeys
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.typesafe.sbt.packager.validation

sealed trait ValidationResult {

/**
* Human readable and understandable description of the validation result.
*/
val description: String

/**
* Help text on how to fix the issue.
*/
val howToFix: String

}

final case class ValidationError(description: String, howToFix: String) extends ValidationResult
final case class ValidationWarning(description: String, howToFix: String) extends ValidationResult
Loading