diff --git a/api/src/main/scala/dcos/metronome/api/Binders.scala b/api/src/main/scala/dcos/metronome/api/Binders.scala index 0c8d3b7e..66570b21 100644 --- a/api/src/main/scala/dcos/metronome/api/Binders.scala +++ b/api/src/main/scala/dcos/metronome/api/Binders.scala @@ -3,8 +3,8 @@ package api import dcos.metronome.jobinfo.JobInfo.Embed import dcos.metronome.model.JobId -import play.api.mvc.{ QueryStringBindable, PathBindable } -import mesosphere.marathon.api.v2.Validation.validateOrThrow +import dcos.metronome.utils.Validation.validateOrThrow +import play.api.mvc.{PathBindable, QueryStringBindable} import scala.util.control.NonFatal diff --git a/api/src/main/scala/dcos/metronome/api/RestController.scala b/api/src/main/scala/dcos/metronome/api/RestController.scala index b7c344a7..a8f91b78 100644 --- a/api/src/main/scala/dcos/metronome/api/RestController.scala +++ b/api/src/main/scala/dcos/metronome/api/RestController.scala @@ -2,9 +2,9 @@ package dcos.metronome package api import com.eclipsesource.schema.SchemaValidator -import com.wix.accord.{ Failure, Success, Validator } -import mesosphere.marathon.api.v2.Validation -import play.api.http.{ ContentTypeOf, ContentTypes, Writeable } +import com.wix.accord.{Failure, Success, Validator} +import dcos.metronome.utils.Validation.failureWrites +import play.api.http.{ContentTypeOf, ContentTypes, Writeable} import play.api.libs.json._ import play.api.mvc._ @@ -31,7 +31,7 @@ class RestController(cc: ControllerComponents) extends AbstractController(cc) { def validateObject(a: A): Either[Result, A] = validator(a) match { case Success => Right(a) - case f: Failure => Left(UnprocessableEntity(Validation.failureWrites.writes(f))) + case f: Failure => Left(UnprocessableEntity(failureWrites.writes(f))) } def readObject(jsValue: JsValue): Either[Result, A] = { diff --git a/api/src/main/scala/dcos/metronome/api/v0/controllers/ScheduledJobSpecController.scala b/api/src/main/scala/dcos/metronome/api/v0/controllers/ScheduledJobSpecController.scala index 64f72f14..2930e363 100644 --- a/api/src/main/scala/dcos/metronome/api/v0/controllers/ScheduledJobSpecController.scala +++ b/api/src/main/scala/dcos/metronome/api/v0/controllers/ScheduledJobSpecController.scala @@ -5,7 +5,7 @@ import dcos.metronome.api._ import dcos.metronome.api.v1.models.{ JobSpecFormat => _, _ } import dcos.metronome.jobspec.JobSpecService import dcos.metronome.model.{ JobId, JobRunSpec, JobSpec, ScheduleSpec } -import mesosphere.marathon.api.v2.json.Formats.FormatWithDefault +import dcos.metronome.utils.Formats.FormatWithDefault import mesosphere.marathon.metrics.Metrics import mesosphere.marathon.plugin.auth.{ Authenticator, Authorizer, CreateRunSpec, UpdateRunSpec } import play.api.libs.functional.syntax._ diff --git a/api/src/main/scala/dcos/metronome/api/v1/models/package.scala b/api/src/main/scala/dcos/metronome/api/v1/models/package.scala index e0df7454..c8db2b98 100644 --- a/api/src/main/scala/dcos/metronome/api/v1/models/package.scala +++ b/api/src/main/scala/dcos/metronome/api/v1/models/package.scala @@ -1,7 +1,7 @@ package dcos.metronome package api.v1 -import java.time.{ Instant, ZoneId } +import java.time.{Instant, ZoneId} import java.time.format.DateTimeFormatter import dcos.metronome.api._ @@ -9,19 +9,19 @@ import dcos.metronome.jobinfo.JobInfo import dcos.metronome.jobrun.StartedJobRun import dcos.metronome.model._ import dcos.metronome.scheduler.TaskState -import mesosphere.marathon.SemVer +import dcos.metronome.utils.SemVer import mesosphere.marathon.core.task.Task -import mesosphere.marathon.state.{ Parameter, Timestamp } +import mesosphere.marathon.state.{Parameter, Timestamp} import play.api.libs.functional.syntax._ import play.api.libs.json.Reads._ -import play.api.libs.json.{ Json, _ } +import play.api.libs.json.{Json, _} import scala.collection.JavaConverters._ import scala.concurrent.duration._ package object models { - import mesosphere.marathon.api.v2.json.Formats.FormatWithDefault + import dcos.metronome.utils.Formats.FormatWithDefault implicit val errorFormat: Format[ErrorDetail] = Json.format[ErrorDetail] implicit val unknownJobsFormat: Format[UnknownJob] = Json.format[UnknownJob] diff --git a/jobs/src/main/scala/dcos/metronome/MarathonBuildInfo.scala b/jobs/src/main/scala/dcos/metronome/MarathonBuildInfo.scala index 886f1b6b..64a08d9e 100644 --- a/jobs/src/main/scala/dcos/metronome/MarathonBuildInfo.scala +++ b/jobs/src/main/scala/dcos/metronome/MarathonBuildInfo.scala @@ -2,11 +2,9 @@ package dcos.metronome import java.util.jar.{ Attributes, Manifest } -import mesosphere.marathon.SemVer -import mesosphere.marathon.io.IO -import mesosphere.marathon.stream.Implicits._ +import dcos.metronome.utils.SemVer -import scala.Predef._ +import scala.collection.JavaConverters import scala.util.control.NonFatal case object MarathonBuildInfo { @@ -18,18 +16,29 @@ case object MarathonBuildInfo { * first matching file for the first JAR in the class path. Instead, we need to enumerate through all of the * manifests, and find the one that applies to the Marathon application jar. */ - private lazy val marathonManifestPath: List[java.net.URL] = - getClass().getClassLoader().getResources("META-INF/MANIFEST.MF").toIterator.filter { manifest => - marathonJar.findFirstMatchIn(manifest.getPath).nonEmpty - }.toList + private lazy val marathonManifestPath: List[java.net.URL] = { + val resources = JavaConverters.enumerationAsScalaIterator(getClass().getClassLoader().getResources("META-INF/MANIFEST.MF")) + resources + .filter(manifest => marathonJar.findFirstMatchIn(manifest.getPath).nonEmpty) + .toList + } lazy val manifest: Option[Manifest] = marathonManifestPath match { case Nil => None case List(file) => - val mf = new Manifest() - IO.using(file.openStream) { f => - mf.read(f) + + val closeable = file.openStream + try { + val mf = new Manifest() + mf.read(closeable) Some(mf) + } finally { + try { + closeable.close() + } catch { + case ex: Exception => + // suppress exceptions + } } case otherwise => throw new RuntimeException(s"Multiple marathon JAR manifests returned! ${otherwise}") diff --git a/jobs/src/main/scala/dcos/metronome/MetronomeBuildInfo.scala b/jobs/src/main/scala/dcos/metronome/MetronomeBuildInfo.scala index e636c7de..ce76a064 100644 --- a/jobs/src/main/scala/dcos/metronome/MetronomeBuildInfo.scala +++ b/jobs/src/main/scala/dcos/metronome/MetronomeBuildInfo.scala @@ -2,7 +2,7 @@ package dcos.metronome import java.util.jar.{ Attributes, Manifest } -import mesosphere.marathon.io.IO +import dcos.metronome.utils.SemVer import scala.collection.JavaConverters._ import scala.io.Source @@ -39,10 +39,18 @@ case object MetronomeBuildInfo { lazy val manifest: Option[Manifest] = manifestPath match { case Nil => None case List(file) => - val mf = new Manifest() - IO.using(file.openStream) { f => - mf.read(f) + val closeable = file.openStream + try { + val mf = new Manifest() + mf.read(closeable) Some(mf) + } finally { + try { + closeable.close() + } catch { + case ex: Exception => + // suppress exceptions + } } case otherwise => throw new RuntimeException(s"Multiple metronome JAR manifests returned! $otherwise") @@ -64,6 +72,6 @@ case object MetronomeBuildInfo { lazy val scalaVersion: String = getAttribute("Scala-Version").getOrElse("2.x.x") - lazy val marathonVersion: mesosphere.marathon.SemVer = MarathonBuildInfo.version + lazy val marathonVersion: SemVer = MarathonBuildInfo.version } diff --git a/jobs/src/main/scala/dcos/metronome/MetronomeInfo.scala b/jobs/src/main/scala/dcos/metronome/MetronomeInfo.scala index 1c7c0343..4dafd073 100644 --- a/jobs/src/main/scala/dcos/metronome/MetronomeInfo.scala +++ b/jobs/src/main/scala/dcos/metronome/MetronomeInfo.scala @@ -1,6 +1,6 @@ package dcos.metronome -import mesosphere.marathon.SemVer +import dcos.metronome.utils.SemVer case class MetronomeInfo( version: String, diff --git a/jobs/src/main/scala/dcos/metronome/model/EnvVarValueOrSecret.scala b/jobs/src/main/scala/dcos/metronome/model/EnvVarValueOrSecret.scala index 82be3271..0f7d1ac5 100644 --- a/jobs/src/main/scala/dcos/metronome/model/EnvVarValueOrSecret.scala +++ b/jobs/src/main/scala/dcos/metronome/model/EnvVarValueOrSecret.scala @@ -1,8 +1,10 @@ package dcos.metronome.model -trait EnvVarValueOrSecret +import mesosphere.marathon.plugin.{ EnvVarSecretRef, EnvVarString } -case class EnvVarValue(value: String) extends EnvVarValueOrSecret +trait EnvVarValueOrSecret extends mesosphere.marathon.plugin.EnvVarValue + +case class EnvVarValue(value: String) extends EnvVarValueOrSecret with EnvVarString object EnvVarValue { implicit object playJsonFormat extends play.api.libs.json.Format[EnvVarValue] { @@ -20,7 +22,7 @@ object EnvVarValue { * @param secret The name of the secret to refer to. At runtime, the value of the * secret will be injected into the value of the variable. */ -case class EnvVarSecret(secret: String) extends EnvVarValueOrSecret +case class EnvVarSecret(secret: String) extends EnvVarValueOrSecret with EnvVarSecretRef object EnvVarSecret { implicit val playJsonFormat = play.api.libs.json.Json.format[EnvVarSecret] diff --git a/jobs/src/main/scala/dcos/metronome/model/JobSpec.scala b/jobs/src/main/scala/dcos/metronome/model/JobSpec.scala index 6c690c32..9d8050d3 100644 --- a/jobs/src/main/scala/dcos/metronome/model/JobSpec.scala +++ b/jobs/src/main/scala/dcos/metronome/model/JobSpec.scala @@ -4,10 +4,8 @@ package model import com.wix.accord.Validator import com.wix.accord.dsl._ import dcos.metronome.model.JobRunSpec._ -import dcos.metronome.utils.glue.MarathonConversions -import mesosphere.marathon.api.v2.Validation._ -import mesosphere.marathon -import mesosphere.marathon.plugin.{ ApplicationSpec, NetworkSpec, Secret, VolumeSpec, VolumeMountSpec } +import dcos.metronome.utils.Validation.every +import mesosphere.marathon.plugin.{ApplicationSpec, NetworkSpec, VolumeMountSpec, VolumeSpec} case class JobSpec( id: JobId, @@ -19,8 +17,8 @@ case class JobSpec( override val user: Option[String] = run.user override val acceptedResourceRoles: Set[String] = Set.empty - override val secrets: Map[String, Secret] = MarathonConversions.secretsToMarathon(run.secrets) - override val env: Map[String, marathon.state.EnvVarValue] = MarathonConversions.envVarToMarathon(run.env) + override val secrets: Map[String, mesosphere.marathon.plugin.Secret] = run.secrets + override val env: Map[String, mesosphere.marathon.plugin.EnvVarValue] = run.env override val volumes: Seq[VolumeSpec] = Seq.empty override val networks: Seq[NetworkSpec] = Seq.empty override val volumeMounts: Seq[VolumeMountSpec] = Seq.empty diff --git a/jobs/src/main/scala/dcos/metronome/model/QueuedJobRunInfo.scala b/jobs/src/main/scala/dcos/metronome/model/QueuedJobRunInfo.scala index dbdde2c4..516eaffa 100644 --- a/jobs/src/main/scala/dcos/metronome/model/QueuedJobRunInfo.scala +++ b/jobs/src/main/scala/dcos/metronome/model/QueuedJobRunInfo.scala @@ -1,9 +1,8 @@ package dcos.metronome package model -import dcos.metronome.utils.glue.MarathonConversions -import mesosphere.marathon.plugin.{ ApplicationSpec, NetworkSpec, PathId, Secret, VolumeMountSpec, VolumeSpec } import mesosphere.marathon.plugin +import mesosphere.marathon.plugin.{ ApplicationSpec, NetworkSpec, PathId, Secret, VolumeMountSpec, VolumeSpec } import mesosphere.marathon.state.Timestamp case class QueuedJobRunInfo( @@ -16,7 +15,7 @@ case class QueuedJobRunInfo( lazy val runId: String = id.path.last override val user: Option[String] = run.user override val secrets: Map[String, Secret] = Map.empty - override val env: Map[String, plugin.EnvVarValue] = MarathonConversions.envVarToMarathon(run.env) + override val env: Map[String, plugin.EnvVarValue] = run.env override val labels = Map.empty[String, String] override val volumes: Seq[VolumeSpec] = Seq.empty override val networks: Seq[NetworkSpec] = Seq.empty diff --git a/jobs/src/main/scala/dcos/metronome/model/SecretDef.scala b/jobs/src/main/scala/dcos/metronome/model/SecretDef.scala index 3f7e557b..9c0a29ab 100644 --- a/jobs/src/main/scala/dcos/metronome/model/SecretDef.scala +++ b/jobs/src/main/scala/dcos/metronome/model/SecretDef.scala @@ -4,7 +4,7 @@ package dcos.metronome.model * A secret declaration * @param source reference to a secret which will be injected with a value from the secret store. */ -case class SecretDef(source: String) +case class SecretDef(source: String) extends mesosphere.marathon.plugin.Secret object SecretDef { import play.api.libs.json.Reads._ diff --git a/jobs/src/main/scala/dcos/metronome/utils/Formats.scala b/jobs/src/main/scala/dcos/metronome/utils/Formats.scala new file mode 100644 index 00000000..bf7767d6 --- /dev/null +++ b/jobs/src/main/scala/dcos/metronome/utils/Formats.scala @@ -0,0 +1,12 @@ +package dcos.metronome.utils + +import play.api.libs.json.OFormat +import play.api.libs.functional.syntax._ + +object Formats { + + implicit class FormatWithDefault[A](val m: OFormat[Option[A]]) extends AnyVal { + def withDefault(a: A): OFormat[A] = m.inmap(_.getOrElse(a), Some(_)) + } + +} diff --git a/jobs/src/main/scala/dcos/metronome/utils/SemVer.scala b/jobs/src/main/scala/dcos/metronome/utils/SemVer.scala new file mode 100644 index 00000000..70366628 --- /dev/null +++ b/jobs/src/main/scala/dcos/metronome/utils/SemVer.scala @@ -0,0 +1,45 @@ +package dcos.metronome.utils + +/** + * Models a semantic version ( Copied from Marathon) + */ +case class SemVer(major: Int, minor: Int, build: Int, commit: Option[String]) extends Ordered[SemVer] { + override def toString(): String = commit match { + case Some(c) => s"$major.$minor.$build-$c" + case None => s"$major.$minor.$build" + } + + override def compare(that: SemVer): Int = Seq( + major.compare(that.major), + minor.compare(that.minor), + build.compare(that.build), + commit.getOrElse("").compare(that.commit.getOrElse(""))) + .find(_ != 0) + .getOrElse(0) +} + +object SemVer { + val empty = SemVer(0, 0, 0, None) + + // Matches e.g. 1.7.42 or v1.7.42 + val versionPattern = """^(\d+)\.(\d+)\.(\d+)$""".r + + // Matches e.g. 1.7.42-deadbeef or v1.7.42-deadbeef + val versionCommitPattern = """^v?(\d+)\.(\d+)\.(\d+)-(\w+)$""".r + + /** + * Create SemVer from string which has the form if 1.7.42-deadbeef. + */ + def apply(version: String): SemVer = + version match { + case versionCommitPattern(major, minor, build, commit) => + apply(major.toInt, minor.toInt, build.toInt, Some(commit)) + case versionPattern(major, minor, build) => + apply(major.toInt, minor.toInt, build.toInt) + case _ => + throw new IllegalArgumentException(s"Could not parse version $version.") + } + + def apply(major: Int, minor: Int, build: Int): SemVer = + apply(major, minor, build, None) +} diff --git a/jobs/src/main/scala/dcos/metronome/utils/Validation.scala b/jobs/src/main/scala/dcos/metronome/utils/Validation.scala new file mode 100644 index 00000000..54336f23 --- /dev/null +++ b/jobs/src/main/scala/dcos/metronome/utils/Validation.scala @@ -0,0 +1,124 @@ +package dcos.metronome.utils + +import com.wix.accord.Descriptions.{Description, Explicit, Generic, Indexed, Path} +import com.wix.accord.{Failure, GroupViolation, Result, RuleViolation, Success, Validator, Violation, validate} +import dcos.metronome.Seq +import play.api.libs.json.{Json, Writes} + +/** + * Mostly copied from marathon + */ +object Validation { + + /** + * Is thrown if an object validation is not successful. + * + * TODO(MARATHON-8202) - convert to Rejection + * + * @param obj object which is not valid + * @param failure validation information kept in a Failure object + */ + case class ValidationFailedException(obj: Any, failure: Failure) extends scala.RuntimeException(s"Validation failed: $failure") + + def validateOrThrow[T](t: T)(implicit validator: Validator[T]): T = validate(t) match { + case Success => t + case f: Failure => throw ValidationFailedException(t, f) + } + + /** + * Apply a validation to each member of the provided (ordered) collection, appropriately appending the index of said + * sequence to the validation Path. + * + * You can use this to validate a Set, but this is not recommended as the index of failures will be + * non-deterministic. + * + * Note that wix accord provide the built in `each` DSL keyword. However, this approach does not lend itself well to + * composable validations. One can write, just fine: + * + * model.values.each is notEmpty + * + * But this approach does lend itself well for cases such as this: + * + * model.values is (condition) or every(notEmpty) + * model.values is optional(every(notEmpty)) + * + * @param validator The validation to apply to each element of the collection + */ + implicit def every[T](validator: Validator[T]): Validator[Iterable[T]] = { + new Validator[Iterable[T]] { + override def apply(seq: Iterable[T]): Result = { + seq.zipWithIndex.foldLeft[Result](Success) { + case (accum, (item, index)) => + validator(item) match { + case Success => accum + case Failure(violations) => + val scopedViolations = violations.map { violation => + violation.withPath(Indexed(index.toLong) +: violation.path) + } + accum.and(Failure(scopedViolations)) + } + } + } + } + } + + implicit class RichViolation(v: Violation) { + def withPath(path: Path): Violation = + v match { + case RuleViolation(value, constraint, _) => + RuleViolation(value, constraint, path) + case GroupViolation(value, constraint, children, _) => + GroupViolation(value, constraint, children, path) + } + } + + lazy val failureWrites: Writes[Failure] = Writes { f => + Json.obj( + "message" -> "Object is not valid", + "details" -> { + allViolations(f) + .groupBy(_.path) + .map { + case (path, ruleViolations) => + Json.obj( + "path" -> path, + "errors" -> ruleViolations.map(_.constraint)) + } + }) + } + + /** + * Given a wix accord violation, return a sequence of our own ConstraintViolation model, rendering the wix accord + * paths down to a string representation. + * + * @param result The wix accord validation result + */ + private[this] def allViolations(result: com.wix.accord.Result): Seq[ConstraintViolation] = { + def renderPath(desc: Description): String = desc match { + case Explicit(s) => s"/${s}" + case Generic(s) => s"/${s}" + case Indexed(index) => s"($index)" + case _ => "" + } + def mkPath(path: Path): String = + if (path.isEmpty) + "/" + else + path.map(renderPath).mkString("") + + def collectViolation(violation: Violation, parents: Path): Seq[ConstraintViolation] = { + violation match { + case RuleViolation(_, constraint, path) => Seq(ConstraintViolation(mkPath(parents ++ path), constraint)) + case GroupViolation(_, _, children, path) => children.to[Seq].flatMap(collectViolation(_, parents ++ path)) + } + } + result match { + case Success => Seq.empty + case Failure(violations) => + violations.to[Seq].flatMap(collectViolation(_, Nil)) + } + } + + case class ConstraintViolation(path: String, constraint: String) + +} diff --git a/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonConversions.scala b/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonConversions.scala deleted file mode 100644 index 03eebbb5..00000000 --- a/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonConversions.scala +++ /dev/null @@ -1,19 +0,0 @@ -package dcos.metronome.utils.glue - -import dcos.metronome.model.{ EnvVarSecret, EnvVarValue, EnvVarValueOrSecret, SecretDef } -import mesosphere.marathon - -object MarathonConversions { - - def envVarToMarathon(envVars: Map[String, EnvVarValueOrSecret]): Map[String, marathon.state.EnvVarValue] = { - envVars.mapValues { - case EnvVarValue(value) => marathon.state.EnvVarString(value) - case EnvVarSecret(secret) => marathon.state.EnvVarSecretRef(secret) - } - } - - def secretsToMarathon(secrets: Map[String, SecretDef]): Map[String, marathon.state.Secret] = { - secrets.map { case (name, value) => name -> marathon.state.Secret(value.source) } - } - -} diff --git a/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonImplicits.scala b/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonImplicits.scala index e36bdac6..09a2ed0d 100644 --- a/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonImplicits.scala +++ b/jobs/src/main/scala/dcos/metronome/utils/glue/MarathonImplicits.scala @@ -10,6 +10,7 @@ import mesosphere.marathon.core.readiness.ReadinessCheck import mesosphere.marathon.raml.Resources import mesosphere.marathon.state.{ AppDefinition, BackoffStrategy, Container, FetchUri, PathId, PortDefinition, RunSpec, UpgradeStrategy, VersionInfo, VolumeMount } import mesosphere.marathon.state + import scala.concurrent.duration._ /** @@ -47,7 +48,7 @@ object MarathonImplicits { } implicit class ConstraintSpecToProto(val spec: ConstraintSpec) extends AnyVal { - def toProto: Option[marathon.Protos.Constraint] = { + def toProto: Option[mesosphere.marathon.Protos.Constraint] = { /** * Unfortunately, Metronome has always allowed valueless constraint operators, but they never had any effect. * @@ -59,12 +60,12 @@ object MarathonImplicits { */ spec.value.map { value => val marathonOperator = spec.operator match { - case Operator.Is => marathon.Protos.Constraint.Operator.IS - case Operator.Like => marathon.Protos.Constraint.Operator.LIKE - case Operator.Unlike => marathon.Protos.Constraint.Operator.UNLIKE + case Operator.Is => mesosphere.marathon.Protos.Constraint.Operator.IS + case Operator.Like => mesosphere.marathon.Protos.Constraint.Operator.LIKE + case Operator.Unlike => mesosphere.marathon.Protos.Constraint.Operator.UNLIKE } - val builder = marathon.Protos.Constraint.newBuilder() + val builder = mesosphere.marathon.Protos.Constraint.newBuilder() .setOperator(marathonOperator) .setField(spec.attribute) builder.setValue(value) @@ -110,7 +111,7 @@ object MarathonImplicits { cmd = jobSpec.run.cmd, args = jobSpec.run.args.getOrElse(Seq.empty), user = jobSpec.run.user, - env = MarathonConversions.envVarToMarathon(jobSpec.run.env), + env = envVarToMarathon(jobSpec.run.env), instances = 1, resources = Resources(cpus = jobSpec.run.cpus, mem = jobSpec.run.mem, disk = jobSpec.run.disk, gpus = jobSpec.run.gpus), executor = "//cmd", @@ -131,7 +132,18 @@ object MarathonImplicits { labels = jobSpec.labels, acceptedResourceRoles = Set.empty, versionInfo = VersionInfo.NoVersion, - secrets = MarathonConversions.secretsToMarathon(jobSpec.run.secrets)) + secrets = secretsToMarathon(jobSpec.run.secrets)) + } + + private[this] def envVarToMarathon(envVars: Map[String, EnvVarValueOrSecret]): Map[String, marathon.state.EnvVarValue] = { + envVars.mapValues { + case EnvVarValue(value) => marathon.state.EnvVarString(value) + case EnvVarSecret(secret) => marathon.state.EnvVarSecretRef(secret) + } + } + + private[this] def secretsToMarathon(secrets: Map[String, SecretDef]): Map[String, marathon.state.Secret] = { + secrets.map { case (name, value) => name -> marathon.state.Secret(value.source) } } } } diff --git a/jobs/src/test/scala/dcos/metronome/jobspec/impl/ScheduleSpecTest.scala b/jobs/src/test/scala/dcos/metronome/jobspec/impl/ScheduleSpecTest.scala index a7ffd63d..66d38f28 100644 --- a/jobs/src/test/scala/dcos/metronome/jobspec/impl/ScheduleSpecTest.scala +++ b/jobs/src/test/scala/dcos/metronome/jobspec/impl/ScheduleSpecTest.scala @@ -1,10 +1,10 @@ package dcos.metronome.jobspec.impl -import java.time.{Instant, ZoneId} +import java.time.{ Instant, ZoneId } -import dcos.metronome.model.{ConcurrencyPolicy, CronSpec, ScheduleSpec} +import dcos.metronome.model.{ ConcurrencyPolicy, CronSpec, ScheduleSpec } import dcos.metronome.utils.test.Mockito -import org.scalatest.{FunSuite, GivenWhenThen, Matchers} +import org.scalatest.{ FunSuite, GivenWhenThen, Matchers } import scala.concurrent.duration._ diff --git a/jobs/src/test/scala/dcos/metronome/repository/impl/kv/marshaller/JobHistoryMarshallerTest.scala b/jobs/src/test/scala/dcos/metronome/repository/impl/kv/marshaller/JobHistoryMarshallerTest.scala index 161b422b..c0956f28 100644 --- a/jobs/src/test/scala/dcos/metronome/repository/impl/kv/marshaller/JobHistoryMarshallerTest.scala +++ b/jobs/src/test/scala/dcos/metronome/repository/impl/kv/marshaller/JobHistoryMarshallerTest.scala @@ -1,7 +1,7 @@ package dcos.metronome package repository.impl.kv.marshaller -import java.time.{ Clock, LocalDateTime, ZoneOffset } +import java.time.{ LocalDateTime, ZoneOffset } import dcos.metronome.model._ import mesosphere.marathon.core.task.Task @@ -36,7 +36,7 @@ class JobHistoryMarshallerTest extends FunSuite with Matchers { successCount = 1337, failureCount = 31337, lastSuccessAt = Some(LocalDateTime.parse("2014-09-06T08:50:12.000").toInstant(ZoneOffset.UTC)), - lastFailureAt = Some(Clock.systemUTC().instant()), + lastFailureAt = Some(LocalDateTime.parse("2014-09-06T06:46:11.553").toInstant(ZoneOffset.UTC)), successfulRuns = Seq(successfulJobRunInfo), failedRuns = Seq(finishedJobRunInfo)) }