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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +29 -0
  8. data/Rakefile +6 -0
  9. data/automan.gemspec +30 -0
  10. data/bin/baker +4 -0
  11. data/bin/mover +4 -0
  12. data/bin/scanner +4 -0
  13. data/bin/snapper +4 -0
  14. data/bin/stacker +4 -0
  15. data/bin/stalker +4 -0
  16. data/lib/automan.rb +27 -0
  17. data/lib/automan/base.rb +64 -0
  18. data/lib/automan/beanstalk/application.rb +74 -0
  19. data/lib/automan/beanstalk/configuration.rb +137 -0
  20. data/lib/automan/beanstalk/deployer.rb +193 -0
  21. data/lib/automan/beanstalk/errors.rb +22 -0
  22. data/lib/automan/beanstalk/package.rb +39 -0
  23. data/lib/automan/beanstalk/router.rb +102 -0
  24. data/lib/automan/beanstalk/terminator.rb +60 -0
  25. data/lib/automan/beanstalk/uploader.rb +58 -0
  26. data/lib/automan/beanstalk/version.rb +100 -0
  27. data/lib/automan/chef/uploader.rb +30 -0
  28. data/lib/automan/cli/baker.rb +63 -0
  29. data/lib/automan/cli/base.rb +14 -0
  30. data/lib/automan/cli/mover.rb +47 -0
  31. data/lib/automan/cli/scanner.rb +24 -0
  32. data/lib/automan/cli/snapper.rb +78 -0
  33. data/lib/automan/cli/stacker.rb +106 -0
  34. data/lib/automan/cli/stalker.rb +279 -0
  35. data/lib/automan/cloudformation/errors.rb +40 -0
  36. data/lib/automan/cloudformation/launcher.rb +196 -0
  37. data/lib/automan/cloudformation/replacer.rb +102 -0
  38. data/lib/automan/cloudformation/terminator.rb +61 -0
  39. data/lib/automan/cloudformation/uploader.rb +57 -0
  40. data/lib/automan/ec2/errors.rb +4 -0
  41. data/lib/automan/ec2/image.rb +137 -0
  42. data/lib/automan/ec2/instance.rb +83 -0
  43. data/lib/automan/mixins/aws_caller.rb +115 -0
  44. data/lib/automan/mixins/utils.rb +18 -0
  45. data/lib/automan/rds/errors.rb +7 -0
  46. data/lib/automan/rds/snapshot.rb +244 -0
  47. data/lib/automan/s3/downloader.rb +25 -0
  48. data/lib/automan/s3/uploader.rb +20 -0
  49. data/lib/automan/version.rb +3 -0
  50. data/lib/automan/wait_rescuer.rb +17 -0
  51. data/spec/beanstalk/application_spec.rb +49 -0
  52. data/spec/beanstalk/configuration_spec.rb +98 -0
  53. data/spec/beanstalk/deployer_spec.rb +162 -0
  54. data/spec/beanstalk/package_spec.rb +9 -0
  55. data/spec/beanstalk/router_spec.rb +65 -0
  56. data/spec/beanstalk/terminator_spec.rb +67 -0
  57. data/spec/beanstalk/uploader_spec.rb +53 -0
  58. data/spec/beanstalk/version_spec.rb +60 -0
  59. data/spec/chef/uploader_spec.rb +9 -0
  60. data/spec/cloudformation/launcher_spec.rb +240 -0
  61. data/spec/cloudformation/replacer_spec.rb +58 -0
  62. data/spec/cloudformation/templates/worker_role.json +337 -0
  63. data/spec/cloudformation/terminator_spec.rb +63 -0
  64. data/spec/cloudformation/uploader_spec.rb +50 -0
  65. data/spec/ec2/image_spec.rb +158 -0
  66. data/spec/ec2/instance_spec.rb +57 -0
  67. data/spec/mixins/aws_caller_spec.rb +39 -0
  68. data/spec/mixins/utils_spec.rb +44 -0
  69. data/spec/rds/snapshot_spec.rb +152 -0
  70. 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