ruby_aem_aws_odysseas 1.4.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 (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