Skip to content

Commit

Permalink
work around bug in EC2 query serialization when using DescribeInstances
Browse files Browse the repository at this point in the history
  • Loading branch information
bpholt committed Dec 7, 2023
1 parent 3f6ee3f commit a5c3c43
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 2 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ lazy val `registrator-health-check-lambda` = project
"io.circe" %% "circe-literal" % "0.14.6" % Test,
"io.circe" %% "circe-testing" % "0.14.6" % Test,
"org.scala-lang.modules" %% "scala-java8-compat" % "1.0.2" % Test,
"com.dwolla" %% "dwolla-otel-natchez" % "0.2.2" % Test,
)
},
topLevelDirectory := None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class Ec2AlgSpec
.traverse { instanceId =>
val fakeClient = new EC2.Default[IO](new NotImplementedError().raiseError) {
override def describeInstances(dryRun: Boolean,
maxResults: Int,
filters: Option[List[Filter]],
instanceIds: Option[List[InstanceId]],
nextToken: Option[String]): IO[DescribeInstancesResult] =
Expand Down
40 changes: 40 additions & 0 deletions core/src/main/scala/com/dwolla/aws/ec2/model.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.dwolla.aws.ec2

import cats.tagless.aop.*
import cats.*
import com.amazonaws.ec2.*
import natchez.*
import com.dwolla.aws.TraceableValueInstances.given

private def traceableAdvice[A: TraceableValue](name: String, a: A): Aspect.Advice[Eval, TraceableValue] =
Aspect.Advice.byValue[TraceableValue, A](name, a)

given Aspect[EC2, TraceableValue, TraceableValue] = new Aspect[EC2, TraceableValue, TraceableValue] {
override def weave[F[_]](af: EC2[F]): EC2[[A] =>> Aspect.Weave[F, TraceableValue, TraceableValue, A]] =
new EC2[[A] =>> Aspect.Weave[F, TraceableValue, TraceableValue, A]] {
override def describeInstances(dryRun: Boolean,
filters: Option[List[Filter]],
instanceIds: Option[List[InstanceId]],
nextToken: Option[String]): Aspect.Weave[F, TraceableValue, TraceableValue, DescribeInstancesResult] =
Aspect.Weave[F, TraceableValue, TraceableValue, DescribeInstancesResult](
"EC2",
List(List(
traceableAdvice("dryRun", dryRun),
traceableAdvice("filters", filters),
traceableAdvice("instanceIds", instanceIds),
traceableAdvice("nextToken", nextToken),
)),
Aspect.Advice("describeInstances", af.describeInstances(dryRun, filters, instanceIds, nextToken))
)
}

override def mapK[F[_], G[_]](af: EC2[F])
(fk: F ~> G): EC2[G] =
new EC2[G] {
override def describeInstances(dryRun: Boolean,
filters: Option[List[Filter]],
instanceIds: Option[List[InstanceId]],
nextToken: Option[String]): G[DescribeInstancesResult] =
fk(af.describeInstances(dryRun, filters, instanceIds, nextToken))
}
}
2 changes: 1 addition & 1 deletion core/src/main/scala/com/dwolla/aws/ecs/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ object TaskStatus {
}
}

def traceableAdvice[A: TraceableValue](name: String, a: A): Aspect.Advice[Eval, TraceableValue] =
private def traceableAdvice[A: TraceableValue](name: String, a: A): Aspect.Advice[Eval, TraceableValue] =
Aspect.Advice.byValue[TraceableValue, A](name, a)

given Aspect[ECS, TraceableValue, TraceableValue] =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.dwolla.autoscaling.ecs.registrator

import cats.*
import cats.effect.*
import cats.effect.std.Console
import cats.mtl.Local
import cats.syntax.all.*
import com.amazonaws.ec2.*
import com.dwolla.aws.ec2.{*, given}
import com.dwolla.tracing.mtl.LocalSpan
import com.dwolla.tracing.syntax.*
import com.dwolla.tracing.{DwollaEnvironment, OpenTelemetryAtDwolla}
import natchez.Span
import natchez.mtl.given
import org.http4s.*
import org.http4s.client.{Client, middleware}
import org.http4s.ember.client.EmberClientBuilder
import org.typelevel.log4cats.*
import smithy4s.aws.*
import smithy4s.aws.kernel.AwsRegion

object TestApp extends ResourceApp.Simple {
private val id = InstanceId("i-04d30e1ebb1109aa2")
override def run: Resource[IO, Unit] =
OpenTelemetryAtDwolla[IO]("ecs-autoscaling-scale-out-hook", DwollaEnvironment.Local)
.flatMap(_.root("EC2 Test App"))
.evalMap(LocalSpan(_))
.flatMap { case given Local[IO, Span[IO]] =>
given LoggerFactory[IO] = new ConsoleLogger[IO]

EmberClientBuilder.default[IO].build
.map(middleware.Logger(true, true, logAction = (IO.println(_: String)).some))
.flatMap(AwsEnvironment.default(_, AwsRegion.US_WEST_2))
.flatMap(AwsClient(EC2, _))
.map(_.traceWithInputsAndOutputs)
.map(Ec2Alg(_))
.evalTap(_ => IO.println("using client via Ec2Alg"))
.evalTap(_.getTagsForInstance(id) >>= IO.println)
.void
}

}

class ConsoleLogger[F[_] : Applicative : Console] extends LoggerFactory[F] {
override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] =
new SelfAwareStructuredLogger[F] {
override def isTraceEnabled: F[Boolean] = true.pure[F]
override def isDebugEnabled: F[Boolean] = true.pure[F]
override def isInfoEnabled: F[Boolean] = true.pure[F]
override def isWarnEnabled: F[Boolean] = true.pure[F]
override def isErrorEnabled: F[Boolean] = true.pure[F]
override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = Console[F].println(msg)
override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = Console[F].println(msg)
override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = Console[F].println(msg)
override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = Console[F].println(msg)
override def info(ctx: Map[String, String])(msg: => String): F[Unit] = Console[F].println(msg)
override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = Console[F].println(msg)
override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = Console[F].println(msg)
override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = Console[F].println(msg)
override def error(ctx: Map[String, String])(msg: => String): F[Unit] = Console[F].println(msg)
override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = Console[F].println(msg)
override def error(t: Throwable)(message: => String): F[Unit] = Console[F].println(message)
override def warn(t: Throwable)(message: => String): F[Unit] = Console[F].println(message)
override def info(t: Throwable)(message: => String): F[Unit] = Console[F].println(message)
override def debug(t: Throwable)(message: => String): F[Unit] = Console[F].println(message)
override def trace(t: Throwable)(message: => String): F[Unit] = Console[F].println(message)
override def error(message: => String): F[Unit] = Console[F].println(message)
override def warn(message: => String): F[Unit] = Console[F].println(message)
override def info(message: => String): F[Unit] = Console[F].println(message)
override def debug(message: => String): F[Unit] = Console[F].println(message)
override def trace(message: => String): F[Unit] = Console[F].println(message)
}
override def fromName(name: String): F[SelfAwareStructuredLogger[F]] =
getLoggerFromName(name).pure[F]
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package preprocessors
import software.amazon.smithy.build.TransformContext
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ShapeId

final class Ec2Preprocessor extends OperationFilteringPreprocessor {
override lazy val shapesToKeep: Set[String] = Set(
Expand All @@ -8,4 +11,18 @@ final class Ec2Preprocessor extends OperationFilteringPreprocessor {
override lazy val namespace: Set[String] = Set("com.amazonaws.ec2")

override def getName: String = "Ec2Preprocessor"

override def transform(ctx: TransformContext): Model =
ctx.getTransformer.filterShapes(
super.transform(ctx),

/* This seems to be a bug in smithy4s; MaxResults and InstanceIds cannot both be set on
* a DescribeInstancesRequest. However, MaxResults is always set on the request, even if
* it's set to the default value. This filter removes the MaxResults attribute from the
* request class so that it can't ever be included.
*
* See https://github.com/disneystreaming/smithy4s/issues/1321
*/
!_.toShapeId.equals(ShapeId.from("com.amazonaws.ec2#DescribeInstancesRequest$MaxResults"))
)
}

0 comments on commit a5c3c43

Please sign in to comment.