Created
February 14, 2014 14:59
-
-
Save komiya-atsushi/9002471 to your computer and use it in GitHub Desktop.
第5回 #渋谷Java http://connpass.com/event/4549/ で発表する内容に関するコードです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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