JavaScriptでドメイン取得して、そのままCloudFormationを立ち上げる(AWS SDKのRoute53Domainsを使ってみるpart.3)


完全に調子に乗ってやっちゃった系です。

「ドメイン取得がJSでできるなら、ドメイン取得→サーバー立ち上げまで全部やっちゃえ」ということで勢いに任せて書いてみました。

しれっとES6のコードとか、promise使ってたりするのでNodeのバージョンには要注意です。

## ディレクトリ構造
```
$ tree -I node_modules
.
├── config.js
├── node_modules
├── index.js
├── package.json
└── parameters.js
```
セキュアな値やパラメーター系をconfig.js / parameters.jsに切り出して.gitignoreさせてます。

##各ファイルのコード

```index.js
var AWS = require('aws-sdk');
var conf = require('./config.js');
var params = require('./parameters.js');
var parameters = params.getParams();

createWebServerStack( parameters );
function createWebServerStack( parameters ) {
	console.log('Check Route53 Domains Status');
	var config = conf.getConf();
	var route53domains = new AWS.Route53Domains({
		apiVersion: '2014-05-15',
		accessKeyId: config.accessKeyId,
		secretAccessKey: config.secretAccessKey,
		region: config.region
	});
	var params = {
	  DomainName: parameters.route53.domain,
	};
	route53domains.checkDomainAvailability(params).promise().then(
		function(data){
			var response = domain + ' is ' + data. Availability;
			if ( data.Availability == 'AVAILABLE') {
				return data;
			} else {
				throw response;
			}
		}
	).then( (data) => {
		console.log('Register your Domain in Route53Domains');
		var params = {
		  AdminContact: parameters.route53.contacts.admin,
		  DomainName: domain,
		  DurationInYears: 1,
		  RegistrantContact: parameters.route53.contacts.registrant,
		  TechContact: parameters.route53.contacts.tech,
		  AutoRenew: true,
		  PrivacyProtectAdminContact: true,
		  PrivacyProtectRegistrantContact: true,
		  PrivacyProtectTechContact: true
		};
		return route53domains.registerDomain(params).promise();
		//context.succeed(data. Availability);
	}).then( (data) => {
		console.log('Domain Registration is Succeeded');
		console.log(data);
		return data;
	}).catch( (err) => {
		console.log('Fail to registration Domain.');
		console.log(err);
		//context.fail(err);
		return err;
	}).then( (data) => {
		console.log('Create EC2 KeyPair');

		var ec2 = new AWS.EC2({
			apiVersion: '2015-10-01',
			accessKeyId: config.accessKeyId,
			secretAccessKey: config.secretAccessKey,
			region: config.region
		});

		var params = {
		  KeyName: "keyfile-" + parameters.accountNo,
		  DryRun: false
		};
		return ec2.createKeyPair(params).promise();
	}).then( (data) => {
		console.log('Send EC2 Keypair to S3 Bucket');
		AWS.config.apiVersions = {
		  s3: '2006-03-01',
		  accessKeyId: config.accessKeyId,
		  secretAccessKey: config.secretAccessKey,
		};

		var s3 = new AWS.S3();
		var params = {
		  Bucket: config.KeyFileBucket,
		  Key: 'keypair/' + data.KeyName + '.pem',
		  ACL: 'bucket-owner-full-control',
		  Body: data.KeyMaterial,
		  ContentType: 'application/x-pem-file',
		  Expires: new Date()
		};
		return s3.putObject(params).promise();
	}).then( (data) => {
		console.log('Create CloudFormation Stack');
		var cloudformation = new AWS.CloudFormation({
			apiVersion: '2010-05-15',
			accessKeyId: config.accessKeyId,
			secretAccessKey: config.secretAccessKey,
			region: config.region
		});

		var ParamKeyName = {
			ParameterKey: 'KeyName',
			ParameterValue: parameters.accountNo,
			UsePreviousValue: true
		};
		var ParamKeyAccountNo = {
			ParameterKey: 'AccountNo',
			ParameterValue: parameters.accountNo,
			UsePreviousValue: true
		};
		var ParamDomainName = {
			ParameterKey: 'DomainName',
			ParameterValue: parameters.route53.domain,
			UsePreviousValue: true
		};
		parameters.cloudformation.parameters.push(ParamKeyName);
		parameters.cloudformation.parameters.push(ParamKeyAccountNo);
		parameters.cloudformation.parameters.push(ParamDomainName);
		var cfnParams = {
			  StackName: parameters.accountNo, /* required */
			  Capabilities: [
				'CAPABILITY_IAM',
			  ],
			  NotificationARNs: [
				config.CfnStatusArn
			  ],
			  Parameters: parameters.cloudformation.parameters,
			  OnFailure: 'DELETE',
			  TemplateURL: config.TemplateURL,
			  TimeoutInMinutes: 100
		};
		return cloudformation.createStack(cfnParams).promise();
	}).then( (data) => {
		console.log('Succeeded to Create CloudFormation');
		console.log(data);
	}).catch( (err) => {
		console.log('Fail to create CloudFormation');
		console.log(err);
		//context.fail(err);
	});
}
```


```config.js
exports.getConf = function() {
	return {
		accessKeyId: "AWS_ACCESS_KEY",
		secretAccessKey: "AWS_SECRET_KEY",
		TemplateURL: 'CLOUDFORMATION_JSON_FILE_URL',
		region: 'REGION',
		KeyFileBucket: 'S3_KEY_FILE_BUCKET_NAME',
		CfnStatusArn: 'SNS_ARN'
	};
};
```

```parameters.js
exports.getParams = function() {
	var baseContact = {
		AddressLine1: 'ADDRESS_LINE_1',
		AddressLine2: 'ADDRESS_LINE_2',
		City: 'CITY',
		ContactType: 'COMPANY',
		CountryCode: 'JP',
		Email: 'EMAIL@ADDRESS',
		FirstName: 'FIRST_NAME',
		LastName: 'LAST_NAME',
		OrganizationName: 'ORGANIZATION_NAME',
		ZipCode: 'XXX-XXXX',
		PhoneNumber : '+81.XXXXXXXXX'
	};
	return {
		accountNo: 'STACK_ID',
		route53: {
			domain: 'DOMAIN',
			contacts: {
				admin: baseContact,
				registrant: baseContact,
				tech: baseContact
			}
		},
		cloudformation: {
			parameters : [
				{
					ParameterKey: 'SSHLocation4EC2',
					ParameterValue: 'SSH_LOCATION',
					UsePreviousValue: true
				},{
					ParameterKey: 'AvailabilityZone',
					ParameterValue: 'AVAILABILITY_ZONE',
					UsePreviousValue: true
				},{
					ParameterKey: 'InstanceType',
					ParameterValue: 'INSTANCE_TYPE',
					UsePreviousValue: true
				}
			]
		}
	};
};
```

ここまで準備してindex.jsを実行すると、
- Route53 Domainsでドメインが取得可能かをチェックする
- 取得可能ならRoute53 Domainsでドメインを取得する
- 取得の成否に関わらずCloudFormationの立ち上げは実行される
- CloudFormationで立ち上げるEC2のためにKeyPairを生成し、S3バケットに保存する
- parametes.jsの値などを使ってCloudFormationを起動する

を勝手にやってくれます。
取得したドメインの承認メール確認や、ドメインとEC2の接続などは別途対応が必要ですが、諸々一発であがるようになると仕事が捗るかなとちょっと夢見てます。