automan 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/automan.gemspec +30 -0
- data/bin/baker +4 -0
- data/bin/mover +4 -0
- data/bin/scanner +4 -0
- data/bin/snapper +4 -0
- data/bin/stacker +4 -0
- data/bin/stalker +4 -0
- data/lib/automan.rb +27 -0
- data/lib/automan/base.rb +64 -0
- data/lib/automan/beanstalk/application.rb +74 -0
- data/lib/automan/beanstalk/configuration.rb +137 -0
- data/lib/automan/beanstalk/deployer.rb +193 -0
- data/lib/automan/beanstalk/errors.rb +22 -0
- data/lib/automan/beanstalk/package.rb +39 -0
- data/lib/automan/beanstalk/router.rb +102 -0
- data/lib/automan/beanstalk/terminator.rb +60 -0
- data/lib/automan/beanstalk/uploader.rb +58 -0
- data/lib/automan/beanstalk/version.rb +100 -0
- data/lib/automan/chef/uploader.rb +30 -0
- data/lib/automan/cli/baker.rb +63 -0
- data/lib/automan/cli/base.rb +14 -0
- data/lib/automan/cli/mover.rb +47 -0
- data/lib/automan/cli/scanner.rb +24 -0
- data/lib/automan/cli/snapper.rb +78 -0
- data/lib/automan/cli/stacker.rb +106 -0
- data/lib/automan/cli/stalker.rb +279 -0
- data/lib/automan/cloudformation/errors.rb +40 -0
- data/lib/automan/cloudformation/launcher.rb +196 -0
- data/lib/automan/cloudformation/replacer.rb +102 -0
- data/lib/automan/cloudformation/terminator.rb +61 -0
- data/lib/automan/cloudformation/uploader.rb +57 -0
- data/lib/automan/ec2/errors.rb +4 -0
- data/lib/automan/ec2/image.rb +137 -0
- data/lib/automan/ec2/instance.rb +83 -0
- data/lib/automan/mixins/aws_caller.rb +115 -0
- data/lib/automan/mixins/utils.rb +18 -0
- data/lib/automan/rds/errors.rb +7 -0
- data/lib/automan/rds/snapshot.rb +244 -0
- data/lib/automan/s3/downloader.rb +25 -0
- data/lib/automan/s3/uploader.rb +20 -0
- data/lib/automan/version.rb +3 -0
- data/lib/automan/wait_rescuer.rb +17 -0
- data/spec/beanstalk/application_spec.rb +49 -0
- data/spec/beanstalk/configuration_spec.rb +98 -0
- data/spec/beanstalk/deployer_spec.rb +162 -0
- data/spec/beanstalk/package_spec.rb +9 -0
- data/spec/beanstalk/router_spec.rb +65 -0
- data/spec/beanstalk/terminator_spec.rb +67 -0
- data/spec/beanstalk/uploader_spec.rb +53 -0
- data/spec/beanstalk/version_spec.rb +60 -0
- data/spec/chef/uploader_spec.rb +9 -0
- data/spec/cloudformation/launcher_spec.rb +240 -0
- data/spec/cloudformation/replacer_spec.rb +58 -0
- data/spec/cloudformation/templates/worker_role.json +337 -0
- data/spec/cloudformation/terminator_spec.rb +63 -0
- data/spec/cloudformation/uploader_spec.rb +50 -0
- data/spec/ec2/image_spec.rb +158 -0
- data/spec/ec2/instance_spec.rb +57 -0
- data/spec/mixins/aws_caller_spec.rb +39 -0
- data/spec/mixins/utils_spec.rb +44 -0
- data/spec/rds/snapshot_spec.rb +152 -0
- metadata +278 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
require 'automan'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Automan::Beanstalk
|
|
7
|
+
class Deployer < Automan::Base
|
|
8
|
+
add_option :name,
|
|
9
|
+
:version_label,
|
|
10
|
+
:package,
|
|
11
|
+
:environment,
|
|
12
|
+
:manifest,
|
|
13
|
+
:configuration_template,
|
|
14
|
+
:configuration_options,
|
|
15
|
+
:solution_stack_name,
|
|
16
|
+
:number_to_keep,
|
|
17
|
+
:beanstalk_name
|
|
18
|
+
|
|
19
|
+
include Automan::Mixins::Utils
|
|
20
|
+
|
|
21
|
+
def initialize(options=nil)
|
|
22
|
+
@number_to_keep = 0
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def manifest_exists?
|
|
27
|
+
s3_object_exists? manifest
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def read_manifest
|
|
31
|
+
|
|
32
|
+
if !manifest_exists?
|
|
33
|
+
raise MissingManifestError, "Manifest #{manifest} does not exist"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# read manifest from s3 location
|
|
37
|
+
json = s3_read(manifest)
|
|
38
|
+
|
|
39
|
+
# parse it from json
|
|
40
|
+
data = JSON.parse json
|
|
41
|
+
|
|
42
|
+
# set version_label and package
|
|
43
|
+
self.version_label = data['version_label']
|
|
44
|
+
self.package = data['package']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def package_exists?
|
|
48
|
+
s3_object_exists? package
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def eb_environment_name
|
|
52
|
+
# Constraint: Must be from 4 to 23 characters in length.
|
|
53
|
+
# The name can contain only letters, numbers, and hyphens.
|
|
54
|
+
# It cannot start or end with a hyphen.
|
|
55
|
+
env_name = "#{name}-#{environment}"
|
|
56
|
+
unless beanstalk_name.nil?
|
|
57
|
+
env_name = beanstalk_name.dup
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if env_name.length > 23
|
|
61
|
+
env_name = env_name[0,23]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
env_name.gsub!(/[^A-Za-z0-9\-]/, '-')
|
|
65
|
+
|
|
66
|
+
env_name.gsub!(/(^\-+)|(\-+$)/, '')
|
|
67
|
+
|
|
68
|
+
if env_name.length < 4
|
|
69
|
+
env_name += "-Env"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
env_name
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Possible status states:
|
|
76
|
+
# Launching
|
|
77
|
+
# Updating
|
|
78
|
+
# Ready
|
|
79
|
+
# Terminating
|
|
80
|
+
# Terminated
|
|
81
|
+
def environment_status
|
|
82
|
+
opts = {
|
|
83
|
+
environment_names: [eb_environment_name]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
response = eb.describe_environments opts
|
|
87
|
+
|
|
88
|
+
unless response.successful?
|
|
89
|
+
logger.error "describe_environments failed: #{response.error}"
|
|
90
|
+
exit 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
status = nil
|
|
94
|
+
response.data[:environments].each do |env|
|
|
95
|
+
if env[:environment_name] == eb_environment_name
|
|
96
|
+
status = env[:status]
|
|
97
|
+
break
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
status
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def update_environment
|
|
104
|
+
logger.info "updating environment #{eb_environment_name} with version #{version_label}"
|
|
105
|
+
|
|
106
|
+
opts = {
|
|
107
|
+
environment_name: eb_environment_name,
|
|
108
|
+
version_label: version_label
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
response = eb.update_environment opts
|
|
112
|
+
|
|
113
|
+
unless response.successful?
|
|
114
|
+
logger.error "update_environment failed: #{response.error}"
|
|
115
|
+
exit 1
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def create_environment
|
|
121
|
+
logger.info "creating environment #{eb_environment_name} for #{name} with version #{version_label}"
|
|
122
|
+
|
|
123
|
+
opts = {
|
|
124
|
+
application_name: name,
|
|
125
|
+
environment_name: eb_environment_name,
|
|
126
|
+
version_label: version_label,
|
|
127
|
+
template_name: configuration_template,
|
|
128
|
+
cname_prefix: eb_environment_name
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
response = eb.create_environment opts
|
|
132
|
+
|
|
133
|
+
unless response.successful?
|
|
134
|
+
logger.error "create_environment failed: #{response.error}"
|
|
135
|
+
exit 1
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def get_version
|
|
141
|
+
v = Automan::Beanstalk::Version.new
|
|
142
|
+
v.application = name
|
|
143
|
+
v.label = version_label
|
|
144
|
+
v
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def ensure_version_exists
|
|
148
|
+
version = get_version()
|
|
149
|
+
|
|
150
|
+
if version.exists?
|
|
151
|
+
logger.warn "version #{version_label} for #{name} already exists. Not creating."
|
|
152
|
+
else
|
|
153
|
+
version.create(package)
|
|
154
|
+
|
|
155
|
+
if number_to_keep > 0
|
|
156
|
+
version.cull_versions(number_to_keep)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def create_or_update_environment
|
|
162
|
+
case environment_status
|
|
163
|
+
when nil, "Terminated"
|
|
164
|
+
create_environment
|
|
165
|
+
when "Ready"
|
|
166
|
+
update_environment
|
|
167
|
+
else
|
|
168
|
+
raise InvalidEnvironmentStatusError, "Could not update environment as it's status is #{environment_status}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def deploy
|
|
173
|
+
logger.info "Deploying service."
|
|
174
|
+
|
|
175
|
+
if !manifest.nil?
|
|
176
|
+
read_manifest
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
log_options
|
|
180
|
+
|
|
181
|
+
unless package_exists?
|
|
182
|
+
raise MissingPackageFileError, "package #{package} does not exist."
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
ensure_version_exists
|
|
186
|
+
|
|
187
|
+
create_or_update_environment
|
|
188
|
+
|
|
189
|
+
logger.info "Finished deploying service."
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Automan::Beanstalk
|
|
2
|
+
class RequestFailedError < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class ELBNameNotFoundError < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class MissingManifestError < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class MissingPackageFileError < StandardError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class InvalidEnvironmentStatusError < StandardError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class NoConfigurationTemplatesError < StandardError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class InvalidConfigurationTemplateError < StandardError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'automan'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Automan::Beanstalk
|
|
6
|
+
class Package < Automan::Base
|
|
7
|
+
add_option :destination, :source, :manifest, :version_label
|
|
8
|
+
|
|
9
|
+
include Automan::Mixins::Utils
|
|
10
|
+
|
|
11
|
+
def upload_package
|
|
12
|
+
log_options
|
|
13
|
+
|
|
14
|
+
# verify local package exists
|
|
15
|
+
unless File.exists? source
|
|
16
|
+
raise MissingPackageFile, "package file #{source} does not exist"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
logger.info "Uploading #{source} to #{destination}"
|
|
20
|
+
|
|
21
|
+
# upload package file
|
|
22
|
+
bucket, key = parse_s3_path destination
|
|
23
|
+
s3.buckets[bucket].objects[key].write(Pathname.new(source))
|
|
24
|
+
|
|
25
|
+
# upload manifest file
|
|
26
|
+
if !manifest.nil?
|
|
27
|
+
logger.info "Uploading manifest file for #{version_label} to #{manifest}"
|
|
28
|
+
contents = {
|
|
29
|
+
"version_label" => version_label,
|
|
30
|
+
"package" => destination
|
|
31
|
+
}.to_json
|
|
32
|
+
|
|
33
|
+
bucket, key = parse_s3_path manifest
|
|
34
|
+
s3.buckets[bucket].objects[key].write(contents)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'automan'
|
|
2
|
+
require 'wait'
|
|
3
|
+
|
|
4
|
+
module Automan::Beanstalk
|
|
5
|
+
class Router < Automan::Base
|
|
6
|
+
|
|
7
|
+
add_option :environment_name,
|
|
8
|
+
:hosted_zone_name,
|
|
9
|
+
:hostname
|
|
10
|
+
|
|
11
|
+
attr_accessor :attempts_made
|
|
12
|
+
|
|
13
|
+
def initialize(options=nil)
|
|
14
|
+
super
|
|
15
|
+
@wait = Wait.new({
|
|
16
|
+
attempts: 10,
|
|
17
|
+
delay: 60, # 10 x 60s == 10m
|
|
18
|
+
debug: true,
|
|
19
|
+
logger: @logger,
|
|
20
|
+
|
|
21
|
+
# rescue from InvalidChangeBatch
|
|
22
|
+
# just because we've found the elb cname doesn't
|
|
23
|
+
# mean it is immediately available as a route53 alias
|
|
24
|
+
rescuer: WaitRescuer.new(AWS::Route53::Errors::InvalidChangeBatch)
|
|
25
|
+
})
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def elb_cname_from_beanstalk_environment(env_name)
|
|
29
|
+
opts = {
|
|
30
|
+
environment_name: env_name
|
|
31
|
+
}
|
|
32
|
+
response = eb.describe_environment_resources opts
|
|
33
|
+
|
|
34
|
+
unless response.successful?
|
|
35
|
+
raise RequestFailedError, "describe_environment_resources failed: #{response.error}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
balancers = response.data[:environment_resources][:load_balancers]
|
|
39
|
+
if balancers.empty?
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
balancers.first[:name]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# be sure alias_name ends in period
|
|
47
|
+
def update_dns_alias(zone_name, alias_name, elb_data)
|
|
48
|
+
zone = r53.hosted_zones.select {|z| z.name == zone_name}.first
|
|
49
|
+
rrset = zone.rrsets[alias_name, 'A']
|
|
50
|
+
|
|
51
|
+
target = elb_data.dns_name.downcase
|
|
52
|
+
|
|
53
|
+
if rrset.exists?
|
|
54
|
+
if rrset.alias_target[:dns_name] == target + '.'
|
|
55
|
+
logger.info "record exists: #{alias_name} -> #{rrset.alias_target[:dns_name]}"
|
|
56
|
+
return
|
|
57
|
+
else
|
|
58
|
+
logger.info "removing old record #{alias_name} -> #{rrset.alias_target[:dns_name]}"
|
|
59
|
+
rrset.delete
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
logger.info "creating record #{alias_name} -> #{target}"
|
|
64
|
+
zone.rrsets.create(
|
|
65
|
+
alias_name,
|
|
66
|
+
'A',
|
|
67
|
+
alias_target: {
|
|
68
|
+
hosted_zone_id: elb_data.canonical_hosted_zone_name_id,
|
|
69
|
+
dns_name: target,
|
|
70
|
+
evaluate_target_health: false
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def run
|
|
76
|
+
log_options
|
|
77
|
+
|
|
78
|
+
wait.until do |attempt|
|
|
79
|
+
|
|
80
|
+
self.attempts_made = attempt
|
|
81
|
+
|
|
82
|
+
logger.info "waiting for elb cname..."
|
|
83
|
+
elb_name = elb_cname_from_beanstalk_environment environment_name
|
|
84
|
+
|
|
85
|
+
if elb_name.nil?
|
|
86
|
+
false
|
|
87
|
+
else
|
|
88
|
+
|
|
89
|
+
elb_data = elb.load_balancers[elb_name]
|
|
90
|
+
|
|
91
|
+
update_dns_alias(
|
|
92
|
+
hosted_zone_name,
|
|
93
|
+
hostname,
|
|
94
|
+
elb_data
|
|
95
|
+
)
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'automan'
|
|
2
|
+
|
|
3
|
+
module Automan::Beanstalk
|
|
4
|
+
class Terminator < Automan::Base
|
|
5
|
+
add_option :name
|
|
6
|
+
|
|
7
|
+
def environment_exists?(environment_name)
|
|
8
|
+
opts = {
|
|
9
|
+
environment_names: [ environment_name ]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
response = eb.describe_environments opts
|
|
13
|
+
|
|
14
|
+
unless response.successful?
|
|
15
|
+
raise RequestFailedError "describe_environments failed: #{response.error}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
response.data[:environments].each do |e|
|
|
19
|
+
if e[:environment_name] == environment_name
|
|
20
|
+
logger.debug "#{environment_name} has status: #{e[:status]}"
|
|
21
|
+
|
|
22
|
+
case e[:status]
|
|
23
|
+
when 'Terminated', 'Terminating'
|
|
24
|
+
return false
|
|
25
|
+
else
|
|
26
|
+
return true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def terminate_environment
|
|
36
|
+
logger.info "terminating environment #{name}"
|
|
37
|
+
|
|
38
|
+
opts = {
|
|
39
|
+
environment_name: name
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
response = eb.terminate_environment opts
|
|
43
|
+
|
|
44
|
+
unless response.successful?
|
|
45
|
+
raise RequestFailedError "terminate_environment failed: #{response.error}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def terminate
|
|
50
|
+
log_options
|
|
51
|
+
|
|
52
|
+
if !environment_exists?(name)
|
|
53
|
+
logger.warn "Environment #{name} does not exist. Doing nothing."
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
terminate_environment
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'automan'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Automan::Beanstalk
|
|
5
|
+
class Uploader < Automan::Base
|
|
6
|
+
add_option :template_files, :s3_path
|
|
7
|
+
|
|
8
|
+
def config_templates
|
|
9
|
+
Dir.glob template_files
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read_config_template(file)
|
|
13
|
+
File.read file
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def config_templates_valid?
|
|
17
|
+
valid = true
|
|
18
|
+
templates = config_templates
|
|
19
|
+
|
|
20
|
+
if templates.empty?
|
|
21
|
+
raise NoConfigurationTemplatesError, "No configuration template files found for #{template_files}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
templates.each do |template|
|
|
25
|
+
raw = read_config_template template
|
|
26
|
+
begin
|
|
27
|
+
JSON.parse(raw)
|
|
28
|
+
rescue Exception => e
|
|
29
|
+
logger.warn "#{template} is invalid json: #{e.message}"
|
|
30
|
+
valid = false
|
|
31
|
+
next
|
|
32
|
+
end
|
|
33
|
+
logger.debug "#{template} is valid json"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
valid
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def upload_file(file)
|
|
40
|
+
opts = {
|
|
41
|
+
localfile: file,
|
|
42
|
+
s3file: "#{s3_path}/#{File.basename(file)}"
|
|
43
|
+
}
|
|
44
|
+
s = Automan::S3::Uploader.new opts
|
|
45
|
+
s.upload
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def upload_config_templates
|
|
49
|
+
unless config_templates_valid?
|
|
50
|
+
raise InvalidConfigurationTemplateError, "There are invalid configuration templates. Halting upload."
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
config_templates.each do |template|
|
|
54
|
+
upload_file(template)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|