Skip to content

Instantly share code, notes, and snippets.

@komiya-atsushi
Created February 14, 2014 14:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save komiya-atsushi/9002471 to your computer and use it in GitHub Desktop.
Save komiya-atsushi/9002471 to your computer and use it in GitHub Desktop.
第5回 #渋谷Java http://connpass.com/event/4549/ で発表する内容に関するコードです。
package biz.k11i.demo;
import biz.k11i.demo.CFnTemplateBuilder.Subnet;
import biz.k11i.demo.CFnTemplateBuilder.VPC;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Java で CloudFormation テンプレートの DSL を実現してみる試み。
* <p>
* 実行には google-gson を必要とします。Gradle なら次の行を build.gradle に追記してください。
* </p>
* <p>
* <code>compile group: 'com.google.code.gson', name: 'gson', version: '2.2.4'</code>
* </p>
*
* @author KOMIYA Atsushi
*/
public class CFnTemplateBuilder {
private static final Gson GSON = new Gson();
private static final Gson PP_GSON = new GsonBuilder().setPrettyPrinting().create();
private List<Jsonable> resources = new ArrayList<>();
public VPC newVPC(String ip, int subnetMaskBits) {
VPC vpc = new VPC(ip, subnetMaskBits);
resources.add(vpc);
return vpc;
}
public String build() {
StringBuilder sb = new StringBuilder()
.append('{')
.append("\"AWSTemplateFormatVersion\": \"2010-09-09\",")
.append("\"Resources\": {");
boolean needCommaSeparation = false;
for (Jsonable resource : resources) {
if (needCommaSeparation) {
sb.append(",");
}
needCommaSeparation = true;
sb.append(resource.toJson());
}
sb.append("} }");
return PP_GSON.toJson(GSON.fromJson(sb.toString(), Map.class));
}
// ----
public static interface Function<P> {
void call(P param);
}
public static interface Jsonable {
String toJson();
}
public static interface HasResourceName {
String resourceName();
}
public static class JsonBuilder implements Jsonable {
private final String resourceName;
private final Map<String, Object> object = new LinkedHashMap<>();
private final Map<String, Object> properties = new LinkedHashMap<>();
public JsonBuilder(String resourceName, String type) {
this.resourceName = resourceName;
this.object.put("Type", type);
}
public JsonBuilder add(String key, Object value) {
this.properties.put(key, value);
return this;
}
public JsonBuilder addMap(String key, Function<Map<String, Object>> initializer) {
this.properties.put(key, map(initializer));
return this;
}
public static Map<String, Object> map(Function<Map<String, Object>> initializer) {
Map<String, Object> map = new LinkedHashMap<>();
initializer.call(map);
return map;
}
public JsonBuilder addList(String key, Function<List<Object>> initializer) {
this.properties.put(key, list(initializer));
return this;
}
public static List<Object> list(Function<List<Object>> initializer) {
List<Object> list = new ArrayList<>();
initializer.call(list);
return list;
}
@Override
public String toJson() {
if (!properties.isEmpty()) {
this.object.put("Properties", properties);
} else {
this.object.remove("Properties");
}
return String.format("\"%s\": %s", resourceName, GSON.toJson(object));
}
}
private static class Cidr {
private final String ip;
private final int subnetMaskBits;
Cidr(String ip, int subnetMaskBits) {
this.ip = ip;
this.subnetMaskBits = subnetMaskBits;
}
public String toString() {
return String.format("%s/%d", ip, subnetMaskBits);
}
}
public static class VPC implements Jsonable, HasResourceName {
private static int sequence = 1;
private final String resourceName = "Vpc" + sequence++;
private final Cidr cidrBlock;
private List<Subnet> subnets = new ArrayList<>();
private List<RouteTable> routeTables = new ArrayList<>();
public VPC(String ip, int subnetMaskBits) {
cidrBlock = new Cidr(ip, subnetMaskBits);
}
public RouteTable newRouteTable() {
RouteTable routeTable = new RouteTable(this);
routeTables.add(routeTable);
return routeTable;
}
public Subnet newSubnet(String ip, int subnetMaskBits) {
Subnet subnet = new Subnet(this, ip, subnetMaskBits);
subnets.add(subnet);
return subnet;
}
public VPC subnet(String ip, int subnetMaskBits, Function<Subnet> initializer) {
initializer.call(newSubnet(ip, subnetMaskBits));
return this;
}
@Override
public String toJson() {
StringBuilder sb = new StringBuilder();
sb.append(new JsonBuilder(resourceName, "AWS::EC2::VPC")
.add("CidrBlock", cidrBlock.toString())
.toJson());
for (Subnet subnet : subnets) {
sb.append(",\n")
.append(subnet.toJson());
}
for (RouteTable routeTable : routeTables) {
sb.append(",\n")
.append(routeTable.toJson());
}
return sb.toString();
}
@Override
public String resourceName() {
return resourceName;
}
}
public static class Subnet implements Jsonable, HasResourceName {
private static int sequence = 1;
private final String resourceName = "Subnet" + sequence++;
private final VPC vpc;
private final Cidr cidr;
private String availabilityZone;
public Subnet(VPC vpc, String ip, int subnetMaskBits) {
this.vpc = vpc;
this.cidr = new Cidr(ip, subnetMaskBits);
}
public Subnet availabilityZone(String availabilityZone) {
this.availabilityZone = availabilityZone;
return this;
}
@Override
public String toJson() {
JsonBuilder builder = new JsonBuilder(resourceName, "AWS::EC2::Subnet")
.add("CidrBlock", cidr.toString())
.add("VpcId", JsonBuilder.map(m -> m.put("Ref", vpc.resourceName())));
if (availabilityZone != null) {
builder.add("AvailabilityZone", availabilityZone);
}
return builder.toJson();
}
@Override
public String resourceName() {
return resourceName;
}
}
public static class RouteTable implements Jsonable, HasResourceName {
private static int sequence = 1;
private final String resourceName = "RouteTable" + sequence++;
private final VPC vpc;
private InternetGateway internetGateway;
private List<Subnet> subnets = new ArrayList<>();
public RouteTable(VPC vpc) {
this.vpc = vpc;
}
public RouteTable attachInternetGateway() {
this.internetGateway = new InternetGateway(vpc);
return this;
}
public RouteTable addRoute(Subnet subnet) {
this.subnets.add(subnet);
return this;
}
@Override
public String toJson() {
StringBuilder sb = new StringBuilder();
sb.append(new JsonBuilder(resourceName, "AWS::EC2::RouteTable")
.addMap("VpcId", m -> m.put("Ref", vpc.resourceName))
.toJson())
.append(",\n")
.append(internetGateway.toJson());
int count = 0;
for (Subnet subnet : subnets) {
sb.append(",\n")
.append(new JsonBuilder(resourceName + "Association" + count++, "AWS::EC2::SubnetRouteTableAssociation")
.addMap("RouteTableId", m -> m.put("Ref", resourceName))
.addMap("SubnetId", m -> m.put("Ref", subnet.resourceName()))
.toJson());
}
return sb.toString();
}
@Override
public String resourceName() {
return resourceName;
}
}
public static class InternetGateway implements Jsonable, HasResourceName {
private static int sequence = 1;
private final String resourceName = "Igw" + sequence++;
private final VPC vpc;
public InternetGateway(VPC vpc) {
this.vpc = vpc;
}
@Override
public String toJson() {
String selfJson = new JsonBuilder(resourceName, "AWS::EC2::InternetGateway")
.toJson();
String attachJson = new JsonBuilder(resourceName + "Attachment", "AWS::EC2::VPCGatewayAttachment")
.addMap("InternetGatewayId", m -> m.put("Ref", resourceName))
.addMap("VpcId", m -> m.put("Ref", vpc.resourceName()))
.toJson();
return String.format("%s,\n%s", selfJson, attachJson);
}
@Override
public String resourceName() {
return resourceName;
}
}
}
/**
* クラスメソッド社の技術ブログエントリ「Amazon VPCを使ったミニマム構成のサーバ環境を構築する」
*
* http://dev.classmethod.jp/cloud/aws/minimum-amazon-vpc-server-environment/
*
* の記事にある VPC を構築するための CloudFormation テンプレートを、上記のクラスを
* 利用して実際に作ってみようと思います。
*/
class HowToUse {
public static void main(String[] args) {
CFnTemplateBuilder builder = new CFnTemplateBuilder();
// 10.0.0.0/16 の VPC 上に、
// 10.0.1.0/24
// 10.0.2.0/24
// の二つのプライベートなサブネットを作成します
VPC vpc = builder
.newVPC("10.0.0.0", 16)
.subnet("10.0.1.0", 24,
privateSubnet -> privateSubnet.availabilityZone("ap-northeast-1a"))
.subnet("10.0.2.0", 24,
anotherPrivateSubnet -> anotherPrivateSubnet.availabilityZone("ap-northeast-1c"));
// 上記のサブネットとは別に、 10.0.0.0/24 のパブリックなサブネットを作成します
Subnet publicSubnet = vpc
.newSubnet("10.0.0.0", 24)
.availabilityZone("ap-northeast-1a");
// 10.0.0.0/24 のパブリックなサブネットについては、
// インターネットとの疎通ができるようにゲートウェイを用意します
vpc.newRouteTable()
.attachInternetGateway()
.addRoute(publicSubnet);
// 本来はこの後にセキュリティグループを作成するなどまだまだ続くのですが、
// 実装が追いつかなかったので断念しました。
System.out.println(builder.build());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment