ruby_aem_aws_odysseas 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/conf/gem.yaml +1 -0
  3. data/lib/ruby_aem_aws/abstract/cloudwatch.rb +83 -0
  4. data/lib/ruby_aem_aws/abstract/component.rb +54 -0
  5. data/lib/ruby_aem_aws/abstract/grouped_component.rb +39 -0
  6. data/lib/ruby_aem_aws/abstract/single_component.rb +36 -0
  7. data/lib/ruby_aem_aws/abstract/snapshot.rb +35 -0
  8. data/lib/ruby_aem_aws/abstract/stackmanager.rb +68 -0
  9. data/lib/ruby_aem_aws/architecture/consolidated_stack.rb +49 -0
  10. data/lib/ruby_aem_aws/architecture/full_set_stack.rb +141 -0
  11. data/lib/ruby_aem_aws/architecture/stack_manager.rb +44 -0
  12. data/lib/ruby_aem_aws/client/cloudwatch.rb +87 -0
  13. data/lib/ruby_aem_aws/client/dynamo_db.rb +36 -0
  14. data/lib/ruby_aem_aws/client/s3.rb +42 -0
  15. data/lib/ruby_aem_aws/client/sns_topic.rb +30 -0
  16. data/lib/ruby_aem_aws/component/author.rb +71 -0
  17. data/lib/ruby_aem_aws/component/author_dispatcher.rb +84 -0
  18. data/lib/ruby_aem_aws/component/author_primary.rb +62 -0
  19. data/lib/ruby_aem_aws/component/author_publish_dispatcher.rb +56 -0
  20. data/lib/ruby_aem_aws/component/author_standby.rb +62 -0
  21. data/lib/ruby_aem_aws/component/chaos_monkey.rb +78 -0
  22. data/lib/ruby_aem_aws/component/component_descriptor.rb +28 -0
  23. data/lib/ruby_aem_aws/component/orchestrator.rb +78 -0
  24. data/lib/ruby_aem_aws/component/publish.rb +78 -0
  25. data/lib/ruby_aem_aws/component/publish_dispatcher.rb +83 -0
  26. data/lib/ruby_aem_aws/component/stack_manager_resources.rb +90 -0
  27. data/lib/ruby_aem_aws/constants.rb +53 -0
  28. data/lib/ruby_aem_aws/error.rb +68 -0
  29. data/lib/ruby_aem_aws/mixins/healthy_count_verifier.rb +154 -0
  30. data/lib/ruby_aem_aws/mixins/healthy_resource_verifier.rb +188 -0
  31. data/lib/ruby_aem_aws/mixins/healthy_state_verifier.rb +45 -0
  32. data/lib/ruby_aem_aws/mixins/instance_describer.rb +34 -0
  33. data/lib/ruby_aem_aws/mixins/metric_verifier.rb +189 -0
  34. data/lib/ruby_aem_aws/mixins/snapshot_verifier.rb +37 -0
  35. data/lib/ruby_aem_aws_odysseas.rb +152 -0
  36. metadata +148 -0
@@ -0,0 +1,45 @@
1
+ # Copyright 2018 Shine Solutions
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../constants'
16
+
17
+ module RubyAemAws
18
+ # Mixin for checking health of a component via EC2 instance state.
19
+ # Add this to a component to make it capable of determining its own health.
20
+ module HealthyStateVerifier
21
+ # @return true if there are one or more instances matching the descriptor and they are all healthy.
22
+ def healthy?
23
+ has_instance = false
24
+ get_all_instances.each do |i|
25
+ next if i.nil? || i.state.code != Constants::INSTANCE_STATE_CODE_RUNNING
26
+
27
+ has_instance = true
28
+ return false if i.state.name != Constants::INSTANCE_STATE_HEALTHY
29
+ end
30
+ has_instance
31
+ end
32
+
33
+ def wait_until_healthy
34
+ instance_healthy = false
35
+ get_all_instances.each do |i|
36
+ next if i.nil? || i.state.code != Constants::INSTANCE_STATE_CODE_RUNNING
37
+
38
+ i.wait_until_running
39
+ instance_healthy = true
40
+ return false if i.state.name != Constants::INSTANCE_STATE_HEALTHY
41
+ end
42
+ instance_healthy
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright 2018 Shine Solutions
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module RubyAemAws
16
+ # Mixin for describing component EC2 instance state.
17
+ # Add this to a component to make it capable of describing its instances.
18
+ module InstanceDescriber
19
+ # @return a string containing instance descriptions.
20
+ def describe_instances
21
+ descriptions = []
22
+ get_all_instances.each do |i|
23
+ next if i.nil?
24
+
25
+ descriptions.push(describe_instance(i))
26
+ end
27
+ descriptions.join(', ')
28
+ end
29
+
30
+ def describe_instance(instance)
31
+ "#{instance.instance_id} (#{instance.state.name})"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,189 @@
1
+ # Copyright 2018 Shine Solutions
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative '../abstract/cloudwatch'
16
+ require_relative '../client/cloudwatch'
17
+
18
+ module RubyAemAws
19
+ # Mixin for checking that an instance has associated CloudWatch metrics.
20
+ module MetricVerifier
21
+ include CloudwatchClient
22
+ include AbstractCloudwatch
23
+
24
+ # @param alarm_name name of the Cloudwatch alarm
25
+ # @return True if Cloudwatch alarm exists for component
26
+ def component_alarm?(alarm_name)
27
+ alarm?(alarm_name)
28
+ end
29
+
30
+ # @param namespace Cloudwatch metric namespace
31
+ # @param metric_name Cloudwatch metric name
32
+ # @return True if Cloudwatch metric exists for component
33
+ def component_metric?(namespace, metric_name)
34
+ dimensions_name = 'FixedDimension'
35
+ dimensions_value = "#{@descriptor.stack_prefix_in}-#{@descriptor.ec2.component}"
36
+ response = metric?(namespace, metric_name, dimensions_name, dimensions_value)
37
+ return true if response.eql? true
38
+ end
39
+
40
+ # @param metric_name Cloudwatch EC2 metric name
41
+ # @return True if Cloudwatch EC2 metric exists for all component instances
42
+ def component_ec2_metric?(metric_name)
43
+ namespace = 'AWS/EC2'
44
+ dimensions_name = 'InstanceId'
45
+ instances_with_metric = []
46
+ instances = get_all_instances
47
+ instances_found = instances.count
48
+
49
+ instances.each do |instance|
50
+ next if instance.nil?
51
+
52
+ instance_id = instance.instance_id
53
+ dimensions_value = instance.instance_id
54
+
55
+ response = metric?(namespace, metric_name, dimensions_name, dimensions_value)
56
+
57
+ instances_with_metric.push(instance_id) if response.eql? true
58
+ end
59
+
60
+ instances_with_metric = instances_with_metric.count
61
+
62
+ return true unless instances_with_metric < instances_found
63
+ end
64
+
65
+ # @param log_stream_name Cloudwatch log stream name
66
+ # @param log_message name of the logfile of the log stream
67
+ # @return True if log message exists in Cloudwatch log stream for all component instances
68
+ def component_log_event?(log_stream_name, log_message)
69
+ instances_with_log_stream = []
70
+ loggroup_name = "#{@descriptor.stack_prefix_in}#{log_stream_name}"
71
+
72
+ instances = get_all_instances
73
+ instances_found = instances.count
74
+
75
+ instances.each do |instance|
76
+ next if instance.nil?
77
+
78
+ instance_id = instance.instance_id
79
+ log_stream_name = "#{@descriptor.ec2.component}/#{instance_id}"
80
+
81
+ response = log_event?(loggroup_name, log_stream_name, log_message)
82
+
83
+ instances_with_log_stream.push(instance_id) if response.eql? true
84
+ end
85
+ instances_with_log_stream = instances_with_log_stream.count
86
+
87
+ return true unless instances_with_log_stream < instances_found
88
+ end
89
+
90
+ # @param log_stream_name Cloudwatch log stream name
91
+ # @return True if Cloudwatch loggroup exists for component
92
+ def component_loggroup?(log_stream_name)
93
+ loggroup_name = "#{@descriptor.stack_prefix_in}#{log_stream_name}"
94
+
95
+ response = loggroup?(loggroup_name)
96
+
97
+ return true if response.eql? true
98
+ end
99
+
100
+ # @param log_stream_name Cloudwatch log stream name
101
+ # @return True if Cloudwatch log stream exists for all component instances
102
+ def component_log_stream?(log_stream_name)
103
+ instances_with_log_stream = []
104
+ loggroup_name = "#{@descriptor.stack_prefix_in}#{log_stream_name}"
105
+
106
+ instances = get_all_instances
107
+ instances_found = instances.count
108
+
109
+ instances.each do |instance|
110
+ next if instance.nil?
111
+
112
+ instance_id = instance.instance_id
113
+ log_stream_name = "#{@descriptor.ec2.component}/#{instance_id}"
114
+
115
+ response = log_stream?(loggroup_name, log_stream_name)
116
+
117
+ instances_with_log_stream.push(instance_id) if response.eql? true
118
+ end
119
+ instances_with_log_stream = instances_with_log_stream.count
120
+
121
+ return true unless instances_with_log_stream < instances_found
122
+ end
123
+
124
+ # @param alarm_name name of the Cloudwatch alarm
125
+ # @return True if Cloudwatch alarm exists
126
+ def alarm?(alarm_name)
127
+ response = get_alarm(alarm_name)
128
+
129
+ return true unless response.metric_alarms.empty?
130
+ end
131
+
132
+ # @param namespace Cloudwatch metric namespace
133
+ # @param metric_name Cloudwatch metric name
134
+ # @param dimensions_name Cloudwatch metric dimension name
135
+ # @param dimensions_value Cloudwatch metric dimension value
136
+ # @return True if Cloudwatch metric exists
137
+ def metric?(namespace, metric_name, dimensions_name, dimensions_value)
138
+ dimension_values = dimensions_value_filter_for_cloudwatch_metric(dimensions_name, dimensions_value)
139
+ dimension_filter = dimensions_filter_for_cloudwatch_metric(dimension_values)
140
+
141
+ response = get_metrics(namespace, metric_name, dimension_filter)
142
+
143
+ return true unless response.metrics.empty?
144
+ end
145
+
146
+ # @param loggroup_name Cloudwatch loggroup name
147
+ # @param log_stream_name Cloudwatch log stream name
148
+ # @return True if Cloudwatch log stream exists
149
+ def log_stream?(loggroup_name, log_stream_name)
150
+ response = loggroup?(loggroup_name)
151
+ return false unless response.eql? true
152
+
153
+ response = get_log_streams(loggroup_name, log_stream_name)
154
+
155
+ return true unless response.log_streams.empty?
156
+ end
157
+
158
+ # @param loggroup_name Cloudwatch loggroup name
159
+ # @param log_stream_name Cloudwatch log stream name
160
+ # @param log_message name of the logfile of the log stream
161
+ # @return True if Cloudwatch log event exists
162
+ def log_event?(loggroup_name, log_stream_name, log_message)
163
+ response = loggroup?(loggroup_name)
164
+ return false unless response.eql? true
165
+
166
+ response = log_stream?(loggroup_name, log_stream_name)
167
+ return false unless response.eql? true
168
+
169
+ response = get_log_event(loggroup_name, log_stream_name, log_message)
170
+ return true unless response.events.empty?
171
+ end
172
+
173
+ # @param loggroup Cloudwatch loggroup name
174
+ # @return True if Cloudwatch loggroup exists
175
+ def loggroup?(loggroup_name)
176
+ namespace = 'AWS/Logs'
177
+ metric_name = 'IncomingLogEvents'
178
+ dimensions_name = 'LogGroupName'
179
+ dimensions_value = loggroup_name
180
+
181
+ dimension_values = dimensions_value_filter_for_cloudwatch_metric(dimensions_name, dimensions_value)
182
+ dimension_filter = dimensions_filter_for_cloudwatch_metric(dimension_values)
183
+
184
+ response = get_metrics(namespace, metric_name, dimension_filter)
185
+
186
+ return true unless response.metrics.empty?
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright 2018 Shine Solutions
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module RubyAemAws
16
+ # Mixin for checking snapshots of a component via EC2 client
17
+ # Add this to a component to make it capable of determining its own snapshots.
18
+ module SnapshotVerifier
19
+ # @param snapshot_id AWS Snapshot ID
20
+ # @return true if snapshot exists, nil if no snapshot exists
21
+ def snapshot?(snapshot_id)
22
+ return true unless get_snapshot_by_id(snapshot_id).nil?
23
+ end
24
+
25
+ # @param snapshot_type AEM snapshot type
26
+ # @return true if snapshots exist, false is no snapshots exist
27
+ def snapshots?(snapshot_type)
28
+ has_snapshot = false
29
+ get_snapshots_by_type(snapshot_type).each do |s|
30
+ next if s.nil?
31
+
32
+ has_snapshot = true
33
+ end
34
+ has_snapshot
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,152 @@
1
+ # Copyright 2018 Shine Solutions
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'ruby_aem_aws/architecture/consolidated_stack'
16
+ require_relative 'ruby_aem_aws/architecture/full_set_stack'
17
+ require_relative 'ruby_aem_aws/architecture/stack_manager'
18
+
19
+ module RubyAemAws
20
+ # AemAws class represents the AWS stack for AEM.
21
+ class AemAws
22
+ # @param conf configuration hash of the following configuration values:
23
+ # - region: the AWS region (eg ap-southeast-2)
24
+ # - aws_access_key_id: the AWS access key
25
+ # - aws_secret_access_key: the AWS secret access key
26
+ # - aws_profile: AWS profile name
27
+ # @return new RubyAemAws::AemAws instance
28
+ def initialize(conf = {})
29
+ conf[:region] ||= Constants::REGION_DEFAULT
30
+ conf[:aws_access_key_id] ||= Constants::ACCESS_KEY_ID
31
+ conf[:aws_secret_access_key] ||= Constants::SECRET_ACCESS_KEY
32
+ conf[:aws_profile] ||= Constants::PROFILE
33
+
34
+ Aws.config.update(region: conf[:region])
35
+
36
+ credentials = Aws::Credentials.new(conf[:aws_access_key_id], conf[:aws_secret_access_key]) unless conf[:aws_access_key_id].nil?
37
+ credentials = Aws::SharedCredentials.new(profile_name: conf[:aws_profile]) unless conf[:aws_profile].nil?
38
+ credentials = Aws::InstanceProfileCredentials.new if conf[:aws_profile].nil? && conf[:aws_access_key_id].nil?
39
+ raise RubyAemAws::ArgumentError unless defined? credentials
40
+
41
+ Aws.config.update(credentials: credentials)
42
+
43
+ @aws = AwsCreator.create_aws
44
+ end
45
+
46
+ # Test connection to Amazon AWS
47
+ #
48
+ # @return One or more regions that are currently available.
49
+ def test_connection
50
+ result = []
51
+ ec2_client = @aws[:Ec2Client]
52
+ ec2_client.describe_regions.regions.each do |region|
53
+ result.push("Region #{region.region_name} (#{region.endpoint})")
54
+ end
55
+ !result.empty?
56
+ end
57
+
58
+ # Create a consolidated instance.
59
+ #
60
+ # @param stack_prefix AWS tag: StackPrefix
61
+ # @param aws_clients Array of AWS Clients and Resource connections:
62
+ # - CloudFormationClient: AWS Cloudformation Client.
63
+ # - CloudWatchClient: AWS Cloudwatch Client.
64
+ # - CloudWatchLogsClient: AWS Cloudwatch Logs Client.
65
+ # - Ec2Resource: AWS EC2 Resource connection.
66
+ # @return new RubyAemAws::ConsolidatedStack instance
67
+ def consolidated(stack_prefix)
68
+ aws_clients = {
69
+ CloudFormationClient: @aws[:CloudFormationClient],
70
+ CloudWatchClient: @aws[:CloudWatchClient],
71
+ CloudWatchLogsClient: @aws[:CloudWatchLogsClient],
72
+ Ec2Resource: @aws[:Ec2Resource]
73
+ }
74
+
75
+ RubyAemAws::ConsolidatedStack.new(stack_prefix, aws_clients)
76
+ end
77
+
78
+ # Create a full set instance.
79
+ #
80
+ # @param stack_prefix AWS tag: StackPrefix
81
+ # @param aws_clients Array of AWS Clients and Resource connections:
82
+ # - AutoScalingClient: AWS AutoScalingGroup Client.
83
+ # - CloudFormationClient: AWS Cloudformation Client.
84
+ # - CloudWatchClient: AWS Cloudwatch Client.
85
+ # - CloudWatchLogsClient: AWS Cloudwatch Logs Client.
86
+ # - Ec2Resource: AWS EC2 Resource connection.
87
+ # - ElbClient: AWS ElasticLoadBalancer Client.
88
+ # @return new RubyAemAws::FullSetStack instance
89
+ def full_set(stack_prefix)
90
+ aws_clients = {
91
+ AutoScalingClient: @aws[:AutoScalingClient],
92
+ CloudFormationClient: @aws[:CloudFormationClient],
93
+ CloudWatchClient: @aws[:CloudWatchClient],
94
+ CloudWatchLogsClient: @aws[:CloudWatchLogsClient],
95
+ Ec2Resource: @aws[:Ec2Resource],
96
+ ElbClient: @aws[:ElbClient]
97
+ }
98
+
99
+ RubyAemAws::FullSetStack.new(stack_prefix, aws_clients)
100
+ end
101
+
102
+ # Create Stack Manager resources
103
+ #
104
+ # @param stack_prefix AWS tag: StackPrefix
105
+ # @param aws_clients Array of AWS Clients and Resource connections:
106
+ # - CloudFormationClient: AWS Cloudformation Client.
107
+ # - CloudWatchClient: AWS Cloudwatch Client.
108
+ # - CloudWatchLogsClient: AWS Cloudwatch Logs Client.
109
+ # - DynamoDBClient: AWS DynamoDB Client.
110
+ # - S3Client: AWS S3 Client.
111
+ # - S3Resource: AWS S3 Resource connection.
112
+ # @return new RubyAemAws::StackManager instance
113
+ def stack_manager(stack_prefix)
114
+ aws_clients = {
115
+ CloudFormationClient: @aws[:CloudFormationClient],
116
+ CloudWatchClient: @aws[:CloudWatchClient],
117
+ CloudWatchLogsClient: @aws[:CloudWatchLogsClient],
118
+ DynamoDBClient: @aws[:DynamoDBClient],
119
+ S3Client: @aws[:S3Client],
120
+ S3Resource: @aws[:S3Resource]
121
+ }
122
+
123
+ RubyAemAws::StackManager.new(stack_prefix, aws_clients)
124
+ end
125
+ end
126
+
127
+ # Encapsulate AWS class creation for mocking.
128
+ class AwsCreator
129
+ def self.create_aws
130
+ {
131
+ Ec2Client: Aws::EC2::Client.new,
132
+ Ec2Resource: Aws::EC2::Resource.new,
133
+ ElbClient: Aws::ElasticLoadBalancing::Client.new(
134
+ retry_limit: 20
135
+ ),
136
+ AutoScalingClient: Aws::AutoScaling::Client.new(
137
+ retry_limit: 20
138
+ ),
139
+ CloudFormationClient: Aws::CloudFormation::Client.new,
140
+ CloudWatchClient: Aws::CloudWatch::Client.new(
141
+ retry_limit: 20
142
+ ),
143
+ CloudWatchLogsClient: Aws::CloudWatchLogs::Client.new(
144
+ retry_limit: 20
145
+ ),
146
+ DynamoDBClient: Aws::DynamoDB::Client.new,
147
+ S3Client: Aws::S3::Client.new,
148
+ S3Resource: Aws::S3::Resource.new
149
+ }
150
+ end
151
+ end
152
+ end