cloud-mu 1.9.0.pre.beta → 2.0.0.pre.alpha

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/Berksfile +16 -54
  3. data/Berksfile.lock +14 -62
  4. data/bin/mu-aws-setup +131 -108
  5. data/bin/mu-configure +311 -74
  6. data/bin/mu-gcp-setup +84 -62
  7. data/bin/mu-load-config.rb +46 -2
  8. data/bin/mu-self-update +11 -9
  9. data/bin/mu-upload-chef-artifacts +4 -4
  10. data/{mu.gemspec → cloud-mu.gemspec} +2 -2
  11. data/cookbooks/awscli/Berksfile +8 -0
  12. data/cookbooks/mu-activedirectory/Berksfile +11 -0
  13. data/cookbooks/mu-firewall/Berksfile +9 -0
  14. data/cookbooks/mu-firewall/metadata.rb +1 -1
  15. data/cookbooks/mu-glusterfs/Berksfile +10 -0
  16. data/cookbooks/mu-jenkins/Berksfile +14 -0
  17. data/cookbooks/mu-master/Berksfile +23 -0
  18. data/cookbooks/mu-master/attributes/default.rb +1 -1
  19. data/cookbooks/mu-master/metadata.rb +2 -2
  20. data/cookbooks/mu-master/recipes/default.rb +1 -1
  21. data/cookbooks/mu-master/recipes/init.rb +7 -3
  22. data/cookbooks/mu-master/recipes/ssl-certs.rb +1 -0
  23. data/cookbooks/mu-mongo/Berksfile +10 -0
  24. data/cookbooks/mu-openvpn/Berksfile +11 -0
  25. data/cookbooks/mu-php54/Berksfile +13 -0
  26. data/cookbooks/mu-splunk/Berksfile +10 -0
  27. data/cookbooks/mu-tools/Berksfile +21 -0
  28. data/cookbooks/mu-tools/files/default/Mu_CA.pem +15 -15
  29. data/cookbooks/mu-utility/Berksfile +9 -0
  30. data/cookbooks/mu-utility/metadata.rb +2 -1
  31. data/cookbooks/nagios/Berksfile +7 -4
  32. data/cookbooks/s3fs/Berksfile +9 -0
  33. data/environments/dev.json +6 -6
  34. data/environments/prod.json +6 -6
  35. data/modules/mu.rb +20 -42
  36. data/modules/mu/cleanup.rb +102 -100
  37. data/modules/mu/cloud.rb +90 -28
  38. data/modules/mu/clouds/aws.rb +449 -218
  39. data/modules/mu/clouds/aws/alarm.rb +29 -17
  40. data/modules/mu/clouds/aws/cache_cluster.rb +78 -64
  41. data/modules/mu/clouds/aws/collection.rb +25 -18
  42. data/modules/mu/clouds/aws/container_cluster.rb +73 -66
  43. data/modules/mu/clouds/aws/database.rb +124 -116
  44. data/modules/mu/clouds/aws/dnszone.rb +27 -20
  45. data/modules/mu/clouds/aws/firewall_rule.rb +30 -22
  46. data/modules/mu/clouds/aws/folder.rb +18 -3
  47. data/modules/mu/clouds/aws/function.rb +77 -23
  48. data/modules/mu/clouds/aws/group.rb +19 -12
  49. data/modules/mu/clouds/aws/habitat.rb +153 -0
  50. data/modules/mu/clouds/aws/loadbalancer.rb +59 -52
  51. data/modules/mu/clouds/aws/log.rb +30 -23
  52. data/modules/mu/clouds/aws/msg_queue.rb +29 -20
  53. data/modules/mu/clouds/aws/notifier.rb +222 -0
  54. data/modules/mu/clouds/aws/role.rb +178 -90
  55. data/modules/mu/clouds/aws/search_domain.rb +40 -24
  56. data/modules/mu/clouds/aws/server.rb +169 -137
  57. data/modules/mu/clouds/aws/server_pool.rb +60 -83
  58. data/modules/mu/clouds/aws/storage_pool.rb +59 -31
  59. data/modules/mu/clouds/aws/user.rb +36 -27
  60. data/modules/mu/clouds/aws/userdata/linux.erb +101 -93
  61. data/modules/mu/clouds/aws/vpc.rb +250 -189
  62. data/modules/mu/clouds/azure.rb +132 -0
  63. data/modules/mu/clouds/cloudformation.rb +65 -1
  64. data/modules/mu/clouds/cloudformation/alarm.rb +8 -0
  65. data/modules/mu/clouds/cloudformation/cache_cluster.rb +7 -0
  66. data/modules/mu/clouds/cloudformation/collection.rb +7 -0
  67. data/modules/mu/clouds/cloudformation/database.rb +7 -0
  68. data/modules/mu/clouds/cloudformation/dnszone.rb +7 -0
  69. data/modules/mu/clouds/cloudformation/firewall_rule.rb +9 -2
  70. data/modules/mu/clouds/cloudformation/loadbalancer.rb +7 -0
  71. data/modules/mu/clouds/cloudformation/log.rb +7 -0
  72. data/modules/mu/clouds/cloudformation/server.rb +7 -0
  73. data/modules/mu/clouds/cloudformation/server_pool.rb +7 -0
  74. data/modules/mu/clouds/cloudformation/vpc.rb +7 -0
  75. data/modules/mu/clouds/google.rb +214 -110
  76. data/modules/mu/clouds/google/container_cluster.rb +42 -24
  77. data/modules/mu/clouds/google/database.rb +15 -6
  78. data/modules/mu/clouds/google/firewall_rule.rb +17 -25
  79. data/modules/mu/clouds/google/group.rb +13 -5
  80. data/modules/mu/clouds/google/habitat.rb +105 -0
  81. data/modules/mu/clouds/google/loadbalancer.rb +28 -20
  82. data/modules/mu/clouds/google/server.rb +93 -354
  83. data/modules/mu/clouds/google/server_pool.rb +18 -10
  84. data/modules/mu/clouds/google/user.rb +22 -14
  85. data/modules/mu/clouds/google/vpc.rb +97 -69
  86. data/modules/mu/config.rb +133 -38
  87. data/modules/mu/config/alarm.rb +25 -0
  88. data/modules/mu/config/cache_cluster.rb +5 -3
  89. data/modules/mu/config/cache_cluster.yml +23 -0
  90. data/modules/mu/config/database.rb +25 -16
  91. data/modules/mu/config/database.yml +3 -3
  92. data/modules/mu/config/function.rb +1 -2
  93. data/modules/mu/config/{project.rb → habitat.rb} +10 -10
  94. data/modules/mu/config/notifier.rb +85 -0
  95. data/modules/mu/config/notifier.yml +9 -0
  96. data/modules/mu/config/role.rb +1 -1
  97. data/modules/mu/config/search_domain.yml +2 -2
  98. data/modules/mu/config/server.rb +13 -1
  99. data/modules/mu/config/server.yml +3 -3
  100. data/modules/mu/config/server_pool.rb +3 -1
  101. data/modules/mu/config/storage_pool.rb +3 -1
  102. data/modules/mu/config/storage_pool.yml +19 -0
  103. data/modules/mu/config/vpc.rb +70 -8
  104. data/modules/mu/groomers/chef.rb +2 -3
  105. data/modules/mu/kittens.rb +500 -122
  106. data/modules/mu/master.rb +5 -5
  107. data/modules/mu/mommacat.rb +151 -91
  108. data/modules/tests/super_complex_bok.yml +12 -0
  109. data/modules/tests/super_simple_bok.yml +12 -0
  110. data/spec/mu/clouds/azure_spec.rb +82 -0
  111. data/spec/spec_helper.rb +105 -0
  112. metadata +26 -5
  113. data/modules/mu/clouds/aws/notification.rb +0 -139
  114. data/modules/mu/config/notification.rb +0 -44
@@ -38,7 +38,7 @@ module MU
38
38
  @config["log_group_name"] = @mu_name
39
39
  @config["log_stream_name"] =
40
40
  if @config["enable_cloudtrail_logging"]
41
- "#{MU.account_number}_CloudTrail_#{@config["region"]}"
41
+ "#{MU::Cloud::AWS.credToAcct(@config['credentials'])}_CloudTrail_#{@config["region"]}"
42
42
  else
43
43
  @mu_name
44
44
  end
@@ -56,7 +56,7 @@ module MU
56
56
  end
57
57
 
58
58
  MU.log "Creating log group #{@mu_name}"
59
- MU::Cloud::AWS.cloudwatchlogs(@config["region"]).create_log_group(
59
+ MU::Cloud::AWS.cloudwatchlogs(region: @config["region"], credentials: @config["credentials"]).create_log_group(
60
60
  log_group_name: @config["log_group_name"],
61
61
  tags: tags
62
62
  )
@@ -76,19 +76,19 @@ module MU
76
76
  end
77
77
  end while resp.nil?
78
78
 
79
- MU::Cloud::AWS.cloudwatchlogs(@config["region"]).create_log_stream(
79
+ MU::Cloud::AWS.cloudwatchlogs(region: @config["region"], credentials: @config["credentials"]).create_log_stream(
80
80
  log_group_name: @config["log_group_name"],
81
81
  log_stream_name: @config["log_stream_name"]
82
82
  )
83
83
 
84
- MU::Cloud::AWS.cloudwatchlogs(@config["region"]).put_retention_policy(
84
+ MU::Cloud::AWS.cloudwatchlogs(region: @config["region"], credentials: @config["credentials"]).put_retention_policy(
85
85
  log_group_name: @config["log_group_name"],
86
86
  retention_in_days: @config["retention_period"]
87
87
  )
88
88
 
89
89
  if @config["filters"] && !@config["filters"].empty?
90
90
  @config["filters"].each{ |filter|
91
- MU::Cloud::AWS.cloudwatchlogs(@config["region"]).put_metric_filter(
91
+ MU::Cloud::AWS.cloudwatchlogs(region: @config["region"], credentials: @config["credentials"]).put_metric_filter(
92
92
  log_group_name: @config["log_group_name"],
93
93
  filter_name: filter["name"],
94
94
  filter_pattern: filter["search_pattern"],
@@ -102,8 +102,8 @@ module MU
102
102
  end
103
103
 
104
104
  if @config["enable_cloudtrail_logging"]
105
- trail_resp = MU::Cloud::AWS.cloudtrail(@config["region"]).describe_trails.trail_list.first
106
- raise MuError, "Can't find a cloudtrail in #{MU.account_number}/#{@config["region"]}. Please create cloudtrail before enabling logging on it" unless trail_resp
105
+ trail_resp = MU::Cloud::AWS.cloudtrail(region: @config["region"], credentials: @config["credentials"]).describe_trails.trail_list.first
106
+ raise MuError, "Can't find a cloudtrail in #{MU::Cloud::AWS.credToAcct(@config['credentials'])}/#{@config["region"]}. Please create cloudtrail before enabling logging on it" unless trail_resp
107
107
 
108
108
  iam_policy = '{
109
109
  "Version": "2012-10-17",
@@ -116,7 +116,7 @@ module MU
116
116
  "logs:PutLogEventsBatch",
117
117
  "logs:PutLogEvents"
118
118
  ],
119
- "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+':logs:'+@config["region"]+':'+MU.account_number+':log-group:'+@config["log_group_name"]+':log-stream:'+@config["log_stream_name"]+'*"
119
+ "Resource": "arn:'+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+':logs:'+@config["region"]+':'+MU::Cloud::AWS.credToAcct(@config['credentials'])+':log-group:'+@config["log_group_name"]+':log-stream:'+@config["log_stream_name"]+'*"
120
120
  }
121
121
  ]
122
122
  }'
@@ -141,12 +141,12 @@ module MU
141
141
 
142
142
  iam_role_name = "#{@mu_name}-CloudTrail"
143
143
  MU.log "Creating IAM role #{iam_role_name}"
144
- iam_resp = MU::Cloud::AWS.iam(@config["region"]).create_role(
144
+ iam_resp = MU::Cloud::AWS.iam.create_role(
145
145
  role_name: iam_role_name,
146
146
  assume_role_policy_document: iam_assume_role_policy
147
147
  )
148
148
 
149
- MU::Cloud::AWS.iam(@config["region"]).put_role_policy(
149
+ MU::Cloud::AWS.iam.put_role_policy(
150
150
  role_name: iam_role_name,
151
151
  policy_name: "CloudTrail_CloudWatchLogs",
152
152
  policy_document: iam_policy
@@ -156,7 +156,7 @@ module MU
156
156
 
157
157
  retries = 0
158
158
  begin
159
- MU::Cloud::AWS.cloudtrail(@config["region"]).update_trail(
159
+ MU::Cloud::AWS.cloudtrail(region: @config["region"], credentials: @config["credentials"]).update_trail(
160
160
  name: trail_resp.name,
161
161
  cloud_watch_logs_log_group_arn: log_group_resp.arn,
162
162
  cloud_watch_logs_role_arn: iam_resp.role.arn
@@ -183,7 +183,7 @@ module MU
183
183
  prettyname = service.sub(/\..*/, "").capitalize
184
184
  doc = '{ "Version": "2012-10-17", "Statement": [ { "Sid": "'+prettyname+'LogsToCloudWatchLogs", "Effect": "Allow", "Principal": { "Service": [ "'+service+'" ] }, "Action": [ "logs:PutLogEvents", "logs:PutLogEventsBatch", "logs:CreateLogStream" ], "Resource": "'+log_arn+'" } ] }'
185
185
 
186
- MU::Cloud::AWS.cloudwatchlogs(region).put_resource_policy(
186
+ MU::Cloud::AWS.cloudwatchlogs(region: region).put_resource_policy(
187
187
  policy_name: "Allow"+prettyname,
188
188
  policy_document: doc
189
189
  )
@@ -209,15 +209,22 @@ module MU
209
209
  }
210
210
  end
211
211
 
212
+ # Does this resource type exist as a global (cloud-wide) artifact, or
213
+ # is it localized to a region/zone?
214
+ # @return [Boolean]
215
+ def self.isGlobal?
216
+ false
217
+ end
218
+
212
219
  # Remove all logs associated with the currently loaded deployment.
213
220
  # @param noop [Boolean]: If true, will only print what would be done
214
221
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
215
222
  # @param region [String]: The cloud provider region
216
223
  # @return [void]
217
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, flags: {})
224
+ def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
218
225
  log_groups =
219
226
  begin
220
- MU::Cloud::AWS.cloudwatchlogs(region).describe_log_groups.log_groups
227
+ MU::Cloud::AWS.cloudwatchlogs(credentials: credentials, region: region).describe_log_groups.log_groups
221
228
  # TO DO: Why is it returning UnknownOperationException instead of valid error?
222
229
  rescue Aws::CloudWatchLogs::Errors::UnknownOperationException => e
223
230
  MU.log e.inspect
@@ -227,10 +234,10 @@ module MU
227
234
  if !log_groups.empty?
228
235
  log_groups.each{ |lg|
229
236
  if lg.log_group_name.match(MU.deploy_id)
230
- log_streams = MU::Cloud::AWS.cloudwatchlogs(region).describe_log_streams(log_group_name: lg.log_group_name).log_streams
237
+ log_streams = MU::Cloud::AWS.cloudwatchlogs(credentials: credentials, region: region).describe_log_streams(log_group_name: lg.log_group_name).log_streams
231
238
  if !log_streams.empty?
232
239
  log_streams.each{ |ls|
233
- MU::Cloud::AWS.cloudwatchlogs(region).delete_log_stream(
240
+ MU::Cloud::AWS.cloudwatchlogs(credentials: credentials, region: region).delete_log_stream(
234
241
  log_group_name: lg.log_group_name,
235
242
  log_stream_name: ls.log_stream_name
236
243
  ) unless noop
@@ -239,7 +246,7 @@ module MU
239
246
  }
240
247
  end
241
248
 
242
- MU::Cloud::AWS.cloudwatchlogs(region).delete_log_group(
249
+ MU::Cloud::AWS.cloudwatchlogs(credentials: credentials, region: region).delete_log_group(
243
250
  log_group_name: lg.log_group_name
244
251
  ) unless noop
245
252
  MU.log "Deleted log group #{lg.log_group_name}"
@@ -248,7 +255,7 @@ module MU
248
255
  end
249
256
 
250
257
  unless noop
251
- MU::Cloud::AWS.iam.list_roles.roles.each{ |role|
258
+ MU::Cloud::AWS.iam(credentials: credentials).list_roles.roles.each{ |role|
252
259
  match_string = "#{MU.deploy_id}.*CloudTrail"
253
260
  # Maybe we should have a more generic way to delete IAM profiles and policies. The call itself should be moved from MU::Cloud::AWS::Server.
254
261
  # MU::Cloud::AWS::Server.removeIAMProfile(role.role_name) if role.role_name.match(match_string)
@@ -261,13 +268,13 @@ module MU
261
268
  # @param region [String]: The cloud provider region.
262
269
  # @param flags [Hash]: Optional flags
263
270
  # @return [OpenStruct]: The cloud provider's complete descriptions of matching log group.
264
- def self.find(cloud_id: nil, region: MU.curRegion, flags: {})
271
+ def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
265
272
  found = nil
266
273
  if !cloud_id.nil? and !cloud_id.match(/^arn:/i)
267
274
  found ||= {}
268
- found[cloud_id] = MU::Cloud::AWS::Log.getLogGroupByName(cloud_id, region: region)
275
+ found[cloud_id] = MU::Cloud::AWS::Log.getLogGroupByName(cloud_id, region: region, credentials: nil)
269
276
  else
270
- resp = MU::Cloud::AWS.cloudwatchlogs(region).describe_log_groups.log_groups.each { |group|
277
+ resp = MU::Cloud::AWS.cloudwatchlogs(region: region, credentials: credentials).describe_log_groups.log_groups.each { |group|
271
278
  if group.arn == cloud_id or group.arn.sub(/:\*$/, "") == cloud_id
272
279
  found ||= {}
273
280
  found[group.log_group_name] = group
@@ -354,8 +361,8 @@ module MU
354
361
  # @param name [String]: The cloud provider's identifier for this log group.
355
362
  # @param region [String]: The cloud provider region
356
363
  # @return [OpenStruct]
357
- def self.getLogGroupByName(name, region: MU.curRegion)
358
- MU::Cloud::AWS.cloudwatchlogs(region).describe_log_groups(log_group_name_prefix: name).log_groups.first
364
+ def self.getLogGroupByName(name, region: MU.curRegion, credentials: nil)
365
+ MU::Cloud::AWS.cloudwatchlogs(region: region, credentials: credentials).describe_log_groups(log_group_name_prefix: name).log_groups.first
359
366
  end
360
367
  end
361
368
  end
@@ -48,7 +48,7 @@ module MU
48
48
  namestr += ".fifo" if attrs['FifoQueue']
49
49
 
50
50
  MU.log "Creating SQS queue #{namestr}", details: attrs
51
- resp = MU::Cloud::AWS.sqs(@config['region']).create_queue(
51
+ resp = MU::Cloud::AWS.sqs(region: @config['region'], credentials: @config['credentials']).create_queue(
52
52
  queue_name: namestr,
53
53
  attributes: attrs
54
54
  )
@@ -75,7 +75,7 @@ module MU
75
75
  }
76
76
  if changed
77
77
  MU.log "Updating SQS queue #{@mu_name}", MU::NOTICE, details: new_attrs
78
- resp = MU::Cloud::AWS.sqs(@config['region']).set_queue_attributes(
78
+ resp = MU::Cloud::AWS.sqs(region: @config['region'], credentials: @config['credentials']).set_queue_attributes(
79
79
  queue_url: @cloud_id,
80
80
  attributes: new_attrs
81
81
  )
@@ -86,7 +86,7 @@ module MU
86
86
  # Canonical Amazon Resource Number for this resource
87
87
  # @return [String]
88
88
  def arn
89
- "arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":sqs:"+@config['region']+":"+MU.account_number+":"+@cloud_id
89
+ "arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":sqs:"+@config['region']+":"+MU::Cloud::AWS.credToAcct(@config['credentials'])+":"+@cloud_id
90
90
  end
91
91
 
92
92
  # Retrieve the AWS descriptor for this SQS queue. AWS doesn't exactly
@@ -94,7 +94,7 @@ module MU
94
94
  # @return [Hash]: AWS doesn't return anything but the SQS URL, so supplement with attributes
95
95
  def cloud_desc
96
96
  if !@cloud_id
97
- resp = MU::Cloud::AWS.sqs(@config['region']).list_queues(
97
+ resp = MU::Cloud::AWS.sqs(region: @config['region'], credentials: @config['credentials']).list_queues(
98
98
  queue_name_prefix: @mu_name
99
99
  )
100
100
  return nil if !resp or !resp.queue_urls
@@ -109,7 +109,8 @@ module MU
109
109
  return nil if !@cloud_id
110
110
  MU::Cloud::AWS::MsgQueue.find(
111
111
  cloud_id: @cloud_id.dup,
112
- region: @config['region']
112
+ region: @config['region'],
113
+ credentials: @config['credentials']
113
114
  )
114
115
  end
115
116
 
@@ -119,18 +120,26 @@ module MU
119
120
  cloud_desc
120
121
  deploy_struct = MU::Cloud::AWS::MsgQueue.find(
121
122
  cloud_id: @cloud_id,
122
- region: @config['region']
123
+ region: @config['region'],
124
+ credentials: @config['credentials']
123
125
  )
124
126
  return deploy_struct
125
127
  end
126
128
 
129
+ # Does this resource type exist as a global (cloud-wide) artifact, or
130
+ # is it localized to a region/zone?
131
+ # @return [Boolean]
132
+ def self.isGlobal?
133
+ false
134
+ end
135
+
127
136
  # Remove all msg_queues associated with the currently loaded deployment.
128
137
  # @param noop [Boolean]: If true, will only print what would be done
129
138
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
130
139
  # @param region [String]: The cloud provider region
131
140
  # @return [void]
132
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, flags: {})
133
- resp = MU::Cloud::AWS.sqs(region).list_queues(
141
+ def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
142
+ resp = MU::Cloud::AWS.sqs(credentials: credentials, region: region).list_queues(
134
143
  queue_name_prefix: MU.deploy_id
135
144
  )
136
145
  if resp and resp.queue_urls
@@ -139,7 +148,7 @@ module MU
139
148
  threads << Thread.new {
140
149
  MU.log "Deleting SQS queue #{url}"
141
150
  if !noop
142
- MU::Cloud::AWS.sqs(region).delete_queue(
151
+ MU::Cloud::AWS.sqs(credentials: credentials, region: region).delete_queue(
143
152
  queue_url: url
144
153
  )
145
154
  sleep 60 # per API docs, this is how long it takes to really delete
@@ -157,14 +166,14 @@ module MU
157
166
  # @param region [String]: The cloud provider region.
158
167
  # @param flags [Hash]: Optional flags
159
168
  # @return [Hash]: AWS doesn't return anything but the SQS URL, so supplement with attributes
160
- def self.find(cloud_id: nil, region: MU.curRegion, flags: {})
169
+ def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
161
170
  flags['account'] ||= MU.account_number
162
171
  return nil if !cloud_id
163
172
 
164
173
  # If it's a URL, make sure it's good
165
174
  begin
166
175
  if cloud_id.match(/^https?:/i)
167
- resp = MU::Cloud::AWS.sqs(region).get_queue_attributes(
176
+ resp = MU::Cloud::AWS.sqs(region: region, credentials: credentials).get_queue_attributes(
168
177
  queue_url: cloud_id,
169
178
  attribute_names: ["All"]
170
179
  )
@@ -175,7 +184,7 @@ module MU
175
184
  end
176
185
  else
177
186
  # If it's a plain queue name, resolve it to a URL
178
- resp = MU::Cloud::AWS.sqs(region).get_queue_url(
187
+ resp = MU::Cloud::AWS.sqs(region: region, credentials: credentials).get_queue_url(
179
188
  queue_name: cloud_id,
180
189
  queue_owner_aws_account_id: flags['account']
181
190
  )
@@ -186,7 +195,7 @@ module MU
186
195
 
187
196
  # Go fetch its attributes
188
197
  if cloud_id
189
- resp = MU::Cloud::AWS.sqs(region).get_queue_attributes(
198
+ resp = MU::Cloud::AWS.sqs(region: region, credentials: credentials).get_queue_attributes(
190
199
  queue_url: cloud_id,
191
200
  attribute_names: ["All"]
192
201
  )
@@ -372,7 +381,7 @@ MU.log "RETURNING FROM FIND ON #{cloud_id}", MU::WARN, details: caller
372
381
  ok = false
373
382
  end
374
383
  begin
375
- MU::Cloud::AWS.kms(queue['region']).describe_key(key_id: queue['kms']['key_id'])
384
+ MU::Cloud::AWS.kms(region: queue['region']).describe_key(key_id: queue['kms']['key_id'])
376
385
  rescue Aws::KMS::Errors::NotFoundException => e
377
386
  MU.log "KMS key '#{queue['kms']['key_id']}' specified in Queue '#{queue['name']}' was not found.", MU::ERR, details: "Key IDs are of the form bf64a093-2c3d-46fa-0d4f-8232fa7ed53. Keys can be created at https://console.aws.amazon.com/iam/home#/encryptionKeys/#{queue['region']}"
378
387
  ok = false
@@ -402,18 +411,18 @@ MU.log "RETURNING FROM FIND ON #{cloud_id}", MU::WARN, details: caller
402
411
  }
403
412
 
404
413
  if @config['failqueue']
405
- sibling = @deploy.findLitterMate(type: "msg_queue", name: config['failqueue']['name'])
406
- id = config['failqueue']['name']
414
+ sibling = @deploy.findLitterMate(type: "msg_queue", name: @config['failqueue']['name'])
415
+ id = @config['failqueue']['name']
407
416
  if sibling # resolve sibling queues to something useful
408
417
  id = sibling.cloud_id
409
418
  end
410
- desc = MU::Cloud::AWS::MsgQueue.find(cloud_id: id)
419
+ desc = MU::Cloud::AWS::MsgQueue.find(cloud_id: id, credentials: @config['credentials'])
411
420
  if !desc
412
- raise MuError, "Failed to get cloud descriptor for SQS queue #{config['failqueue']['name']}"
421
+ raise MuError, "Failed to get cloud descriptor for SQS queue #{@config['failqueue']['name']}"
413
422
  end
414
423
  rdr_pol = {
415
424
  "deadLetterTargetArn" => desc["QueueArn"],
416
- "maxReceiveCount" => config['failqueue']['retries_before_fail']
425
+ "maxReceiveCount" => @config['failqueue']['retries_before_fail']
417
426
  }
418
427
  attrs["RedrivePolicy"] = JSON.generate(rdr_pol)
419
428
  end
@@ -465,7 +474,7 @@ MU.log "RETURNING FROM FIND ON #{cloud_id}", MU::WARN, details: caller
465
474
  end
466
475
 
467
476
  begin
468
- MU::Cloud::AWS.sqs(@config['region']).tag_queue(
477
+ MU::Cloud::AWS.sqs(region: @config['region'], credentials: @config['credentials']).tag_queue(
469
478
  queue_url: url,
470
479
  tags: tags
471
480
  )
@@ -0,0 +1,222 @@
1
+ # Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
2
+ #
3
+ # Licensed under the BSD-3 license (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the root of the project or at
6
+ #
7
+ # http://egt-labs.com/mu/LICENSE.html
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 MU
16
+ class Cloud
17
+ class AWS
18
+ # Support for AWS SNS
19
+ class Notifier < MU::Cloud::Notifier
20
+ @deploy = nil
21
+ @config = nil
22
+
23
+ attr_reader :mu_name
24
+ attr_reader :config
25
+ attr_reader :cloud_id
26
+
27
+ # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
28
+ # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::logs}
29
+ def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
30
+ @deploy = mommacat
31
+ @config = MU::Config.manxify(kitten_cfg)
32
+ @cloud_id ||= cloud_id
33
+ @mu_name ||= @deploy.getResourceName(@config["name"])
34
+ end
35
+
36
+ # Called automatically by {MU::Deploy#createResources}
37
+ def create
38
+ MU::Cloud::AWS.sns(region: @config['region'], credentials: @config['credentials']).create_topic(name: @mu_name)
39
+ @cloud_id = @mu_name
40
+ MU.log "Created SNS topic #{@mu_name}"
41
+ end
42
+
43
+ # Called automatically by {MU::Deploy#createResources}
44
+ def groom
45
+ if @config['subscriptions']
46
+ @config['subscriptions'].each { |sub|
47
+ MU::Cloud::AWS::Notifier.subscribe(
48
+ arn: arn,
49
+ endpoint: sub['endpoint'],
50
+ region: @config['region'],
51
+ credentials: @config['credentials'],
52
+ protocol: sub['type']
53
+ )
54
+ }
55
+ end
56
+ end
57
+
58
+ # Does this resource type exist as a global (cloud-wide) artifact, or
59
+ # is it localized to a region/zone?
60
+ # @return [Boolean]
61
+ def self.isGlobal?
62
+ false
63
+ end
64
+
65
+ # Remove all notifiers associated with the currently loaded deployment.
66
+ # @param noop [Boolean]: If true, will only print what would be done
67
+ # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
68
+ # @param region [String]: The cloud provider region
69
+ # @return [void]
70
+ def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
71
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).list_topics.topics.each { |topic|
72
+ if topic.topic_arn.match(MU.deploy_id)
73
+ # We don't have a way to tag our SNS topics, so we will delete any topic that has the MU-ID in its ARN.
74
+ # This may fail to find notifier groups in some cases (eg. cache_cluster) so we might want to delete from each API as well.
75
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).delete_topic(topic_arn: topic.topic_arn)
76
+ MU.log "Deleted SNS topic: #{topic.topic_arn}"
77
+ end
78
+ }
79
+ end
80
+
81
+ # Canonical Amazon Resource Number for this resource
82
+ # @return [String]
83
+ def arn
84
+ "arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":sns:"+@config['region']+":"+MU::Cloud::AWS.credToAcct(@config['credentials'])+":"+@cloud_id
85
+ end
86
+
87
+ # Return the metadata for this user cofiguration
88
+ # @return [Hash]
89
+ def notify
90
+ desc = MU::Cloud::AWS.sns(region: @config["region"], credentials: @config["credentials"]).get_topic_attributes(topic_arn: arn).attributes
91
+ MU.structToHash(desc)
92
+ end
93
+
94
+ # Locate an existing notifier.
95
+ # @param cloud_id [String]: The cloud provider's identifier for this resource.
96
+ # @param region [String]: The cloud provider region.
97
+ # @param flags [Hash]: Optional flags
98
+ # @return [OpenStruct]: The cloud provider's complete descriptions of matching notifier.
99
+ def self.find(cloud_id: nil, region: MU.curRegion, credentials: nil, flags: {})
100
+ found = {}
101
+ if cloud_id
102
+ arn = "arn:"+(MU::Cloud::AWS.isGovCloud?(region) ? "aws-us-gov" : "aws")+":sns:"+region+":"+MU::Cloud::AWS.credToAcct(credentials)+":"+cloud_id
103
+ desc = MU::Cloud::AWS.sns(region: region, credentials: credentials).get_topic_attributes(topic_arn: arn).attributes
104
+ found[cloud_id] = desc if desc
105
+ end
106
+ found
107
+ end
108
+
109
+ # Cloud-specific configuration properties.
110
+ # @param config [MU::Config]: The calling MU::Config object
111
+ # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
112
+ def self.schema(config)
113
+ toplevel_required = []
114
+ schema = {
115
+ "subscriptions" => {
116
+ "type" => "array",
117
+ "items" => {
118
+ "type" => "object",
119
+ "required" => ["endpoint"],
120
+ "properties" => {
121
+ "type" => {
122
+ "type" => "string",
123
+ "description" => "",
124
+ "enum" => ["http", "https", "email", "email-json", "sms", "sqs", "application", "lambda"]
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ }
131
+ [toplevel_required, schema]
132
+ end
133
+
134
+ # Cloud-specific pre-processing of {MU::Config::BasketofKittens::notifier}, bare and unvalidated.
135
+
136
+ # @param notifier [Hash]: The resource to process and validate
137
+ # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
138
+ # @return [Boolean]: True if validation succeeded, False otherwise
139
+ def self.validateConfig(notifier, configurator)
140
+ ok = true
141
+
142
+ if notifier['subscriptions']
143
+ notifier['subscriptions'].each { |sub|
144
+ if !sub["type"]
145
+ if sub["endpoint"].match(/^http:/i)
146
+ sub["type"] = "http"
147
+ elsif sub["endpoint"].match(/^https:/i)
148
+ sub["type"] = "https"
149
+ elsif sub["endpoint"].match(/^sqs:/i)
150
+ sub["type"] = "sqs"
151
+ elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
152
+ sub["type"] = "sms"
153
+ elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
154
+ sub["type"] = "email"
155
+ else
156
+ MU.log "Notifier #{notifier['name']} subscription #{sub['endpoint']} did not specify a type, and I'm unable to guess one", MU::ERR
157
+ ok = false
158
+ end
159
+ end
160
+ }
161
+ end
162
+
163
+ ok
164
+ end
165
+
166
+
167
+ # Subscribe to a notifier group. This can either be an email address, SQS queue, application endpoint, etc...
168
+ # Will create the subscription only if it doesn't already exist.
169
+ # @param arn [String]: The cloud provider's identifier of the notifier group.
170
+ # @param protocol [String]: The type of the subscription (eg. email,https, etc..).
171
+ # @param endpoint [String]: The endpoint of the subscription. This will depend on the 'protocol' (as an example if protocol is email, endpoint will be the email address) ..
172
+ # @param region [String]: The cloud provider region.
173
+ def self.subscribe(arn: nil, protocol: nil, endpoint: nil, region: MU.curRegion, credentials: nil)
174
+ retries = 0
175
+ begin
176
+ resp = MU::Cloud::AWS.sns(region: region, credentials: credentials).list_subscriptions_by_topic(topic_arn: arn).subscriptions
177
+ rescue Aws::SNS::Errors::NotFound
178
+ if retries < 5
179
+ MU.log "Couldn't find topic #{arn}, retrying several times in case of a lagging resource"
180
+ retries += 1
181
+ sleep 30
182
+ retry
183
+ else
184
+ raise MuError, "Couldn't find topic #{arn}, giving up"
185
+ end
186
+ end
187
+
188
+ already_subscribed = false
189
+ if resp && !resp.empty?
190
+ resp.each { |subscription|
191
+ already_subscribed = true if subscription.protocol == protocol && subscription.endpoint == endpoint
192
+ }
193
+ end
194
+
195
+ unless already_subscribed
196
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).subscribe(topic_arn: arn, protocol: protocol, endpoint: endpoint)
197
+ MU.log "Subscribed #{endpoint} to SNS topic #{arn}"
198
+ end
199
+ end
200
+
201
+ # Test if a notifier group exists
202
+ # Create a new notifier group. Will check if the group exists before creating it.
203
+ # @param topic_name [String]: The cloud provider's name for the notifier group.
204
+ # @param region [String]: The cloud provider region.
205
+ # @param account_number [String]: The cloud provider account number.
206
+ # @return [string]: The cloud provider's identifier.
207
+ def self.topicExist(topic_name, region: MU.curRegion, account_number: MU.account_number, credentials: nil)
208
+ arn = "arn:#{MU::Cloud::AWS.isGovCloud?(region) ? "aws-us-gov" : "aws"}:sns:#{region}:#{account_number}:#{topic_name}"
209
+ match = nil
210
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).list_topics.topics.each { |topic|
211
+ if topic.topic_arn == arn
212
+ match = topic.topic_arn
213
+ break
214
+ end
215
+ }
216
+ return match
217
+ end
218
+
219
+ end
220
+ end
221
+ end
222
+ end