awsdsl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ae154e947361276d4545c34fffffbc0eb07caf3e
4
+ data.tar.gz: b0a01325a610b74f8d8a9b38bf314a79e9f85324
5
+ SHA512:
6
+ metadata.gz: 5537d5bfd37391d089dec9988d5af873d04c4b0a9e5a4ee748684a061a51d4b5e6535b06523d686b0f24a595b5efc85e235b96cded6251e8c1738119a4a0f929
7
+ data.tar.gz: eedb01fb25690bd8c417f9dadfc3cd6f7f65ea1088ace302a50bc8538e88d6920724b929151d316a6bd113f73a0b608b8606b877aa9bfe8619f479024873d851
data/.gitignore ADDED
@@ -0,0 +1,28 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rvmrc
6
+ .yardoc
7
+ /Gemfile.lock
8
+ _yardoc
9
+ /coverage
10
+ /doc/
11
+ /pkg
12
+ /spec/reports
13
+ /Berksfile*
14
+ tmp
15
+ *~
16
+ *.tar*
17
+ \#*
18
+ .DS_Store
19
+ /spec/knife.rb
20
+ /spec/*.pem
21
+ /features/config.yml
22
+ *.sw[op]
23
+ \.\#*
24
+ rerun.txt
25
+ .rspec
26
+ .kitchen
27
+ vendor/ruby
28
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'rspec'
7
+ gem 'pry'
data/Gemfile.lock ADDED
@@ -0,0 +1,167 @@
1
+ GIT
2
+ remote: https://github.com/josephglanville/gersberms
3
+ revision: b979728441156433c8a57d845a058c1b51e5e755
4
+ specs:
5
+ gersberms (0.0.1)
6
+ aws-sdk (~> 1.0)
7
+ berkshelf (~> 3.2.1)
8
+ net-scp (~> 1.2.1)
9
+ net-ssh (~> 2.9.1)
10
+ rake
11
+ thor (~> 0.19)
12
+
13
+ PATH
14
+ remote: .
15
+ specs:
16
+ awsdsl (0.0.1)
17
+ activesupport (~> 4)
18
+ aws-sdk (~> 1.0)
19
+ cfndsl
20
+ clamp (~> 0.6)
21
+ gersberms
22
+ netaddr
23
+
24
+ GEM
25
+ remote: https://rubygems.org/
26
+ specs:
27
+ activesupport (4.2.1)
28
+ i18n (~> 0.7)
29
+ json (~> 1.7, >= 1.7.7)
30
+ minitest (~> 5.1)
31
+ thread_safe (~> 0.3, >= 0.3.4)
32
+ tzinfo (~> 1.1)
33
+ addressable (2.3.7)
34
+ aws-sdk (1.63.0)
35
+ aws-sdk-v1 (= 1.63.0)
36
+ aws-sdk-v1 (1.63.0)
37
+ json (~> 1.4)
38
+ nokogiri (>= 1.4.4)
39
+ berkshelf (3.2.3)
40
+ addressable (~> 2.3.4)
41
+ berkshelf-api-client (~> 1.2)
42
+ buff-config (~> 1.0)
43
+ buff-extensions (~> 1.0)
44
+ buff-shell_out (~> 0.1)
45
+ celluloid (~> 0.16.0)
46
+ celluloid-io (~> 0.16.1)
47
+ cleanroom (~> 1.0)
48
+ faraday (~> 0.9.0)
49
+ minitar (~> 0.5.4)
50
+ octokit (~> 3.0)
51
+ retryable (~> 2.0)
52
+ ridley (~> 4.0)
53
+ solve (~> 1.1)
54
+ thor (~> 0.19)
55
+ berkshelf-api-client (1.2.1)
56
+ faraday (~> 0.9.0)
57
+ buff-config (1.0.1)
58
+ buff-extensions (~> 1.0)
59
+ varia_model (~> 0.4)
60
+ buff-extensions (1.0.0)
61
+ buff-ignore (1.1.1)
62
+ buff-ruby_engine (0.1.0)
63
+ buff-shell_out (0.2.0)
64
+ buff-ruby_engine (~> 0.1.0)
65
+ celluloid (0.16.0)
66
+ timers (~> 4.0.0)
67
+ celluloid-io (0.16.2)
68
+ celluloid (>= 0.16.0)
69
+ nio4r (>= 1.1.0)
70
+ cfndsl (0.1.14)
71
+ clamp (0.6.4)
72
+ cleanroom (1.0.0)
73
+ coderay (1.1.0)
74
+ dep-selector-libgecode (1.0.2)
75
+ dep_selector (1.0.3)
76
+ dep-selector-libgecode (~> 1.0)
77
+ ffi (~> 1.9)
78
+ diff-lcs (1.2.5)
79
+ erubis (2.7.0)
80
+ faraday (0.9.1)
81
+ multipart-post (>= 1.2, < 3)
82
+ ffi (1.9.8)
83
+ hashie (2.1.2)
84
+ hitimes (1.2.2)
85
+ i18n (0.7.0)
86
+ json (1.8.2)
87
+ method_source (0.8.2)
88
+ mini_portile (0.6.2)
89
+ minitar (0.5.4)
90
+ minitest (5.6.0)
91
+ mixlib-authentication (1.3.0)
92
+ mixlib-log
93
+ mixlib-log (1.6.0)
94
+ multipart-post (2.0.0)
95
+ net-http-persistent (2.9.4)
96
+ net-scp (1.2.1)
97
+ net-ssh (>= 2.6.5)
98
+ net-ssh (2.9.2)
99
+ netaddr (1.5.0)
100
+ nio4r (1.1.0)
101
+ nokogiri (1.6.6.2)
102
+ mini_portile (~> 0.6.0)
103
+ octokit (3.8.0)
104
+ sawyer (~> 0.6.0, >= 0.5.3)
105
+ pry (0.10.1)
106
+ coderay (~> 1.1.0)
107
+ method_source (~> 0.8.1)
108
+ slop (~> 3.4)
109
+ rake (10.4.2)
110
+ retryable (2.0.1)
111
+ ridley (4.1.2)
112
+ addressable
113
+ buff-config (~> 1.0)
114
+ buff-extensions (~> 1.0)
115
+ buff-ignore (~> 1.1)
116
+ buff-shell_out (~> 0.1)
117
+ celluloid (~> 0.16.0)
118
+ celluloid-io (~> 0.16.1)
119
+ erubis
120
+ faraday (~> 0.9.0)
121
+ hashie (>= 2.0.2, < 3.0.0)
122
+ json (>= 1.7.7)
123
+ mixlib-authentication (>= 1.3.0)
124
+ net-http-persistent (>= 2.8)
125
+ retryable (>= 2.0.0)
126
+ semverse (~> 1.1)
127
+ varia_model (~> 0.4)
128
+ rspec (3.2.0)
129
+ rspec-core (~> 3.2.0)
130
+ rspec-expectations (~> 3.2.0)
131
+ rspec-mocks (~> 3.2.0)
132
+ rspec-core (3.2.2)
133
+ rspec-support (~> 3.2.0)
134
+ rspec-expectations (3.2.0)
135
+ diff-lcs (>= 1.2.0, < 2.0)
136
+ rspec-support (~> 3.2.0)
137
+ rspec-mocks (3.2.1)
138
+ diff-lcs (>= 1.2.0, < 2.0)
139
+ rspec-support (~> 3.2.0)
140
+ rspec-support (3.2.2)
141
+ sawyer (0.6.0)
142
+ addressable (~> 2.3.5)
143
+ faraday (~> 0.8, < 0.10)
144
+ semverse (1.2.1)
145
+ slop (3.6.0)
146
+ solve (1.2.1)
147
+ dep_selector (~> 1.0)
148
+ semverse (~> 1.1)
149
+ thor (0.19.1)
150
+ thread_safe (0.3.5)
151
+ timers (4.0.1)
152
+ hitimes
153
+ tzinfo (1.2.2)
154
+ thread_safe (~> 0.1)
155
+ varia_model (0.4.0)
156
+ buff-extensions (~> 1.0)
157
+ hashie (>= 2.0.2, < 3.0.0)
158
+
159
+ PLATFORMS
160
+ ruby
161
+
162
+ DEPENDENCIES
163
+ awsdsl!
164
+ gersberms!
165
+ pry
166
+ rake
167
+ rspec
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ AWS DSL
2
+ ======
3
+
4
+ This project is an opinionated take on running applications on AWS.
5
+ It leverages [CloudFormation](http://aws.amazon.com/cloudformation/) and [Gersberms](https://github.com/josephglanville/gersberms) to build your application into an Amazon Machine Image and deploy it on bare EC2 servers.
6
+
7
+ In a nutshell you declare your infrastructure in a Stackfile, which is a Ruby DSL that describes CloudFormation resources and AMI build instructions.
8
+
9
+ It is in some ways analagous to OpsWorks however it's less intrusive and focuses on building immutable AMIs and replacing machines during updates rather than updating already running machines by re-running Chef.
10
+
11
+ That said it you are already using OpsWorks it would be easy to get started using AWS DSL.
12
+
13
+ Install
14
+ -------
15
+
16
+ For now I recommending using Bundler to install and manage AWS DSL.
17
+ Simply add this to your Gemfile
18
+
19
+ ```ruby
20
+ gem 'awsdsl', git: 'https://github.com/josephglanville/awsdsl'
21
+ ```
22
+
23
+ I will publish the gem to RubyGems when I feel it's stabilized.
24
+
25
+ Getting Started
26
+ ---------------
27
+
28
+ To get started with AWS DSL you need a few things.
29
+
30
+ * Your application deployable using Chef.
31
+ * Your Chef cookbooks managed with Berkshelf.
32
+ * Basic understanding of EC2, ELB and any other resources you might need.
33
+
34
+ Because AWS DSL abstracts away the vast majority of CloudFormation you shouldn't need an indepth understanding of CloudFormation but it doesn't hurt.
35
+
36
+ Simple Stackfile
37
+ ----------------
38
+
39
+ ```ruby
40
+ stack 'static' do
41
+ description 'static files example'
42
+
43
+ vpc 'static' do
44
+ region 'ap-southeast-2'
45
+ subnet 'public' do
46
+ az 'a', 'b'
47
+ end
48
+ end
49
+
50
+ role 'nginx' do
51
+ vpc 'static'
52
+ subnet 'public'
53
+ load_balancer 'static' do
54
+ listener port: 80
55
+ health_check target: 'HTTP:80/'
56
+ end
57
+ update_policy min_inservice: 0
58
+ instance_type 't2.micro'
59
+ key_pair 'joseph@reinteractive.net'
60
+ chef_provisioner runlist: 'nginx'
61
+ vars nginx: {
62
+ init_style: 'upstart'
63
+ }
64
+ end
65
+ end
66
+ ```
67
+
68
+ This simple stack declares a simple role along with provisioning a full VPC.
69
+ Said VPC includes 2 subnets across 2 AZs and an Internet Gateway to allow public addressing to work.
70
+ It runs the nginx::default recipe when preparing the AMI, providing the contents of vars as the node attributes.
71
+
72
+ Complex Stackfile
73
+ -----------------
74
+
75
+ ```ruby
76
+ stack 'logs' do
77
+ description 'logstash cluster'
78
+ ssl_cert_arn = 'arn:aws:iam::account_id::certificate'
79
+ zone_arn = 'arn:aws:route53:::hostedzone/zone_id'
80
+ snapshot_bucket_arn = 'arn:aws:s3:::snapshot_bucket'
81
+ cloudtrail_queue_arn = 'arn:aws:sqs:ap-southeast-2:account_id:queue'
82
+
83
+ role_profile 'es_comms' do
84
+ security_group 'sg-id'
85
+ subnets 'subnet-id'
86
+ vpc 'vpc-id'
87
+ end
88
+
89
+ role_profile 'es_bucket' do
90
+ policy_statement effect: 'Allow', action: 's3:ListBucket', resource: snapshot_bucket_arn
91
+ policy_statement effect: 'Allow',
92
+ action: %w(s3:GetObject s3:PutObjecs3:DeleteObject s3:DeleteObject),
93
+ resource: "#{snapshot_bucket_arn}/*"
94
+ end
95
+
96
+ role_profile 'ec2_discovery' do
97
+ policy_statement effect: 'Allow', action: 'ec2:DescribeInstances', resource: '*'
98
+ end
99
+
100
+ role 'logstash' do
101
+ include_profile 'ec2_discovery', 'es_comms'
102
+ load_balancer 'logstash' do
103
+ listener port: 80
104
+ listener port: 443, proto: 'HTTPS', cert: ssl_cert_arn
105
+ listener port: 9000, proto: 'TCP'
106
+ dns_record name: 'logstash.zone.com', zone: 'zone-id'
107
+ health_check target: 'HTTP:80/health'
108
+ end
109
+ policy_statement effect: 'Allow', action: 'sqs:*', resource: cloudtrail_queue_arn
110
+ min_size 2
111
+ max_size 4
112
+ tgt_size 2
113
+ update_policy pause_time: '5M'
114
+ instance_type 't2.micro'
115
+ chef_provisioner runlist: 'logstash'
116
+ end
117
+
118
+ role 'elasticsearch' do
119
+ include_profile 'ec2_discovery', 'es_bucket', 'es_comms'
120
+ load_balancer 'elasticsearch' do
121
+ listener port: 9200
122
+ health_check target: 'HTTP:9200/'
123
+ dns_record name: 'elasticsearch.zone.com', zone: 'zone-id'
124
+ security_group 'sg-id'
125
+ internal true
126
+ end
127
+ min_size 3
128
+ max_size 5
129
+ tgt_size 5
130
+ update_policy pause_time: '10M', min_inservice: 3
131
+ instance_type 't2.micro'
132
+ block_device name: '/dev/sda1', size: 20
133
+ chef_provisioner runlist: 'elasticsearch'
134
+ allow role: 'logstash', ports: 9200
135
+ allow role: 'utility', ports: 9200
136
+ allow role: 'elasticsearch', ports: 9200
137
+ end
138
+
139
+ role 'utility' do
140
+ include_profile 'es_bucket', 'es_comms'
141
+ policy_statement effect: 'Allow',
142
+ action: [
143
+ 'route53:ChangeResourceRecordSets',
144
+ 'route53:GetHostedZone',
145
+ 'route53:ListResourceRecordSets'
146
+ ],
147
+ resource: zone_arn
148
+ policy_statement effect: 'Allow', action: 'route53:ListHostedZones', resource: '*'
149
+ min_size 0
150
+ max_size 1
151
+ tgt_size 1
152
+ update_policy min_inservice: 0
153
+ instance_type 't2.micro'
154
+ chef_provisioner runlist: 'utility'
155
+ end
156
+
157
+ elasticache 'redis' do
158
+ vpc 'vpc-id'
159
+ subnet 'subnet-id'
160
+ engine 'redis'
161
+ node_type 't2.micro'
162
+ allow role: 'logstash'
163
+ end
164
+ end
165
+ ```
166
+
167
+ This much more complex example deploys a full Elasticsearch Logstash Kibana cluster. Along with standing up an Elasticache cluster running Redis.
168
+ It uses advanced features like Role Profiles (which are effectively Role mixins) and the powerful "allow" syntax which makes configuring security groups a breeze.
169
+
170
+ As you can see AWS DSL doesn't get in your way if you need to declare additional policy documents or do advanced things like setup SSL listeners or configure DNS records for your ELBs.
171
+
172
+ Command Line
173
+ ------------
174
+
175
+ Once you have your Stackfile you will need to build the AMIs.
176
+
177
+ ```
178
+ bundle exec awsdsl build
179
+ ```
180
+
181
+ Then create your stack
182
+
183
+ ```
184
+ bundle exec awsdsl create
185
+ ```
186
+
187
+ When you build new AMIs or update settings in your Stackfile you can push updates like so
188
+
189
+ ```
190
+ bundle exec awsdsl update
191
+ ```
192
+
193
+ Philosophy
194
+ ----------
195
+
196
+ AWS DSL was written to enable the versioning of infrastructure alongside code.
197
+ All infrastructure concerns are declared in the AWS DSL Stackfile including how to configure the application runtime environment which will be built into an AMI.
198
+ This is advantagous as updates to code that require infrastructure support can be done in unison.
199
+
200
+ AWS DSL thinks about your application in terms of Roles. A role is a singular purposed entity in your application and represents a build target and a scaling primitive.
201
+ You specify how to package your application into an AMI, tell it how many instances you want to run and any other considerations like security groups and away it goes.
202
+
203
+ To DRY up this process AWS DSL has Role Profiles. Role Profiles are analagous to mixins (or multiple inheritance if you must), anything you can put in a Role can be put in Role Profile and then you can mixin multiple Role Profiles into a Role with the include_profile keyword.
204
+
205
+
206
+
207
+ TODO
208
+ ----
209
+
210
+ * cloud-init/cfn-init integration and environment variable system
211
+ * build AMIs seperately
212
+ * cfndsl section to allow arbitrary resource creation
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task default: [:spec]
6
+
7
+ task :integration do
8
+ ENV['INTEGRATION'] = 'true'
9
+ Rake::Task['spec'].execute
10
+ end
data/awsdsl.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'awsdsl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'awsdsl'
8
+ spec.version = AWSDSL::VERSION
9
+ spec.authors = ['Joseph Glanville']
10
+ spec.email = ['jpg@jpg.id.au']
11
+ spec.summary = 'A simple DSL for deploying and running apps on AWS'
12
+ spec.description = 'A simple DSL for deploying and running apps on AWS'
13
+ spec.homepage = 'https://github.com/josephglanville/awsdsl'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'aws-sdk', '~> 1.0'
22
+ spec.add_dependency 'activesupport', '~> 4'
23
+ spec.add_dependency 'clamp', '~> 0.6'
24
+ spec.add_dependency 'cfndsl', '~> 0.1'
25
+ spec.add_dependency 'gersberms', '~> 1.0'
26
+ spec.add_dependency 'netaddr', '~> 1.5'
27
+ end
data/bin/awsdsl ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'awsdsl/command_line'
3
+
4
+ AWSDSL::CommandLine.run
@@ -0,0 +1,98 @@
1
+ require 'gersberms'
2
+ require 'awsdsl/base_ami'
3
+
4
+ module AWSDSL
5
+ class AMIBuilder
6
+ def initialize(stack)
7
+ @stack = stack
8
+ end
9
+
10
+ def build
11
+ @stack.roles.each do |role|
12
+ build_ami(role)
13
+ end
14
+ end
15
+
16
+ def latest_amis
17
+ @stack.roles.each do |role|
18
+ role.ami = latest_ami(role).id
19
+ end
20
+ @stack
21
+ end
22
+
23
+ def self.build(stack)
24
+ AMIBuilder.new(stack).build
25
+ end
26
+
27
+ def self.latest_amis(stack)
28
+ AMIBuilder.new(stack).latest_amis
29
+ end
30
+
31
+ def build_ami(role)
32
+ output_ami = ami_name(role)
33
+ # TODO(jpg): This needs to be better, also deep_merge
34
+ json = (@stack.vars || {}).merge(role.vars || {})
35
+ @builder = Gersberms::Gersberms.new base_ami: base_ami(role),
36
+ ami_name: output_ami,
37
+ json: json
38
+ begin
39
+ start_builder
40
+ role.file_provisioners.each do |provisioner|
41
+ @builder.options[:files] = provisioner
42
+ @builder.upload_files
43
+ end
44
+ role.chef_provisioners.each do |provisioner|
45
+ runlist = provisioner[:runlist]
46
+ runlist = [runlist] unless runlist.is_a? Array
47
+ @builder.options[:runlist] = runlist
48
+ @builder.run_chef
49
+ end
50
+ shutdown_builder
51
+ role.ami = @builder.image.id
52
+ rescue => e
53
+ @builder.destroy_instance
54
+ @builder.destroy_keypair
55
+ raise "Failed to build AMI for #{role.name}:\nError: #{e.message}\nBacktrace: #{e.backtrace.join("\n")}"
56
+ end
57
+ end
58
+
59
+ def start_builder
60
+ @builder.preflight
61
+ @builder.create_keypair
62
+ @builder.create_instance
63
+ @builder.install_chef
64
+ @builder.upload_cookbooks
65
+ end
66
+
67
+ def shutdown_builder
68
+ @builder.stop_instance
69
+ @builder.create_ami
70
+ @builder.destroy_instance
71
+ @builder.destroy_keypair
72
+ end
73
+
74
+ def base_ami(role)
75
+ base = role.base_ami || 'ubuntu'
76
+ if BaseAMI::DISTROS.include?(base)
77
+ base = BaseAMI.find(base)
78
+ end
79
+ base
80
+ end
81
+
82
+ def ami_name(role)
83
+ last = latest_ami(role)
84
+ num = last.name.split('-').last.to_i + 1 if last
85
+ num ||= 1
86
+ "#{@stack.name}-#{role.name}-#{num}"
87
+ end
88
+
89
+ def latest_ami(role)
90
+ ec2 = AWS::EC2.new
91
+ amis = ec2.images.with_owner('self').select do |i|
92
+ i.name.start_with?("#{@stack.name}-#{role.name}")
93
+ end
94
+ latest_num = amis.map { |i| i.name.split('-').last.to_i }.sort.last
95
+ amis.select { |i| i.name == "#{@stack.name}-#{role.name}-#{latest_num}" }.first
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ module AWSDSL
2
+ module BaseAMI
3
+ DISTROS = %w(ubuntu)
4
+
5
+ def self.find(distro)
6
+ send(distro)
7
+ end
8
+
9
+ def self.ubuntu
10
+ ec2 = AWS::EC2.new
11
+ ami = ec2.images.with_owner('099720109477')
12
+ .filter('name', 'ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server*')
13
+ .sort_by(&:name).last
14
+ ami.id
15
+ end
16
+ end
17
+ end