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

Add role for generically installing an apt package #83

Merged
merged 2 commits into from
Nov 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/ansible/PlaybookGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object PlaybookGenerator {
if (role.variables.isEmpty)
role.roleId.value
else
s"{ role: ${role.roleId.value}, ${role.variables.map { case (k, v) => s"$k: '$v'" }.mkString(", ")} }"
s"{ role: ${role.roleId.value}, ${role.variables.map { case (k, v) => s"$k: ${v.quoted}" }.mkString(", ")} }"
}

}
1 change: 0 additions & 1 deletion app/data/Recipes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package data
import com.amazonaws.services.dynamodbv2.model._
import models._
import org.joda.time.DateTime

import com.gu.scanamo.syntax._

import scala.collection.JavaConverters._
Expand Down
49 changes: 45 additions & 4 deletions app/models/CustomisedRole.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package models

import cats.data.Xor
import com.gu.scanamo.DynamoFormat
import com.gu.scanamo.error.TypeCoercionError
import fastparse.WhitespaceApi

case class CustomisedRole(
roleId: RoleId,
variables: Map[String, String]) {
variables: Map[String, ParamValue]) {

def variablesToString = variables.map { case (k, v) => s"$k: $v" }.mkString("{ ", ", ", " }")

Expand All @@ -14,12 +19,48 @@ case class CustomisedRole(

}

sealed trait ParamValue { def quoted: String }
case class SingleParamValue(param: String) extends ParamValue {
override def toString: String = param
val quoted = s"'$param'"
}
case class ListParamValue(params: List[SingleParamValue]) extends ParamValue {
override def toString: String = s"[${params.mkString(", ")}]"
val quoted = s"[${params.map(_.quoted).mkString(", ")}]"
}
object ListParamValue {
def of(params: String*) = ListParamValue(params.map(SingleParamValue).toList)
}
object ParamValue {
implicit val format = DynamoFormat.xmap[ParamValue, String](
CustomisedRole.paramValue.parse(_).fold(
(_, _, _) => Xor.left(TypeCoercionError(new RuntimeException("Unable to read ParamValue"))),
(pv, _) => Xor.right(pv))
)(_.toString)
}

object CustomisedRole {
val White = WhitespaceApi.Wrapper {
import fastparse.all._
NoTrace(" ".rep)
}
import fastparse.noApi._
import White._

val key: Parser[String] = P(CharsWhile(_ != ':').!)
val singleValue: Parser[SingleParamValue] = P(CharPred(c => c.isLetterOrDigit || c == '-' || c == '_').rep.!).map(SingleParamValue(_))
val multiValues: Parser[ListParamValue] = P("[" ~ singleValue.rep(sep = ",") ~ "]").map(
params => ListParamValue(params.toList))
val paramValue: Parser[ParamValue] = multiValues | singleValue
val pair: Parser[(String, ParamValue)] = P(key ~ ":" ~ paramValue)

private val KeyValuePair = """(.+): ?(.+)""".r
val parameters = P(Start ~ pair.rep(sep = ",") ~ End)

def formInputTextToVariables(input: String): Map[String, String] = {
input.split(", *").collect { case KeyValuePair(k, v) => (k.trim, v.trim) }.toMap
def formInputTextToVariables(input: String): Map[String, ParamValue] = {
parameters.parse(input) match {
case Parsed.Success(value, _) => value.toMap
case f: Parsed.Failure => Map.empty
}
}

}
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ libraryDependencies ++= Seq(
"com.gu" %% "play-googleauth" % "0.4.0",
"com.adrianhurt" %% "play-bootstrap3" % "0.4.5-P24",
"org.quartz-scheduler" % "quartz" % "2.2.3",
"com.lihaoyi" %% "fastparse" % "0.4.1",
"org.scalatest" %% "scalatest" % "2.2.6" % Test
)
routesGenerator := InjectedRoutesGenerator
Expand Down
7 changes: 7 additions & 0 deletions roles/packages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# packages

Install package(s) with `apt-get` or `yum`. The `packages` parameter specifies which package(s) are installed, e.g.

```
packages: [package-1, package-2]
```
4 changes: 4 additions & 0 deletions roles/packages/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
dependencies:
- role: apt
when: ansible_os_family == "Debian"
10 changes: 10 additions & 0 deletions roles/packages/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: Install a package with apt-get
apt: name={{ item }} state=present
with_items: "{{ packages }}"
when: ansible_os_family == "Debian"

- name: Install a package with yum
yum: name={{ item }} state=present
with_items: "{{ packages }}"
when: ansible_os_family == "RedHat"
6 changes: 3 additions & 3 deletions test/ansible/PlaybookGeneratorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PlaybookGeneratorSpec extends FlatSpec with Matchers {
description = "",
amiId = AmiId(""),
builtinRoles = List(
CustomisedRole(RoleId("builtinRole1"), Map("foo" -> "bar")),
CustomisedRole(RoleId("builtinRole1"), Map("foo" -> SingleParamValue("bar"))),
CustomisedRole(RoleId("builtinRole2"), Map.empty)
),
createdBy = "Testy McTest",
Expand All @@ -24,7 +24,7 @@ class PlaybookGeneratorSpec extends FlatSpec with Matchers {
modifiedAt = DateTime.now()
),
roles = List(
CustomisedRole(RoleId("recipeRole1"), Map("wow" -> "yeah")),
CustomisedRole(RoleId("recipeRole1"), Map("wow" -> ListParamValue.of("yeah", "bonza"))),
CustomisedRole(RoleId("recipeRole2"), Map.empty)
),
createdBy = "Testy McTest",
Expand All @@ -42,7 +42,7 @@ class PlaybookGeneratorSpec extends FlatSpec with Matchers {
| roles:
| - { role: builtinRole1, foo: 'bar' }
| - builtinRole2
| - { role: recipeRole1, wow: 'yeah' }
| - { role: recipeRole1, wow: ['yeah', 'bonza'] }
| - recipeRole2
|""".stripMargin
)
Expand Down
21 changes: 21 additions & 0 deletions test/models/CustomisedRoleTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package models

import org.scalatest.{ FunSuite, ShouldMatchers }

class CustomisedRoleTest extends FunSuite with ShouldMatchers {

test("should parse a map of variables") {
CustomisedRole.formInputTextToVariables("ssh_keys_bucket: bucket, ssh_keys_prefix: Team")
.shouldBe(Map("ssh_keys_bucket" -> SingleParamValue("bucket"), "ssh_keys_prefix" -> SingleParamValue("Team")))
}

test("should parse variable lists") {
CustomisedRole.formInputTextToVariables("packages: [python-pip, emacs24]")
.shouldBe(Map("packages" -> ListParamValue.of("python-pip", "emacs24")))
}

test("should round trip param values") {
def roundTrip(pv: ParamValue) = ParamValue.format.read(ParamValue.format.write(pv)).toOption.get
roundTrip(SingleParamValue("X")) shouldBe (SingleParamValue("X"))
}
}