Skip to content

Commit

Permalink
Reduce ability to create invalid RDS template
Browse files Browse the repository at this point in the history
Implement a complex mechanism for reducing the chance of creating JSON
for an RDS DBInstance that is invalid.  My hope is that the complexity
equals and is not greater than the complexity inherent in the
restrictions around RDS creation.  There is still one run-time check
around storage encryption since specifying it is only valid on new
instances created within VPCs.

There are restrictions on the values of AllocatedStorage when using
Iops but often these values are given as parameter and therefore must
accept Token[Int].  I currently used Either to take Int or Token[Int]
but at some point it would be good to allow one to reach through the
Token to see if the Int is there too test.

Tests were added to cover all of the above.

A couple other minor changes:
-   Clean up build.sbt
-   Implement and use EnumFormat for all enumerations JSON serialization
  • Loading branch information
ddgenome committed Oct 21, 2015
1 parent 14a156c commit 405590a
Show file tree
Hide file tree
Showing 8 changed files with 958 additions and 194 deletions.
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation")

libraryDependencies ++= Seq (
// -- testing --
"org.scalatest" %% "scalatest" % "2.2.1" % "test"
"org.scalatest" %% "scalatest" % "2.2.1" % "test"
// -- json --
,"io.spray" %% "spray-json" % "1.3.2"
,"io.spray" %% "spray-json" % "1.3.2"
// -- reflection --
,"org.scala-lang" % "scala-reflect" % scalaVersion.value
).map(_.force())

resolvers ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.monsanto.arch.cloudformation.model

import spray.json._
import scala.reflect.runtime.universe._

class EnumFormat[T](values: Seq[T], stringifier: T => String = (x: T) => x.toString)
(implicit tag: TypeTag[T]) extends JsonFormat[T] {
override def read(json: JsValue): T =
json match {
case s: JsString =>
values.find(x => stringifier(x) == s.value).getOrElse(deserializationError(s.toString + " is not a valid " + tag.tpe))
case x => deserializationError(x.toString + " is not a String")
}
override def write(obj: T) = JsString(stringifier(obj))
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,98 +35,136 @@ object `AWS::CloudWatch::Alarm` extends DefaultJsonProtocol {
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm`] = jsonFormat17(`AWS::CloudWatch::Alarm`.apply)
}

sealed abstract class `AWS::CloudWatch::Alarm::ComparisonOperator`(val operator: String)
sealed trait `AWS::CloudWatch::Alarm::ComparisonOperator`
object `AWS::CloudWatch::Alarm::ComparisonOperator` extends DefaultJsonProtocol {
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::ComparisonOperator`] = new JsonFormat[`AWS::CloudWatch::Alarm::ComparisonOperator`] {
def write(obj: `AWS::CloudWatch::Alarm::ComparisonOperator`) = JsString(obj.operator)
//TODO
def read(json: JsValue) = ???
}
case object GreaterThanOrEqualToThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`
case object GreaterThanThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`
case object LessThanOrEqualToThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`
case object LessThanThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`
val values = Seq(GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanOrEqualToThreshold, LessThanThreshold)
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::ComparisonOperator`] =
new EnumFormat[`AWS::CloudWatch::Alarm::ComparisonOperator`](values)
}
case object GreaterThanOrEqualToThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`("GreaterThanOrEqualToThreshold")
case object GreaterThanThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`("GreaterThanThreshold")
case object LessThanOrEqualToThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`("LessThanOrEqualToThreshold")
case object LessThanThreshold extends `AWS::CloudWatch::Alarm::ComparisonOperator`("LessThanThreshold")

case class `AWS::CloudWatch::Alarm::Dimension`(Name: String, Value: Token[ResourceRef[_]])
object `AWS::CloudWatch::Alarm::Dimension` extends DefaultJsonProtocol {
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Dimension`] = jsonFormat2(`AWS::CloudWatch::Alarm::Dimension`.apply)
def from[A <: Resource[A]](name: String, value: Token[ResourceRef[A]]): `AWS::CloudWatch::Alarm::Dimension` = `AWS::CloudWatch::Alarm::Dimension`(name, value.asInstanceOf[Token[ResourceRef[_]]])
}

sealed abstract class `AWS::CloudWatch::Alarm::Namespace`(val name: String)
sealed trait `AWS::CloudWatch::Alarm::Namespace`
object `AWS::CloudWatch::Alarm::Namespace` extends DefaultJsonProtocol {
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Namespace`] = new JsonFormat[`AWS::CloudWatch::Alarm::Namespace`] {
def write(obj: `AWS::CloudWatch::Alarm::Namespace`) = JsString(obj.name)
//TODO
def read(json: JsValue) = ???
}
case object `AWS/AutoScaling` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/Billing` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/CloudFront` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/DynamoDB` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/ElastiCache` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/EBS` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/EC2` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/ELB` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/ElasticMapReduce` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/Kinesis` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/OpsWorks` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/Redshift` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/RDS` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/Route53` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/SNS` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/SQS` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/SWF` extends `AWS::CloudWatch::Alarm::Namespace`
case object `AWS/StorageGateway` extends `AWS::CloudWatch::Alarm::Namespace`
val values = Seq(`AWS/AutoScaling`,
`AWS/Billing`,
`AWS/CloudFront`,
`AWS/DynamoDB`,
`AWS/ElastiCache`,
`AWS/EBS`,
`AWS/EC2`,
`AWS/ELB`,
`AWS/ElasticMapReduce`,
`AWS/Kinesis`,
`AWS/OpsWorks`,
`AWS/Redshift`,
`AWS/RDS`,
`AWS/Route53`,
`AWS/SNS`,
`AWS/SQS`,
`AWS/SWF`,
`AWS/StorageGateway`
)
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Namespace`] =
new EnumFormat[`AWS::CloudWatch::Alarm::Namespace`](values)
}
case object `AWS/AutoScaling` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/AutoScaling")
case object `AWS/Billing` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/Billing")
case object `AWS/CloudFront` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/CloudFront")
case object `AWS/DynamoDB` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/DynamoDB")
case object `AWS/ElastiCache` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/ElastiCache")
case object `AWS/EBS` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/EBS")
case object `AWS/EC2` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/EC2")
case object `AWS/ELB` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/ELB")
case object `AWS/ElasticMapReduce` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/ElasticMapReduce")
case object `AWS/Kinesis` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/Kinesis")
case object `AWS/OpsWorks` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/OpsWorks")
case object `AWS/Redshift` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/Redshift")
case object `AWS/RDS` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/RDS")
case object `AWS/Route53` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/Route53")
case object `AWS/SNS` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/SNS")
case object `AWS/SQS` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/SQS")
case object `AWS/SWF` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/SWF")
case object `AWS/StorageGateway` extends `AWS::CloudWatch::Alarm::Namespace`("AWS/StorageGateway")

sealed abstract class `AWS::CloudWatch::Alarm::Statistic`(val name: String)
sealed trait `AWS::CloudWatch::Alarm::Statistic`
object `AWS::CloudWatch::Alarm::Statistic` extends DefaultJsonProtocol {
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Statistic`] = new JsonFormat[`AWS::CloudWatch::Alarm::Statistic`] {
def write(obj: `AWS::CloudWatch::Alarm::Statistic`) = JsString(obj.name)
//TODO
def read(json: JsValue) = ???
}
case object SampleCount extends `AWS::CloudWatch::Alarm::Statistic`
case object Average extends `AWS::CloudWatch::Alarm::Statistic`
case object Sum extends `AWS::CloudWatch::Alarm::Statistic`
case object Minimum extends `AWS::CloudWatch::Alarm::Statistic`
case object Maximum extends `AWS::CloudWatch::Alarm::Statistic`
val values = Seq(SampleCount, Average, Sum, Minimum, Maximum)
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Statistic`] =
new EnumFormat[`AWS::CloudWatch::Alarm::Statistic`](values)
}
case object SampleCount extends `AWS::CloudWatch::Alarm::Statistic`("SampleCount")
case object Average extends `AWS::CloudWatch::Alarm::Statistic`("Average")
case object Sum extends `AWS::CloudWatch::Alarm::Statistic`("Sum")
case object Minimum extends `AWS::CloudWatch::Alarm::Statistic`("Minimum")
case object Maximum extends `AWS::CloudWatch::Alarm::Statistic`("Maximum")

sealed abstract class `AWS::CloudWatch::Alarm::Unit`(val name: String)
sealed trait `AWS::CloudWatch::Alarm::Unit`
object `AWS::CloudWatch::Alarm::Unit` extends DefaultJsonProtocol {
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Unit`] = new JsonFormat[`AWS::CloudWatch::Alarm::Unit`] {
def write(obj: `AWS::CloudWatch::Alarm::Unit`) = JsString(obj.name)
//TODO
def read(json: JsValue) = ???
}
case object Seconds extends `AWS::CloudWatch::Alarm::Unit`
case object Microseconds extends `AWS::CloudWatch::Alarm::Unit`
case object Milliseconds extends `AWS::CloudWatch::Alarm::Unit`
case object Bytes extends `AWS::CloudWatch::Alarm::Unit`
case object Kilobytes extends `AWS::CloudWatch::Alarm::Unit`
case object Megabytes extends `AWS::CloudWatch::Alarm::Unit`
case object Gigabytes extends `AWS::CloudWatch::Alarm::Unit`
case object Terabytes extends `AWS::CloudWatch::Alarm::Unit`
case object Bits extends `AWS::CloudWatch::Alarm::Unit`
case object Kilobits extends `AWS::CloudWatch::Alarm::Unit`
case object Megabits extends `AWS::CloudWatch::Alarm::Unit`
case object Gigabits extends `AWS::CloudWatch::Alarm::Unit`
case object Terabits extends `AWS::CloudWatch::Alarm::Unit`
case object Percent extends `AWS::CloudWatch::Alarm::Unit`
case object Count extends `AWS::CloudWatch::Alarm::Unit`
case object `Bytes/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Kilobytes/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Megabytes/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Gigabytes/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Terabytes/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Bits/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Kilobits/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Megabits/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Gigabits/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Terabits/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object `Count/Second` extends `AWS::CloudWatch::Alarm::Unit`
case object UnitNone extends `AWS::CloudWatch::Alarm::Unit`
val values = Seq(
Seconds,
Microseconds,
Milliseconds,
Bytes,
Kilobytes,
Megabytes,
Gigabytes,
Terabytes,
Bits,
Kilobits,
Megabits,
Gigabits,
Terabits,
Percent,
Count,
`Bytes/Second`,
`Kilobytes/Second`,
`Megabytes/Second`,
`Gigabytes/Second`,
`Terabytes/Second`,
`Bits/Second`,
`Kilobits/Second`,
`Megabits/Second`,
`Gigabits/Second`,
`Terabits/Second`,
`Count/Second`,
UnitNone
)
implicit val format: JsonFormat[`AWS::CloudWatch::Alarm::Unit`] =
new EnumFormat[`AWS::CloudWatch::Alarm::Unit`](values)
}
case object Seconds extends `AWS::CloudWatch::Alarm::Unit`("Seconds")
case object Microseconds extends `AWS::CloudWatch::Alarm::Unit`("Microseconds")
case object Milliseconds extends `AWS::CloudWatch::Alarm::Unit`("Milliseconds")
case object Bytes extends `AWS::CloudWatch::Alarm::Unit`("Bytes")
case object Kilobytes extends `AWS::CloudWatch::Alarm::Unit`("Kilobytes")
case object Megabytes extends `AWS::CloudWatch::Alarm::Unit`("Megabytes")
case object Gigabytes extends `AWS::CloudWatch::Alarm::Unit`("Gigabytes")
case object Terabytes extends `AWS::CloudWatch::Alarm::Unit`("Terabytes")
case object Bits extends `AWS::CloudWatch::Alarm::Unit`("Bits")
case object Kilobits extends `AWS::CloudWatch::Alarm::Unit`("Kilobits")
case object Megabits extends `AWS::CloudWatch::Alarm::Unit`("Megabits")
case object Gigabits extends `AWS::CloudWatch::Alarm::Unit`("Gigabits")
case object Terabits extends `AWS::CloudWatch::Alarm::Unit`("Terabits")
case object Percent extends `AWS::CloudWatch::Alarm::Unit`("Percent")
case object Count extends `AWS::CloudWatch::Alarm::Unit`("Count")
case object `Bytes/Second` extends `AWS::CloudWatch::Alarm::Unit`("Bytes/Second")
case object `Kilobytes/Second` extends `AWS::CloudWatch::Alarm::Unit`("Kilobytes/Second")
case object `Megabytes/Second` extends `AWS::CloudWatch::Alarm::Unit`("Megabytes/Second")
case object `Gigabytes/Second` extends `AWS::CloudWatch::Alarm::Unit`("Gigabytes/Second")
case object `Terabytes/Second` extends `AWS::CloudWatch::Alarm::Unit`("Terabytes/Second")
case object `Bits/Second` extends `AWS::CloudWatch::Alarm::Unit`("Bits/Second")
case object `Kilobits/Second` extends `AWS::CloudWatch::Alarm::Unit`("Kilobits/Second")
case object `Megabits/Second` extends `AWS::CloudWatch::Alarm::Unit`("Megabits/Second")
case object `Gigabits/Second` extends `AWS::CloudWatch::Alarm::Unit`("Gigabits/Second")
case object `Terabits/Second` extends `AWS::CloudWatch::Alarm::Unit`("Terabits/Second")
case object `Count/Second` extends `AWS::CloudWatch::Alarm::Unit`("Count/Second")
case object UnitNone extends `AWS::CloudWatch::Alarm::Unit`("UnitNone")
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ object `AWS::ElasticLoadBalancing::LoadBalancer` extends DefaultJsonProtocol {
DependsOn = DependsOn
)

implicit val format: JsonFormat[`AWS::ElasticLoadBalancing::LoadBalancer`] = jsonFormat19(`AWS::ElasticLoadBalancing::LoadBalancer`.apply)
implicit val format: JsonFormat[`AWS::ElasticLoadBalancing::LoadBalancer`] =
jsonFormat19(`AWS::ElasticLoadBalancing::LoadBalancer`.apply)
}

case class ELBAccessLoggingPolicy(
Expand All @@ -178,19 +179,9 @@ sealed trait ELBLoggingEmitInterval
object ELBLoggingEmitInterval extends DefaultJsonProtocol {
case object `5` extends ELBLoggingEmitInterval
case object `60` extends ELBLoggingEmitInterval

implicit val format: JsonFormat[ELBLoggingEmitInterval] = new JsonFormat[ELBLoggingEmitInterval] {
override def write(obj: ELBLoggingEmitInterval): JsValue = obj match {
case `5` => JsNumber(5)
case `60` => JsNumber(60)
}
override def read(json: JsValue): ELBLoggingEmitInterval = {
json.toString() match {
case "5" => `5`
case "60" => `60`
}
}
}
val values = Seq(`5`, `60`)
implicit val format: JsonFormat[ELBLoggingEmitInterval] =
new EnumFormat[ELBLoggingEmitInterval](values)
}

case class ELBAppCookieStickinessPolicy(
Expand Down Expand Up @@ -242,18 +233,8 @@ object ELBListenerProtocol extends DefaultJsonProtocol {
case object HTTPS extends ELBListenerProtocol
case object SSL extends ELBListenerProtocol
case object TCP extends ELBListenerProtocol

implicit val format: JsonFormat[ELBListenerProtocol] = new JsonFormat[ELBListenerProtocol] {
override def write(obj: ELBListenerProtocol)= JsString(obj.toString)
override def read(json: JsValue): ELBListenerProtocol = {
json.toString match {
case "HTTP" => HTTP
case "HTTPS" => HTTPS
case "SSL" => SSL
case "TCP" => TCP
}
}
}
val values = Seq(HTTPS, HTTPS, SSL, TCP)
implicit val format: JsonFormat[ELBListenerProtocol] = new EnumFormat[ELBListenerProtocol](values)
}

case class ELBHealthCheck(
Expand Down Expand Up @@ -285,16 +266,8 @@ object NameValuePair extends DefaultJsonProtocol {

sealed trait ELBScheme
object ELBScheme extends DefaultJsonProtocol {
case object internal extends ELBScheme
case object internal extends ELBScheme
case object `internet-facing` extends ELBScheme

implicit val format: JsonFormat[ELBScheme] = new JsonFormat[ELBScheme] {
override def write(obj: ELBScheme)= JsString(obj.toString)
override def read(json: JsValue): ELBScheme = {
json.toString match {
case "internal" => internal
case "internet-facing" => `internet-facing`
}
}
}
val values = Seq(internal, `internet-facing`)
implicit val format: JsonFormat[ELBScheme] = new EnumFormat[ELBScheme](values)
}

0 comments on commit 405590a

Please sign in to comment.