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

Finish migration from Scalaz to Cats #75

Merged
merged 11 commits into from
Jun 2, 2018
12 changes: 5 additions & 7 deletions core/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ libraryDependencies ++= Seq(
"co.fs2" %% "fs2-core" % V.fs2,
"co.fs2" %% "fs2-io" % V.fs2,
"co.fs2" %% "fs2-scodec" % V.fs2,
"org.typelevel" %% "cats-effect" % V.catsEffect,
"org.typelevel" %% "cats-core" % V.cats,
"org.typelevel" %% "cats-effect" % V.catsEffect,
"org.typelevel" %% "cats-free" % V.cats,
"io.verizon.helm" %% "core" % "3.0.82",
"io.verizon.helm" %% "http4s" % "3.0.82",
"io.verizon.knobs" %% "core" % V.knobs,
"io.verizon.journal" %% "core" % V.journal,
"io.verizon.quiver" %% "core" % "5.4.11",
"io.verizon.delorean" %% "core" % "1.1.37",
"io.verizon.quiver" %% "core" % "7.0.19",
"org.yaml" % "snakeyaml" % "1.16",
"io.argonaut" %% "argonaut" % "6.2.1",
"io.argonaut" %% "argonaut-cats" % "6.2.1",
Expand All @@ -46,9 +45,8 @@ libraryDependencies ++= Seq(
"org.flywaydb" % "flyway-core" % "3.2.1",
"net.databinder.dispatch" %% "dispatch-core" % "0.11.2",
"ca.mrvisser" %% "sealerate" % "0.0.4",
"org.scalaz" %% "scalaz-scalacheck-binding" % V.scalaz % "test",
"org.typelevel" %% "cats-kernel-laws" % V.cats % "test",
"org.apache.commons" % "commons-email" % "1.3.3",
"com.github.alexarchambault" %% "scalacheck-shapeless_1.12" % "0.3.1" % "test",
"com.amazonaws" % "aws-java-sdk-autoscaling" % "1.11.25",
"com.amazonaws" % "aws-java-sdk-elasticloadbalancing" % "1.11.25",
"com.google.guava" % "guava" % "20.0",
Expand Down Expand Up @@ -81,6 +79,6 @@ scalacOptions in (Compile, doc) ++= Seq(
"-no-link-warnings" // Suppresses problems with Scaladoc @throws links
)

scalaTestVersion := "2.2.6"
scalaTestVersion := "3.0.5"

scalaCheckVersion := "1.12.5"
scalaCheckVersion := "1.13.5"
31 changes: 14 additions & 17 deletions core/src/main/scala/Actionable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,14 @@ import nelson.notifications.Notify
import nelson.storage.{StoreOp, StoreOpF}

import cats.~>
import cats.data.{Kleisli, OptionT}
import cats.effect.IO
import nelson.CatsHelpers._
import cats.implicits._

import io.prometheus.client.Counter

import java.time.Instant

import scalaz.{~> => _, _}
import scalaz.Scalaz._

/**
* An Actionable is something that can be "acted" upon in the context
* of a datacenter and namespace. In Nelson this typically means deploying
Expand Down Expand Up @@ -63,12 +61,12 @@ object Actionable {
(for {
u <- OptionT(StoreOp.getUnit(unit.name, version))
n <- OptionT(StoreOp.getNamespace(dc.name, ns.name)) // gets a Datacetner.Namespace
id <- StoreOp.createDeployment(u.id, hash, n, unit.workflow.name, plan.name, policy.name).liftM[OptionT]
_ <- plan.environment.resources.map { case (name, uri) =>
id <- OptionT.liftF(StoreOp.createDeployment(u.id, hash, n, unit.workflow.name, plan.name, policy.name))
_ <- OptionT.liftF(plan.environment.resources.map { case (name, uri) =>
StoreOp.createDeploymentResource(id, name, uri)
}.toList.sequence.liftM[OptionT]
_ <- StoreOp.createDeploymentExpiration(id, exp).liftM[OptionT]
} yield id).run
}.toList.sequence)
_ <- OptionT.liftF(StoreOp.createDeploymentExpiration(id, exp))
} yield id).value
}

def action(unit: UnitDef @@ Versioned): Kleisli[IO, (NelsonConfig,ActionConfig), Unit] =
Expand All @@ -91,19 +89,18 @@ object Actionable {
()
}

def deploy(id: ID)(t: WorkflowOp ~> IO): IO[Throwable \/ Unit] =
def deploy(id: ID)(t: WorkflowOp ~> IO): IO[Either[Throwable, Unit]] =
unitw.workflow.deploy(id, hash, unit, plan, dc, ns).foldMap(t).attempt.flatMap(_.fold(
e => (Workflow.syntax.status(id, DeploymentStatus.Failed,
s"workflow failed because: ${e.getMessage}").foldMap(t) *>
incCounter(deployFailureCounter)).attempt.map(_.toDisjunction),
incCounter(deployFailureCounter)).attempt,
s => (Notify.sendDeployedNotifications(unit, actionConfig)(cfg) *>
incCounter(deploySuccessCounter)).attempt.map(_.toDisjunction)
incCounter(deploySuccessCounter)).attempt
))

create(unit, dc, ns, plan, hash, exp, policy).foldMap(cfg.storage).flatMap(_.cata(
some = id => deploy(id)(dc.workflow).map(_ => ()),
none = incCounter(deployFailureCounter)
))
create(unit, dc, ns, plan, hash, exp, policy).foldMap(cfg.storage).flatMap(_.fold(incCounter(deployFailureCounter)) { id =>
deploy(id)(dc.workflow).map(_ => ())
})
}
}

Expand Down Expand Up @@ -146,7 +143,7 @@ object Actionable {
lbd <- StoreOp.findLoadbalancerDeployment(lb.name, major, nsd.id).foldMap(cfg.storage)

// if loadbalancer exists resize, otherwise create
_ <- lbd.cata(l => resize(l)(t), launch(id, sn, lb.routes, nsd.id)(t))
_ <- lbd.fold(launch(id, sn, lb.routes, nsd.id)(t))(l => resize(l)(t))
} yield ()
}
}
Expand Down
34 changes: 0 additions & 34 deletions core/src/main/scala/BiggerApplies.scala

This file was deleted.

104 changes: 33 additions & 71 deletions core/src/main/scala/CatsHelpers.scala
Original file line number Diff line number Diff line change
@@ -1,88 +1,22 @@
package nelson

import cats.Monad
import cats.arrow.FunctionK
import cats.data.{Validated, ValidatedNel}
import cats.free.Free
import cats.Eval
import cats.effect.{Effect, IO, Timer}
import cats.free.Cofree
import cats.syntax.functor._
import cats.syntax.monadError._

import fs2.{Pipe, Sink, Stream}

import quiver.{Context, Decomp, Graph}

import java.util.concurrent.{ScheduledExecutorService, TimeoutException}

import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration
import scala.collection.immutable.{Stream => SStream}

object CatsHelpers {
implicit val catsIOScalazInstances: scalaz.Monad[IO] with scalaz.Catchable[IO] =
new scalaz.Monad[IO] with scalaz.Catchable[IO] {
def bind[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa.flatMap(f)
def point[A](a: => A): IO[A] = IO(a)

def attempt[A](fa: IO[A]): IO[scalaz.\/[Throwable, A]] = fa.attempt.map {
case Left(a) => scalaz.-\/(a)
case Right(b) => scalaz.\/-(b)
}
def fail[A](err: Throwable): IO[A] = IO.raiseError(err)
}

implicit def catsFreeScalazInstances[F[_]]: scalaz.Monad[Free[F, ?]] =
new scalaz.Monad[Free[F, ?]] {
def bind[A, B](fa: Free[F, A])(f: A => Free[F, B]): Free[F, B] = fa.flatMap(f)
def point[A](a: => A): Free[F, A] = Free.pure(a)
}

implicit def scalazEitherCatsInstances[L]: Monad[scalaz.\/[L, ?]] =
new Monad[scalaz.\/[L, ?]] {
def flatMap[A, B](fa: scalaz.\/[L, A])(f: A => scalaz.\/[L, B]): scalaz.\/[L, B] =
fa.flatMap(f)
def pure[A](a: A): scalaz.\/[L, A] = scalaz.\/.right(a)

@annotation.tailrec
def tailRecM[A, B](a: A)(f: A => scalaz.\/[L, Either[A, B]]): scalaz.\/[L, B] =
f(a) match {
case left@scalaz.-\/(_) => left.asInstanceOf[scalaz.\/[L, B]] // yolo
case scalaz.\/-(e) => e match {
case Left(a) => tailRecM(a)(f)
case right@Right(b) => scalaz.\/.right(b)
}
}
}

implicit class NelsonEnrichedEither[A, B](val either: Either[A, B]) extends AnyVal {
def toDisjunction: scalaz.\/[A, B] = either match {
case Left(a) => scalaz.-\/(a)
case Right(b) => scalaz.\/-(b)
}
}

implicit class NelsonEnrichedDisjunction[A, B](val either: scalaz.\/[A, B]) extends AnyVal {
def toValidatedNel: ValidatedNel[A, B] =
either.fold(Validated.invalidNel, Validated.valid)

def toValidated: Validated[A, B] =
either.fold(Validated.invalid, Validated.valid)
}

implicit class NelsonEnrichedValidated[A, B](val validated: Validated[A, B]) extends AnyVal {
def toDisjunction: scalaz.\/[A, B] =
validated.fold(scalaz.\/.left, scalaz.\/.right)
}

implicit class NelsonEnrichedScalazFunctionK[F[_], G[_]](val functionK: scalaz.~>[F, G]) extends AnyVal {
def asCats: FunctionK[F, G] = new FunctionK[F, G] {
def apply[A](fa: F[A]): G[A] = functionK(fa)
}
}

implicit class NelsonEnrichedCatsFunctionK[F[_], G[_]](val functionK: FunctionK[F, G]) extends AnyVal {
def asScalaz: scalaz.~>[F, G] = new scalaz.~>[F, G] {
def apply[A](fa: F[A]): G[A] = functionK(fa)
}
}

implicit class NelsonEnrichedIO[A](val io: IO[A]) extends AnyVal {
/** Run `other` if this IO fails */
def or(other: IO[A]): IO[A] = io.attempt.flatMap {
Expand Down Expand Up @@ -122,4 +56,32 @@ object CatsHelpers {
def throughO[O2](pipe: Pipe[F, O, O2]): Stream[F, Either[W, O2]] =
stream.through(pipeO(pipe))
}


/** This is pending release of https://github.com/Verizon/quiver/pull/31 */
private type Tree[A] = Cofree[SStream, A]

private def flattenTree[A](tree: Tree[A]): SStream[A] = {
def go(tree: Tree[A], xs: SStream[A]): SStream[A] =
SStream.cons(tree.head, tree.tail.value.foldRight(xs)(go(_, _)))
go(tree, SStream.Empty)
}

private def Node[A](root: A, forest: => SStream[Tree[A]]): Tree[A] =
Cofree[SStream, A](root, Eval.later(forest))

implicit class NelsonEnrichedGraph[N, A, B](val graph: Graph[N, A, B]) extends AnyVal {
def reachable(v: N): Vector[N] =
xdfWith(Seq(v), _.successors, _.vertex)._1.flatMap(flattenTree)

def xdfWith[C](vs: Seq[N], d: Context[N, A, B] => Seq[N], f: Context[N, A, B] => C): (Vector[Tree[C]], Graph[N, A, B]) =
if (vs.isEmpty || graph.isEmpty) (Vector(), graph)
else graph.decomp(vs.head) match {
case Decomp(None, g) => g.xdfWith(vs.tail, d, f)
case Decomp(Some(c), g) =>
val (xs, _) = g.xdfWith(d(c), d, f)
val (ys, g3) = g.xdfWith(vs.tail, d, f)
(Node(f(c), xs.toStream) +: ys, g3)
}
}
}
56 changes: 25 additions & 31 deletions core/src/main/scala/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ import nelson.vault.http4s._

import cats.~>
import cats.effect.{Effect, IO}
import cats.syntax.either._
import nelson.CatsHelpers._
import cats.implicits._

import java.io.FileInputStream
import java.nio.file.{Path, Paths}
Expand All @@ -49,8 +48,6 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.util.control.NonFatal

import scalaz.Scalaz._

/**
*
*/
Expand Down Expand Up @@ -529,20 +526,20 @@ object Config {

def readNomadInfrastructure(kfg: KConfig): Option[Infrastructure.Nomad] = {
def readSplunk: Option[Infrastructure.SplunkConfig] =
(kfg.lookup[String]("docker.splunk-url") |@| kfg.lookup[String]("docker.splunk-token")
)((x,y) => Infrastructure.SplunkConfig(x,y))
(kfg.lookup[String]("docker.splunk-url"), kfg.lookup[String]("docker.splunk-token")
).mapN((x,y) => Infrastructure.SplunkConfig(x,y))

def readLoggingImage: Option[Docker.Image] =
kfg.lookup[String]("logging-sidecar")
.flatMap(a => docker.Docker.Image.fromString(a).toOption)

(kfg.lookup[String]("endpoint") |@|
kfg.lookup[Duration]("timeout") |@|
kfg.lookup[String]("docker.user") |@|
kfg.lookup[String]("docker.password") |@|
kfg.lookup[String]("docker.host") |@|
(kfg.lookup[String]("endpoint"),
kfg.lookup[Duration]("timeout"),
kfg.lookup[String]("docker.user"),
kfg.lookup[String]("docker.password"),
kfg.lookup[String]("docker.host"),
kfg.lookup[Int]("mhz-per-cpu")
)((a,b,c,d,e,g) => {
).mapN((a,b,c,d,e,g) => {
val splunk = readSplunk
val loggingSidecar = readLoggingImage
val uri = Uri.fromString(a).toOption.yolo(s"nomad.endpoint -- $a -- is an invalid Uri")
Expand All @@ -551,12 +548,12 @@ object Config {
}

def readKubernetesInfrastructure(kfg: KConfig): Option[Infrastructure.Kubernetes] =
(kfg.lookup[String]("endpoint") |@|
kfg.lookup[String]("version").flatMap(KubernetesVersion.fromString) |@|
kfg.lookup[String]("ca-cert").map(p => Paths.get(p)) |@|
kfg.lookup[String]("token") |@|
(kfg.lookup[String]("endpoint"),
kfg.lookup[String]("version").flatMap(KubernetesVersion.fromString),
kfg.lookup[String]("ca-cert").map(p => Paths.get(p)),
kfg.lookup[String]("token"),
kfg.lookup[Duration]("timeout")
) { (endpoint, version, caCert, token, timeout) =>
).mapN { (endpoint, version, caCert, token, timeout) =>
val uri = Uri.fromString(endpoint).toOption.yolo(s"kubernetes.endpoint -- $endpoint -- is an invalid Uri")
Infrastructure.Kubernetes(uri, version, caCert, token, timeout)
}
Expand Down Expand Up @@ -616,9 +613,9 @@ object Config {
@SuppressWarnings(Array("org.brianmckenna.wartremover.warts.NoNeedForMonad"))
def readDatacenter(id: String, kfg: KConfig): IO[Datacenter] = {
val proxyCreds =
(kfg.lookup[String](s"proxy-credentials.username") |@|
(kfg.lookup[String](s"proxy-credentials.username"),
kfg.lookup[String](s"proxy-credentials.password")
)((a,b) => Infrastructure.ProxyCredentials(a,b))
).mapN((a,b) => Infrastructure.ProxyCredentials(a,b))

val consul = {
val a = kfg.require[String]("infrastructure.consul.endpoint")
Expand Down Expand Up @@ -732,13 +729,13 @@ object Config {

val zones = readAvailabilityZones(kfg.subconfig("availability-zones"))

(kfg.lookup[String]("access-key-id") |@|
kfg.lookup[String]("secret-access-key") |@|
lookupRegion(kfg) |@|
kfg.lookup[String]("launch-configuration-name") |@|
kfg.lookup[List[String]]("elb-security-group-names") |@|
(kfg.lookup[String]("access-key-id"),
kfg.lookup[String]("secret-access-key"),
lookupRegion(kfg),
kfg.lookup[String]("launch-configuration-name"),
kfg.lookup[List[String]]("elb-security-group-names"),
kfg.lookup[String]("image")
)((a,b,c,d,e,f) => Infrastructure.Aws(a,b,c,d,e.toSet,zones,f))
).mapN((a,b,c,d,e,f) => Infrastructure.Aws(a,b,c,d,e.toSet,zones,f))
}

private def readNomad(cfg: KConfig): NomadConfig =
Expand Down Expand Up @@ -819,12 +816,9 @@ object Config {
val name = splitted.head // yolo, but safe
val maybeVersion = splitted.drop(1).headOption

maybeVersion.cata(
none = Option(HttpUserAgent(name, None)),
some = version =>
Version.fromString(version)
.map(version => HttpUserAgent(name, Some(version)))
)
maybeVersion.fold(Option(HttpUserAgent(name, None))) { version =>
Version.fromString(version).map(version => HttpUserAgent(name, Some(version)))
}
}
cfg.lookup[List[String]]("http-user-agents").map { agents =>
val httpUserAgents: List[HttpUserAgent] = agents
Expand Down
Loading