cloud-mu 3.2.0 → 3.5.0
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 +4 -4
- data/Dockerfile +1 -1
- data/ansible/roles/mu-nat/tasks/main.yml +3 -0
- data/bin/mu-adopt +12 -1
- data/bin/mu-aws-setup +41 -7
- data/bin/mu-azure-setup +34 -0
- data/bin/mu-configure +214 -119
- data/bin/mu-gcp-setup +37 -2
- data/bin/mu-load-config.rb +2 -1
- data/bin/mu-node-manage +3 -0
- data/bin/mu-refresh-ssl +67 -0
- data/bin/mu-run-tests +28 -6
- data/bin/mu-self-update +30 -10
- data/bin/mu-upload-chef-artifacts +30 -26
- data/cloud-mu.gemspec +10 -8
- data/cookbooks/mu-master/attributes/default.rb +5 -1
- data/cookbooks/mu-master/metadata.rb +2 -2
- data/cookbooks/mu-master/recipes/default.rb +81 -26
- data/cookbooks/mu-master/recipes/init.rb +197 -62
- data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
- data/cookbooks/mu-master/recipes/vault.rb +78 -77
- data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
- data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
- data/cookbooks/mu-tools/attributes/default.rb +12 -0
- data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
- data/cookbooks/mu-tools/libraries/helper.rb +98 -4
- data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
- data/cookbooks/mu-tools/recipes/apply_security.rb +31 -9
- data/cookbooks/mu-tools/recipes/aws_api.rb +8 -2
- data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
- data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
- data/cookbooks/mu-tools/recipes/google_api.rb +7 -0
- data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
- data/cookbooks/mu-tools/resources/disk.rb +113 -42
- data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
- data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
- data/extras/Gemfile.lock.bootstrap +394 -0
- data/extras/bucketstubs/error.html +0 -0
- data/extras/bucketstubs/index.html +0 -0
- data/extras/clean-stock-amis +11 -3
- data/extras/generate-stock-images +6 -3
- data/extras/git_rpm/build.sh +20 -0
- data/extras/git_rpm/mugit.spec +53 -0
- data/extras/image-generators/AWS/centos7.yaml +19 -16
- data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
- data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
- data/extras/image-generators/VMWare/centos8.yaml +15 -0
- data/extras/openssl_rpm/build.sh +19 -0
- data/extras/openssl_rpm/mussl.spec +46 -0
- data/extras/python_rpm/muthon.spec +14 -4
- data/extras/ruby_rpm/muby.spec +9 -5
- data/extras/sqlite_rpm/build.sh +19 -0
- data/extras/sqlite_rpm/muqlite.spec +47 -0
- data/install/installer +7 -5
- data/modules/mommacat.ru +2 -2
- data/modules/mu.rb +14 -7
- data/modules/mu/adoption.rb +5 -5
- data/modules/mu/cleanup.rb +47 -25
- data/modules/mu/cloud.rb +29 -1
- data/modules/mu/cloud/dnszone.rb +0 -2
- data/modules/mu/cloud/machine_images.rb +1 -1
- data/modules/mu/cloud/providers.rb +6 -1
- data/modules/mu/cloud/resource_base.rb +16 -7
- data/modules/mu/cloud/ssh_sessions.rb +5 -1
- data/modules/mu/cloud/wrappers.rb +20 -7
- data/modules/mu/config.rb +28 -12
- data/modules/mu/config/bucket.rb +31 -2
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/cdn.rb +100 -0
- data/modules/mu/config/container_cluster.rb +1 -1
- data/modules/mu/config/database.rb +3 -3
- data/modules/mu/config/dnszone.rb +4 -3
- data/modules/mu/config/endpoint.rb +1 -0
- data/modules/mu/config/firewall_rule.rb +1 -1
- data/modules/mu/config/function.rb +16 -7
- data/modules/mu/config/job.rb +89 -0
- data/modules/mu/config/notifier.rb +7 -18
- data/modules/mu/config/ref.rb +55 -9
- data/modules/mu/config/schema_helpers.rb +12 -3
- data/modules/mu/config/server.rb +11 -5
- data/modules/mu/config/server_pool.rb +2 -2
- data/modules/mu/config/vpc.rb +11 -10
- data/modules/mu/defaults/AWS.yaml +106 -106
- data/modules/mu/deploy.rb +40 -14
- data/modules/mu/groomers/chef.rb +2 -2
- data/modules/mu/master.rb +70 -3
- data/modules/mu/mommacat.rb +28 -9
- data/modules/mu/mommacat/daemon.rb +13 -7
- data/modules/mu/mommacat/naming.rb +2 -2
- data/modules/mu/mommacat/search.rb +16 -5
- data/modules/mu/mommacat/storage.rb +67 -32
- data/modules/mu/providers/aws.rb +298 -85
- data/modules/mu/providers/aws/alarm.rb +5 -5
- data/modules/mu/providers/aws/bucket.rb +284 -50
- data/modules/mu/providers/aws/cache_cluster.rb +26 -26
- data/modules/mu/providers/aws/cdn.rb +782 -0
- data/modules/mu/providers/aws/collection.rb +16 -16
- data/modules/mu/providers/aws/container_cluster.rb +84 -64
- data/modules/mu/providers/aws/database.rb +59 -55
- data/modules/mu/providers/aws/dnszone.rb +29 -12
- data/modules/mu/providers/aws/endpoint.rb +535 -50
- data/modules/mu/providers/aws/firewall_rule.rb +32 -26
- data/modules/mu/providers/aws/folder.rb +1 -1
- data/modules/mu/providers/aws/function.rb +300 -134
- data/modules/mu/providers/aws/group.rb +16 -14
- data/modules/mu/providers/aws/habitat.rb +4 -4
- data/modules/mu/providers/aws/job.rb +469 -0
- data/modules/mu/providers/aws/loadbalancer.rb +67 -45
- data/modules/mu/providers/aws/log.rb +17 -17
- data/modules/mu/providers/aws/msg_queue.rb +22 -13
- data/modules/mu/providers/aws/nosqldb.rb +99 -8
- data/modules/mu/providers/aws/notifier.rb +137 -65
- data/modules/mu/providers/aws/role.rb +119 -83
- data/modules/mu/providers/aws/search_domain.rb +166 -30
- data/modules/mu/providers/aws/server.rb +209 -118
- data/modules/mu/providers/aws/server_pool.rb +95 -130
- data/modules/mu/providers/aws/storage_pool.rb +19 -11
- data/modules/mu/providers/aws/user.rb +5 -5
- data/modules/mu/providers/aws/userdata/linux.erb +5 -4
- data/modules/mu/providers/aws/vpc.rb +109 -54
- data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
- data/modules/mu/providers/azure.rb +78 -12
- data/modules/mu/providers/azure/server.rb +20 -4
- data/modules/mu/providers/cloudformation/server.rb +1 -1
- data/modules/mu/providers/google.rb +21 -5
- data/modules/mu/providers/google/bucket.rb +1 -1
- data/modules/mu/providers/google/container_cluster.rb +1 -1
- data/modules/mu/providers/google/database.rb +1 -1
- data/modules/mu/providers/google/firewall_rule.rb +1 -1
- data/modules/mu/providers/google/folder.rb +7 -3
- data/modules/mu/providers/google/function.rb +66 -31
- data/modules/mu/providers/google/group.rb +1 -1
- data/modules/mu/providers/google/habitat.rb +1 -1
- data/modules/mu/providers/google/loadbalancer.rb +1 -1
- data/modules/mu/providers/google/role.rb +6 -3
- data/modules/mu/providers/google/server.rb +1 -1
- data/modules/mu/providers/google/server_pool.rb +1 -1
- data/modules/mu/providers/google/user.rb +1 -1
- data/modules/mu/providers/google/vpc.rb +28 -3
- data/modules/tests/aws-jobs-functions.yaml +46 -0
- data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
- data/modules/tests/centos6.yaml +4 -0
- data/modules/tests/centos7.yaml +4 -0
- data/modules/tests/ecs.yaml +2 -2
- data/modules/tests/eks.yaml +1 -1
- data/modules/tests/functions/node-function/lambda_function.js +10 -0
- data/modules/tests/functions/python-function/lambda_function.py +12 -0
- data/modules/tests/k8s.yaml +1 -1
- data/modules/tests/microservice_app.yaml +288 -0
- data/modules/tests/rds.yaml +5 -5
- data/modules/tests/regrooms/rds.yaml +5 -5
- data/modules/tests/server-with-scrub-muisms.yaml +1 -1
- data/modules/tests/super_complex_bok.yml +2 -2
- data/modules/tests/super_simple_bok.yml +2 -2
- metadata +42 -17
|
@@ -35,7 +35,7 @@ module MU
|
|
|
35
35
|
# Canonical Amazon Resource Number for this resource
|
|
36
36
|
# @return [String]
|
|
37
37
|
def arn
|
|
38
|
-
"arn:"+(MU::Cloud::AWS.isGovCloud?(@
|
|
38
|
+
"arn:"+(MU::Cloud::AWS.isGovCloud?(@region) ? "aws-us-gov" : "aws")+":elasticache:"+@region+":"+MU::Cloud::AWS.credToAcct(@credentials)+":cluster/"+@cloud_id
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Locate an existing Cache Cluster or Cache Clusters and return an array containing matching AWS resource descriptors for those that match.
|
|
@@ -109,7 +109,7 @@ module MU
|
|
|
109
109
|
def addStandardTags(resource, resource_type, region: MU.curRegion)
|
|
110
110
|
MU.log "Adding tags to ElasticCache resource #{resource}"
|
|
111
111
|
MU::Cloud::AWS.elasticache(region: region).add_tags_to_resource(
|
|
112
|
-
resource_name: MU::Cloud::AWS::CacheCluster.getARN(resource, resource_type, "elasticache", region: @
|
|
112
|
+
resource_name: MU::Cloud::AWS::CacheCluster.getARN(resource, resource_type, "elasticache", region: @region, credentials: @credentials),
|
|
113
113
|
tags: allTags
|
|
114
114
|
)
|
|
115
115
|
end
|
|
@@ -170,12 +170,12 @@ module MU
|
|
|
170
170
|
# config_struct[:preferred_cache_cluster_a_zs] = @config["preferred_cache_cluster_azs"]
|
|
171
171
|
|
|
172
172
|
MU.log "Creating cache replication group #{@config['identifier']}"
|
|
173
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
173
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).create_replication_group(config_struct).replication_group
|
|
174
174
|
|
|
175
175
|
wait_start_time = Time.now
|
|
176
176
|
retries = 0
|
|
177
177
|
begin
|
|
178
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
178
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).wait_until(:replication_group_available, replication_group_id: @config['identifier']) do |waiter|
|
|
179
179
|
waiter.max_attempts = nil
|
|
180
180
|
waiter.before_attempt do |attempts|
|
|
181
181
|
MU.log "Waiting for cache replication group #{@config['identifier']} to become available", MU::NOTICE if attempts % 5 == 0
|
|
@@ -192,11 +192,11 @@ module MU
|
|
|
192
192
|
retry
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
resp = MU::Cloud::AWS::CacheCluster.getCacheReplicationGroupById(@config['identifier'], region: @
|
|
195
|
+
resp = MU::Cloud::AWS::CacheCluster.getCacheReplicationGroupById(@config['identifier'], region: @region)
|
|
196
196
|
|
|
197
197
|
# We want to make sure the clusters in the cache replication group get our tags
|
|
198
198
|
resp.member_clusters.each { |member|
|
|
199
|
-
addStandardTags(member, "cluster", region: @
|
|
199
|
+
addStandardTags(member, "cluster", region: @region)
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
MU::Cloud.resourceClass("AWS", "DNSZone").genericMuDNSEntry(
|
|
@@ -228,7 +228,7 @@ module MU
|
|
|
228
228
|
|
|
229
229
|
MU.log "Creating cache cluster #{@config['identifier']}"
|
|
230
230
|
begin
|
|
231
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
231
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).create_cache_cluster(config_struct).cache_cluster
|
|
232
232
|
rescue ::Aws::ElastiCache::Errors::InvalidParameterValue => e
|
|
233
233
|
if e.message.match(/security group (sg-[^\s]+)/)
|
|
234
234
|
bad_sg = Regexp.last_match[1]
|
|
@@ -243,7 +243,7 @@ module MU
|
|
|
243
243
|
wait_start_time = Time.now
|
|
244
244
|
retries = 0
|
|
245
245
|
begin
|
|
246
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
246
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).wait_until(:cache_cluster_available, cache_cluster_id: @config['identifier']) do |waiter|
|
|
247
247
|
waiter.max_attempts = nil
|
|
248
248
|
waiter.before_attempt do |attempts|
|
|
249
249
|
MU.log "Waiting for cache cluster #{@config['identifier']} to become available", MU::NOTICE if attempts % 5 == 0
|
|
@@ -260,7 +260,7 @@ module MU
|
|
|
260
260
|
retry
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
-
resp = MU::Cloud::AWS::CacheCluster.getCacheClusterById(@config['identifier'], region: @
|
|
263
|
+
resp = MU::Cloud::AWS::CacheCluster.getCacheClusterById(@config['identifier'], region: @region, credentials: @credentials)
|
|
264
264
|
MU.log "Cache Cluster #{@config['identifier']} is ready to use"
|
|
265
265
|
@cloud_id = resp.cache_cluster_id
|
|
266
266
|
end
|
|
@@ -291,10 +291,10 @@ module MU
|
|
|
291
291
|
# If we didn't specify a VPC try to figure out if the account has a default VPC
|
|
292
292
|
vpc_id = nil
|
|
293
293
|
subnets = []
|
|
294
|
-
MU::Cloud::AWS.ec2(region: @
|
|
294
|
+
MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_vpcs.vpcs.each { |vpc|
|
|
295
295
|
if vpc.is_default
|
|
296
296
|
vpc_id = vpc.vpc_id
|
|
297
|
-
subnets = MU::Cloud::AWS.ec2(region: @
|
|
297
|
+
subnets = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_subnets(
|
|
298
298
|
filters: [
|
|
299
299
|
{
|
|
300
300
|
name: "vpc-id",
|
|
@@ -327,7 +327,7 @@ module MU
|
|
|
327
327
|
else
|
|
328
328
|
MU.log "Creating subnet group #{@config["subnet_group_name"]} for cache cluster #{@config['identifier']}"
|
|
329
329
|
|
|
330
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
330
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).create_cache_subnet_group(
|
|
331
331
|
cache_subnet_group_name: @config["subnet_group_name"],
|
|
332
332
|
cache_subnet_group_description: @config["subnet_group_name"],
|
|
333
333
|
subnet_ids: subnet_ids
|
|
@@ -347,7 +347,7 @@ module MU
|
|
|
347
347
|
# Create a Cache Cluster parameter group.
|
|
348
348
|
def createParameterGroup
|
|
349
349
|
MU.log "Creating a cache cluster parameter group #{@config["parameter_group_name"]}"
|
|
350
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
350
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).create_cache_parameter_group(
|
|
351
351
|
cache_parameter_group_name: @config["parameter_group_name"],
|
|
352
352
|
cache_parameter_group_family: @config["parameter_group_family"],
|
|
353
353
|
description: "Parameter group for #{@config["parameter_group_family"]}"
|
|
@@ -360,7 +360,7 @@ module MU
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
MU.log "Modifiying cache cluster parameter group #{@config["parameter_group_name"]}"
|
|
363
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
363
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).modify_cache_parameter_group(
|
|
364
364
|
cache_parameter_group_name: @config["parameter_group_name"],
|
|
365
365
|
parameter_name_values: params
|
|
366
366
|
)
|
|
@@ -370,7 +370,7 @@ module MU
|
|
|
370
370
|
# Retrieve a Cache Cluster parameter group name of on existing parameter group.
|
|
371
371
|
# @return [String]: Cache Cluster parameter group name.
|
|
372
372
|
def getParameterGroup
|
|
373
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
373
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).describe_cache_parameter_groups(
|
|
374
374
|
cache_parameter_group_name: @config["parameter_group_name"]
|
|
375
375
|
).cache_parameter_groups.first.cache_parameter_group_name
|
|
376
376
|
end
|
|
@@ -404,7 +404,7 @@ module MU
|
|
|
404
404
|
def notify
|
|
405
405
|
### TO DO: Flatten the replication group deployment metadata structure. It is probably waaaaaaay too nested.
|
|
406
406
|
if @config["create_replication_group"]
|
|
407
|
-
repl_group = MU::Cloud::AWS::CacheCluster.getCacheReplicationGroupById(@config['identifier'], region: @
|
|
407
|
+
repl_group = MU::Cloud::AWS::CacheCluster.getCacheReplicationGroupById(@config['identifier'], region: @region, credentials: @credentials)
|
|
408
408
|
# DNS records for the "real" zone should always be registered as late as possible so override_existing only overwrites the records after the resource is ready to use.
|
|
409
409
|
if @config['dns_records']
|
|
410
410
|
@config['dns_records'].each { |dnsrec|
|
|
@@ -418,7 +418,7 @@ module MU
|
|
|
418
418
|
deploy_struct = {
|
|
419
419
|
"identifier" => repl_group.replication_group_id,
|
|
420
420
|
"create_style" => @config["create_style"],
|
|
421
|
-
"region" => @
|
|
421
|
+
"region" => @region,
|
|
422
422
|
"members" => repl_group.member_clusters,
|
|
423
423
|
"automatic_failover" => repl_group.automatic_failover,
|
|
424
424
|
"snapshotting_cluster_id" => repl_group.snapshotting_cluster_id,
|
|
@@ -427,7 +427,7 @@ module MU
|
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
repl_group.member_clusters.each { |id|
|
|
430
|
-
cluster = MU::Cloud::AWS::CacheCluster.getCacheClusterById(id, region: @
|
|
430
|
+
cluster = MU::Cloud::AWS::CacheCluster.getCacheClusterById(id, region: @region)
|
|
431
431
|
|
|
432
432
|
vpc_sg_ids = []
|
|
433
433
|
cluster.security_groups.each { |vpc_sg|
|
|
@@ -468,7 +468,7 @@ module MU
|
|
|
468
468
|
deploy_struct[member.cache_cluster_id]["current_role"] = member.current_role
|
|
469
469
|
}
|
|
470
470
|
else
|
|
471
|
-
cluster = MU::Cloud::AWS::CacheCluster.getCacheClusterById(@config['identifier'], region: @
|
|
471
|
+
cluster = MU::Cloud::AWS::CacheCluster.getCacheClusterById(@config['identifier'], region: @region, credentials: @credentials)
|
|
472
472
|
|
|
473
473
|
vpc_sg_ids = []
|
|
474
474
|
cluster.security_groups.each { |vpc_sg|
|
|
@@ -515,7 +515,7 @@ module MU
|
|
|
515
515
|
|
|
516
516
|
attempts = 0
|
|
517
517
|
begin
|
|
518
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
518
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).create_snapshot(
|
|
519
519
|
cache_cluster_id: @config["identifier"],
|
|
520
520
|
snapshot_name: snap_id
|
|
521
521
|
)
|
|
@@ -535,7 +535,7 @@ module MU
|
|
|
535
535
|
MU.log "Waiting for snapshot of cache cluster #{@config["identifier"]} to be ready...", MU::NOTICE if attempts % 20 == 0
|
|
536
536
|
MU.log "Waiting for snapshot of cache cluster #{@config["identifier"]} to be ready...", MU::DEBUG
|
|
537
537
|
|
|
538
|
-
snapshot_resp = MU::Cloud::AWS.elasticache(region: @
|
|
538
|
+
snapshot_resp = MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).describe_snapshots(snapshot_name: snap_id)
|
|
539
539
|
attempts += 1
|
|
540
540
|
break unless snapshot_resp.snapshots.first.snapshot_status != "available"
|
|
541
541
|
sleep 15
|
|
@@ -546,7 +546,7 @@ module MU
|
|
|
546
546
|
|
|
547
547
|
# @return [String]: The cloud provider's identifier for the snapshot.
|
|
548
548
|
def getExistingSnapshot
|
|
549
|
-
MU::Cloud::AWS.elasticache(region: @
|
|
549
|
+
MU::Cloud::AWS.elasticache(region: @region, credentials: @credentials).describe_snapshots(snapshot_name: @config["identifier"]).snapshots.first.snapshot_name
|
|
550
550
|
rescue NoMethodError
|
|
551
551
|
raise MuError, "Snapshot #{@config["identifier"]} doesn't exist, make sure you provided a valid snapshot ID/Name"
|
|
552
552
|
end
|
|
@@ -569,7 +569,7 @@ module MU
|
|
|
569
569
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server.
|
|
570
570
|
# @param region [String]: The cloud provider's region in which to operate.
|
|
571
571
|
# @return [void]
|
|
572
|
-
def self.cleanup(noop: false, ignoremaster: false, credentials: nil, region: MU.curRegion, flags: {})
|
|
572
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, region: MU.curRegion, flags: {})
|
|
573
573
|
skipsnapshots = flags["skipsnapshots"]
|
|
574
574
|
all_clusters = MU::Cloud::AWS.elasticache(credentials: credentials, region: region).describe_cache_clusters
|
|
575
575
|
our_clusters = []
|
|
@@ -577,7 +577,7 @@ module MU
|
|
|
577
577
|
|
|
578
578
|
# Because we can't run list_tags_for_resource on a cache cluster that isn't in "available" state we're loading the deploy to make sure we have a cache cluster to cleanup.
|
|
579
579
|
# To ensure we don't miss cache clusters that have been terminated mid creation we'll load the 'original_config'. We might want to find a better approach for this.
|
|
580
|
-
deploy = MU::MommaCat.getLitter(
|
|
580
|
+
deploy = MU::MommaCat.getLitter(deploy_id)
|
|
581
581
|
if deploy.original_config && deploy.original_config.has_key?("cache_clusters") && !deploy.original_config["cache_clusters"].empty?
|
|
582
582
|
|
|
583
583
|
# The ElastiCache API and documentation are a mess, the replication group ARN resource_type is not documented, and is not easily guessable.
|
|
@@ -615,14 +615,14 @@ module MU
|
|
|
615
615
|
sleep 30
|
|
616
616
|
retry
|
|
617
617
|
else
|
|
618
|
-
raise MuError, "Failed to get tags for cache cluster #{cluster_id}, MU-ID #{
|
|
618
|
+
raise MuError, "Failed to get tags for cache cluster #{cluster_id}, MU-ID #{deploy_id}: #{e.inspect}"
|
|
619
619
|
end
|
|
620
620
|
end
|
|
621
621
|
|
|
622
622
|
found_muid = false
|
|
623
623
|
found_master = false
|
|
624
624
|
tags.each { |tag|
|
|
625
|
-
found_muid = true if tag.key == "MU-ID" && tag.value ==
|
|
625
|
+
found_muid = true if tag.key == "MU-ID" && tag.value == deploy_id
|
|
626
626
|
found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
|
|
627
627
|
}
|
|
628
628
|
next if !found_muid
|
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
# Copyright:: Copyright (c) 2020 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
|
+
# A scheduled task facility as configured in {MU::Config::BasketofKittens::cdns}
|
|
19
|
+
class CDN < MU::Cloud::CDN
|
|
20
|
+
|
|
21
|
+
# Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us.
|
|
22
|
+
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
|
23
|
+
def initialize(**args)
|
|
24
|
+
super
|
|
25
|
+
@mu_name ||= @deploy.getResourceName(@config["name"])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Called automatically by {MU::Deploy#createResources}
|
|
29
|
+
def create
|
|
30
|
+
resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).create_cloud_front_origin_access_identity(
|
|
31
|
+
cloud_front_origin_access_identity_config: {
|
|
32
|
+
caller_reference: @mu_name,
|
|
33
|
+
comment: @mu_name
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@origin_access_identity = "origin-access-identity/cloudfront/"+resp.cloud_front_origin_access_identity.id
|
|
38
|
+
|
|
39
|
+
params = get_properties
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
MU.log "Creating CloudFront distribution #{@mu_name}", details: params
|
|
43
|
+
MU.retrier([Aws::CloudFront::Errors::InvalidOrigin], wait: 10, max: 6) {
|
|
44
|
+
resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).create_distribution_with_tags(
|
|
45
|
+
distribution_config_with_tags: {
|
|
46
|
+
distribution_config: params,
|
|
47
|
+
tags: {
|
|
48
|
+
items: @tags.each_key.map { |k| { :key => k, :value => @tags[k] } }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
@cloud_id = resp.distribution.id
|
|
53
|
+
}
|
|
54
|
+
ready?
|
|
55
|
+
rescue ::Aws::CloudFront::Errors::InvalidViewerCertificate => e
|
|
56
|
+
cert_arn, cert_domains = MU::Cloud::AWS.findSSLCertificate(
|
|
57
|
+
name: @config['certificate']["name"],
|
|
58
|
+
id: @config['certificate']["id"],
|
|
59
|
+
region: @config['certificate']['region'],
|
|
60
|
+
credentials: @config['certificate']['credentials']
|
|
61
|
+
)
|
|
62
|
+
raise MuError.new e.message, details: { "aliases" => @config['aliases'], "certificate domains" => cert_domains }
|
|
63
|
+
rescue ::Aws::CloudFront::Errors::InvalidOrigin => e
|
|
64
|
+
raise MuError.new e.message, details: params[:origins]
|
|
65
|
+
rescue ::Aws::CloudFront::Errors::InvalidArgument => e
|
|
66
|
+
raise MuError.new e.message, details: params
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Called automatically by {MU::Deploy#createResources}
|
|
71
|
+
def groom
|
|
72
|
+
params = get_properties
|
|
73
|
+
|
|
74
|
+
if !@config['dns_records'].nil?
|
|
75
|
+
if !MU::Cloud::AWS.isGovCloud?
|
|
76
|
+
MU::Cloud.resourceClass("AWS", "DNSZone").createRecordsFromConfig(@config['dns_records'], target: cloud_desc.domain_name)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
MU.log "CloudFront Distribution #{@config['name']} at #{cloud_desc.domain_name}", MU::SUMMARY
|
|
80
|
+
if @config['aliases']
|
|
81
|
+
@config['aliases'].each { |a|
|
|
82
|
+
MU.log "Alias for CloudFront Distribution #{@config['name']}: #{a}", MU::SUMMARY
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Make sure we show up in the bucket policy of our target bucket,
|
|
87
|
+
# if it's a sibling in this deploy
|
|
88
|
+
cloud_desc(use_cache: false).origins.items.each { |o|
|
|
89
|
+
if o.s3_origin_config
|
|
90
|
+
id = o.s3_origin_config.origin_access_identity.sub(/^origin-access-identity\/cloudfront\//, '')
|
|
91
|
+
bucketref = get_bucketref_from_domain(o.domain_name)
|
|
92
|
+
next if !bucketref or !bucketref.kitten
|
|
93
|
+
resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).get_cloud_front_origin_access_identity(id: id)
|
|
94
|
+
# bucketref.kitten.allowPrincipal("arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity "+id, doc_id: "PolicyForCloudFrontPrivateContent", permissions: ["GetObject"])
|
|
95
|
+
bucketref.kitten.allowPrincipal(resp.cloud_front_origin_access_identity.s3_canonical_user_id, doc_id: "PolicyForCloudFrontPrivateContent", permissions: ["GetObject"], name: @mu_name)
|
|
96
|
+
end
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Canonical Amazon Resource Number for this resource
|
|
102
|
+
# @return [String]
|
|
103
|
+
def arn
|
|
104
|
+
cloud_desc ? cloud_desc.arn : nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Return the metadata for this cdn
|
|
108
|
+
# @return [Hash]
|
|
109
|
+
def notify
|
|
110
|
+
MU.structToHash(cloud_desc, stringify_keys: true)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Wait until the distribution is ready (status is +Deployed+)
|
|
114
|
+
def ready?
|
|
115
|
+
self.class.ready?(@cloud_id, credentials: @credentials)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Wait until a distribution is ready (status is +Deployed+)
|
|
119
|
+
# @param id [String]
|
|
120
|
+
# @param credentials [String]
|
|
121
|
+
def self.ready?(id, credentials: nil)
|
|
122
|
+
desc = nil
|
|
123
|
+
MU.retrier([], loop_if: Proc.new { !desc or desc.status != "Deployed" }, wait: 30, max:60) {
|
|
124
|
+
desc = MU::Cloud::AWS.cloudfront(credentials: credentials).get_distribution(id: id).distribution
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Does this resource type exist as a global (cloud-wide) artifact, or
|
|
129
|
+
# is it localized to a region/zone?
|
|
130
|
+
# @return [Boolean]
|
|
131
|
+
def self.isGlobal?
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Denote whether this resource implementation is experiment, ready for
|
|
136
|
+
# testing, or ready for production use.
|
|
137
|
+
def self.quality
|
|
138
|
+
MU::Cloud::ALPHA
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Remove all cdns associated with the currently loaded deployment.
|
|
142
|
+
# @param noop [Boolean]: If true, will only print what would be done
|
|
143
|
+
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
144
|
+
# @return [void]
|
|
145
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {})
|
|
146
|
+
|
|
147
|
+
resp = MU::Cloud::AWS.cloudfront(credentials: credentials).list_distributions
|
|
148
|
+
if resp and resp.distribution_list and resp.distribution_list.items
|
|
149
|
+
delete_threads = []
|
|
150
|
+
ids = Hash[resp.distribution_list.items.map { |distro| [distro.arn, distro] }]
|
|
151
|
+
|
|
152
|
+
ids.each_key { |arn|
|
|
153
|
+
tags = MU::Cloud::AWS.cloudfront(credentials: credentials).list_tags_for_resource(resource: arn).tags.items
|
|
154
|
+
|
|
155
|
+
found_muid = found_master = false
|
|
156
|
+
name = nil
|
|
157
|
+
tags.each { |tag|
|
|
158
|
+
name = tag.value if tag.key == "Name"
|
|
159
|
+
found_muid = true if tag.key == "MU-ID" && tag.value == deploy_id
|
|
160
|
+
found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if found_muid and (ignoremaster or found_master)
|
|
164
|
+
delete_threads << Thread.new(arn, name) { |my_arn, my_name|
|
|
165
|
+
current = MU::Cloud::AWS.cloudfront(credentials: credentials).get_distribution_config(id: ids[my_arn].id)
|
|
166
|
+
etag = current.etag
|
|
167
|
+
|
|
168
|
+
if !noop
|
|
169
|
+
|
|
170
|
+
if current.distribution_config.enabled
|
|
171
|
+
newcfg = MU.structToHash(current.distribution_config)
|
|
172
|
+
newcfg[:enabled] = false
|
|
173
|
+
MU.log "Disabling CloudFront distribution #{my_name ? my_name : ids[my_arn].id})", MU::NOTICE
|
|
174
|
+
updated = MU::Cloud::AWS.cloudfront(credentials: credentials).update_distribution(id: ids[my_arn].id, distribution_config: newcfg, if_match: etag)
|
|
175
|
+
etag = updated.etag
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
MU.log "Deleting CloudFront distribution #{my_name ? my_name : ids[my_arn].id})"
|
|
181
|
+
if !noop
|
|
182
|
+
ready?(ids[my_arn].id, credentials: credentials)
|
|
183
|
+
MU::Cloud::AWS.cloudfront(credentials: credentials).delete_distribution(id: ids[my_arn].id, if_match: etag)
|
|
184
|
+
end
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
}
|
|
188
|
+
delete_threads.each { |t| t.join }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
resp = MU::Cloud::AWS.cloudfront(credentials: credentials).list_cloud_front_origin_access_identities
|
|
192
|
+
if resp and resp.cloud_front_origin_access_identity_list and
|
|
193
|
+
resp.cloud_front_origin_access_identity_list.items.each and
|
|
194
|
+
deploy_id =~ /-\d{10}-[A-Z]{2}/
|
|
195
|
+
resp.cloud_front_origin_access_identity_list.items.each { |ident|
|
|
196
|
+
if ident.comment =~ /^#{Regexp.quote(deploy_id)}-/
|
|
197
|
+
MU.log "Deleting CloudFront origin access identity #{ident.id} (#{ident.comment})"
|
|
198
|
+
if !noop
|
|
199
|
+
getresp = MU::Cloud::AWS.cloudfront(credentials: credentials).get_cloud_front_origin_access_identity(id: ident.id)
|
|
200
|
+
begin
|
|
201
|
+
MU::Cloud::AWS.cloudfront(credentials: credentials).delete_cloud_front_origin_access_identity(id: ident.id, if_match: getresp.etag)
|
|
202
|
+
rescue ::Aws::CloudFront::Errors::CloudFrontOriginAccessIdentityInUse => e
|
|
203
|
+
MU.log "Got #{e.message} deleting #{ident.id}; it likely belongs to a distribution we can't to delete", MU::WARN, details: ident
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
}
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Locate an existing event.
|
|
212
|
+
# @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching CloudWatch Event
|
|
213
|
+
def self.find(**args)
|
|
214
|
+
found = {}
|
|
215
|
+
|
|
216
|
+
MU::Cloud::AWS.cloudfront(credentials: args[:credentials]).list_distributions.distribution_list.items.each { |d|
|
|
217
|
+
next if args[:cloud_id] and ![d.id, d.arn].include?(args[:cloud_id])
|
|
218
|
+
found[d.id] = d
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
found
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Reverse-map our cloud description into a runnable config hash.
|
|
225
|
+
# We assume that any values we have in +@config+ are placeholders, and
|
|
226
|
+
# calculate our own accordingly based on what's live in the cloud.
|
|
227
|
+
def toKitten(**_args)
|
|
228
|
+
bok = {
|
|
229
|
+
"cloud" => "AWS",
|
|
230
|
+
"credentials" => @credentials,
|
|
231
|
+
"cloud_id" => @cloud_id
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if !cloud_desc
|
|
235
|
+
MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
|
|
236
|
+
return nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).list_tags_for_resource(resource: arn)
|
|
240
|
+
if resp and resp.tags and resp.tags.items
|
|
241
|
+
tags = MU.structToHash(resp.tags.items, stringify_keys: true)
|
|
242
|
+
bok['name'] = MU::Adoption.tagsToName(tags)
|
|
243
|
+
bok['tags'] = tags if !tags.empty?
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if !bok['name']
|
|
247
|
+
bok['name'] = if cloud_desc.domain_name !~ /\.cloudfront\.net$/
|
|
248
|
+
cloud_desc.domain_name.sub(/\..*/, '')
|
|
249
|
+
elsif cloud_desc.aliases and !cloud_desc.aliases.items.empty?
|
|
250
|
+
cloud_desc.aliases.items.first.sub(/\..*/, '')
|
|
251
|
+
# XXX maybe try to guess from the name of an origin resource?
|
|
252
|
+
else
|
|
253
|
+
@cloud_id
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
cloud_desc.origins.items.each { |o|
|
|
258
|
+
bok['origins'] ||= []
|
|
259
|
+
origin = {
|
|
260
|
+
"path" => o.origin_path,
|
|
261
|
+
"name" => o.id
|
|
262
|
+
}
|
|
263
|
+
if o.s3_origin_config
|
|
264
|
+
origin["bucket"] = get_bucketref_from_domain(o.domain_name)
|
|
265
|
+
end
|
|
266
|
+
origin["domain_name"] = o.domain_name if !origin["bucket"]
|
|
267
|
+
if o.custom_origin_config
|
|
268
|
+
origin["http_port"] = o.custom_origin_config.http_port
|
|
269
|
+
origin["https_port"] = o.custom_origin_config.https_port
|
|
270
|
+
origin["protocol_policy"] = o.custom_origin_config.origin_protocol_policy
|
|
271
|
+
origin["ssl_protocols"] = o.custom_origin_config.origin_ssl_protocols.items
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
if o.custom_headers and !o.custom_headers.empty?
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
bok['origins'] << origin
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if cloud_desc.aliases and cloud_desc.aliases.items and
|
|
281
|
+
!cloud_desc.aliases.items.empty?
|
|
282
|
+
bok['aliases'] = cloud_desc.aliases.items
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
bok['disabled'] = true if !cloud_desc.enabled
|
|
286
|
+
|
|
287
|
+
bok['behaviors'] = []
|
|
288
|
+
|
|
289
|
+
add_behavior = Proc.new { |b, default|
|
|
290
|
+
behavior = {}
|
|
291
|
+
|
|
292
|
+
behavior["origin"] = b.target_origin_id
|
|
293
|
+
behavior["path_pattern"] = b.path_pattern if b.respond_to?(:path_pattern)
|
|
294
|
+
behavior["protocol_policy"] = b.viewer_protocol_policy
|
|
295
|
+
if b.lambda_function_associations and !b.lambda_function_associations.items.empty?
|
|
296
|
+
b.lambda_function_associations.items.each { |f|
|
|
297
|
+
behavior['functions'] ||= []
|
|
298
|
+
f.lambda_function_arn.match(/^arn:.*?:lambda:([^:]+?):(\d*):function:([^:]+)/)
|
|
299
|
+
region = Regexp.last_match[1]
|
|
300
|
+
acct = Regexp.last_match[2]
|
|
301
|
+
id = Regexp.last_match[3]
|
|
302
|
+
behavior['functions'] << MU::Config::Ref.get(
|
|
303
|
+
id: id,
|
|
304
|
+
region: region,
|
|
305
|
+
type: "functions",
|
|
306
|
+
event_type: f.event_type,
|
|
307
|
+
include_body: f.include_body,
|
|
308
|
+
cloud: "AWS",
|
|
309
|
+
credentials: @credentials,
|
|
310
|
+
habitat: MU::Config::Ref.get(
|
|
311
|
+
id: acct,
|
|
312
|
+
cloud: "AWS",
|
|
313
|
+
credentials: @credentials
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
[:min_ttl, :default_ttl, :max_ttl].each { |ttl|
|
|
318
|
+
behavior[ttl.to_s] = b.send(ttl)
|
|
319
|
+
}
|
|
320
|
+
end
|
|
321
|
+
bok['behaviors'] << behavior
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
add_behavior.call(cloud_desc.default_cache_behavior, true)
|
|
325
|
+
|
|
326
|
+
if cloud_desc.cache_behaviors and
|
|
327
|
+
!cloud_desc.cache_behaviors.items.empty?
|
|
328
|
+
cloud_desc.cache_behaviors.items.each { |b|
|
|
329
|
+
add_behavior.call(b, false)
|
|
330
|
+
}
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
bok
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# Cloud-specific configuration properties.
|
|
338
|
+
# @param _config [MU::Config]: The calling MU::Config object
|
|
339
|
+
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
|
340
|
+
def self.schema(_config)
|
|
341
|
+
toplevel_required = []
|
|
342
|
+
|
|
343
|
+
schema = {
|
|
344
|
+
"disabled" => {
|
|
345
|
+
"type" => "boolean",
|
|
346
|
+
"description" => "Flag this CloudFront distribution as disabled",
|
|
347
|
+
"default" => false
|
|
348
|
+
},
|
|
349
|
+
"certificate" => MU::Config::Ref.schema(type: "certificate", desc: "Required if any domains have been specified with +aliases+; parser will attempt to autodetect a valid ACM or IAM certificate if not specified.", omit_fields: ["cloud", "tag", "deploy_id"]),
|
|
350
|
+
"behaviors" => {
|
|
351
|
+
"items" => {
|
|
352
|
+
"properties" => {
|
|
353
|
+
"min_ttl" => {
|
|
354
|
+
"type" => "integer",
|
|
355
|
+
"description" => "The minimum amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.",
|
|
356
|
+
"default" => 0
|
|
357
|
+
},
|
|
358
|
+
"default_ttl" => {
|
|
359
|
+
"type" => "integer",
|
|
360
|
+
"description" => "The default amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.",
|
|
361
|
+
"default" => 86400
|
|
362
|
+
},
|
|
363
|
+
"max_ttl" => {
|
|
364
|
+
"type" => "integer",
|
|
365
|
+
"description" => "The maximum amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.",
|
|
366
|
+
"default" => 31536000
|
|
367
|
+
},
|
|
368
|
+
"protocol_policy" => {
|
|
369
|
+
"type" => "string",
|
|
370
|
+
"enum" => %w{allow-all https-only redirect-to-https},
|
|
371
|
+
"default" => "redirect-to-https"
|
|
372
|
+
},
|
|
373
|
+
"functions" => {
|
|
374
|
+
"type" => "array",
|
|
375
|
+
"items" => MU::Config::Ref.schema(type: "functions", desc: "Add a Lambda function which can be invoked on requests or responses through this distribution.")
|
|
376
|
+
},
|
|
377
|
+
"forwarded_values" => {
|
|
378
|
+
"type" => "object",
|
|
379
|
+
"description" => "HTTP request artifacts to include in requests passed to our back-end +origin+",
|
|
380
|
+
"default" => {
|
|
381
|
+
"query_string" => false
|
|
382
|
+
},
|
|
383
|
+
"properties" => {
|
|
384
|
+
"query_string" => {
|
|
385
|
+
"type" => "boolean",
|
|
386
|
+
"description" => "Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior and cache based on the query string parameters.",
|
|
387
|
+
"default" => false
|
|
388
|
+
},
|
|
389
|
+
"cookies" => {
|
|
390
|
+
"type" => "object",
|
|
391
|
+
"description" => "A complex type that specifies whether you want CloudFront to forward cookies to the origin and, if so, which ones.",
|
|
392
|
+
"default" => {
|
|
393
|
+
"forward" => "none"
|
|
394
|
+
},
|
|
395
|
+
"properties" => {
|
|
396
|
+
"forward" => {
|
|
397
|
+
"type" => "string",
|
|
398
|
+
"description" => "Specifies which cookies to forward to the origin for this cache behavior: all, none, or the list of cookies specified in +whitelisted_names+",
|
|
399
|
+
"enum" => %w{none whitelist all}
|
|
400
|
+
},
|
|
401
|
+
"whitelisted_names" => {
|
|
402
|
+
"type" => "array",
|
|
403
|
+
"items" => {
|
|
404
|
+
"description" => "Required if you specify whitelist for the value of +forward+",
|
|
405
|
+
"type" => "string"
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"headers" => {
|
|
411
|
+
"type" => "array",
|
|
412
|
+
"items" => {
|
|
413
|
+
"description" => "Specifies the headers, if any, that you want CloudFront to forward to the origin for this cache behavior (whitelisted headers).",
|
|
414
|
+
"type" => "string"
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
"query_string_cache_keys" => {
|
|
418
|
+
"type" => "array",
|
|
419
|
+
"items" => {
|
|
420
|
+
"description" => "Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior and cache based on the query string parameters",
|
|
421
|
+
"type" => "string"
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"origins" => {
|
|
430
|
+
"items" => {
|
|
431
|
+
"properties" => {
|
|
432
|
+
"bucket" => MU::Config::Ref.schema(type: "buckets", desc: "Reference an S3 bucket for use as an origin"),
|
|
433
|
+
"endpoint" => MU::Config::Ref.schema(type: "endpoints", desc: "Reference an API Gateway for use as an origin"),
|
|
434
|
+
"loadbalancer" => MU::Config::Ref.schema(type: "loadbalancers", desc: "Reference a Load Balancer for use as an origin"),
|
|
435
|
+
"connection_attempts" => {
|
|
436
|
+
"type" => "integer",
|
|
437
|
+
"default" => 3
|
|
438
|
+
},
|
|
439
|
+
"connection_timeout" => {
|
|
440
|
+
"type" => "integer",
|
|
441
|
+
"default" => 10
|
|
442
|
+
},
|
|
443
|
+
"protocol_policy" => {
|
|
444
|
+
"type" => "string",
|
|
445
|
+
"enum" => %w{http-only https-only match-viewer},
|
|
446
|
+
"default" => "match-viewer"
|
|
447
|
+
},
|
|
448
|
+
"ssl_protocols" => {
|
|
449
|
+
"type" => "array",
|
|
450
|
+
"default" => ["TLSv1.2"],
|
|
451
|
+
"items" => {
|
|
452
|
+
"type" => "string",
|
|
453
|
+
"enum" => %w{SSLv3 TLSv1 TLSv1.1 TLSv1.2},
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
"http_port" => {
|
|
457
|
+
"type" => "integer",
|
|
458
|
+
"default" => 80
|
|
459
|
+
},
|
|
460
|
+
"https_port" => {
|
|
461
|
+
"type" => "integer",
|
|
462
|
+
"default" => 443
|
|
463
|
+
},
|
|
464
|
+
"custom_headers" => {
|
|
465
|
+
"type" => "array",
|
|
466
|
+
"items" => {
|
|
467
|
+
"description" => "A list of HTTP header names and values that CloudFront adds to requests it sends to the origin.",
|
|
468
|
+
"type" => "object",
|
|
469
|
+
"required" => ["key", "value"],
|
|
470
|
+
"properties" => {
|
|
471
|
+
"key" => {
|
|
472
|
+
"type" => "string"
|
|
473
|
+
},
|
|
474
|
+
"value" => {
|
|
475
|
+
"type" => "string"
|
|
476
|
+
},
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
schema["behaviors"]["items"]["properties"]["functions"]["items"]["include_body"] = {
|
|
486
|
+
"type" => "boolean",
|
|
487
|
+
"default" => false
|
|
488
|
+
}
|
|
489
|
+
schema["behaviors"]["items"]["properties"]["functions"]["items"]["event_type"] = {
|
|
490
|
+
"type" => "string",
|
|
491
|
+
"enum" => %w{viewer-request viewer-response origin-request origin-response},
|
|
492
|
+
"default" => "viewer-request"
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
[toplevel_required, schema]
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::cdns}, bare and unvalidated.
|
|
499
|
+
# @param cdn [Hash]: The resource to process and validate
|
|
500
|
+
# @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
|
501
|
+
# @return [Boolean]: True if validation succeeded, False otherwise
|
|
502
|
+
def self.validateConfig(cdn, configurator)
|
|
503
|
+
ok = true
|
|
504
|
+
|
|
505
|
+
cdn['origins'].each { |o|
|
|
506
|
+
count = 0
|
|
507
|
+
['bucket', 'endpoint', 'loadbalancer'].each { |sib_type|
|
|
508
|
+
if o[sib_type]
|
|
509
|
+
if count > 0
|
|
510
|
+
ok = false
|
|
511
|
+
MU.log "Origin in CloudFront distro #{cdn['name']} may specify at most one of bucket, endpoint, or loadbalancer.", MU::ERR
|
|
512
|
+
end
|
|
513
|
+
target_ref = MU::Config::Ref.get(o[sib_type])
|
|
514
|
+
if target_ref.name
|
|
515
|
+
MU::Config.addDependency(cdn, target_ref.name, sib_type, their_phase: "groom")
|
|
516
|
+
end
|
|
517
|
+
count += 1
|
|
518
|
+
end
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
cert_domains = nil
|
|
523
|
+
|
|
524
|
+
if cdn['certificate']
|
|
525
|
+
cert_arn, cert_domains = MU::Cloud::AWS.resolveSSLCertificate(cdn['certificate'], region: cdn['region'], credentials: cdn['credentials'])
|
|
526
|
+
if !cert_arn
|
|
527
|
+
MU.log "Failed to find an ACM or IAM certificate specified in CloudFront distribution #{cdn['name']}", MU::ERR, details: cdn['certificate'].to_h
|
|
528
|
+
ok = false
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
if cdn['aliases']
|
|
533
|
+
cdn['aliases'].each { |a|
|
|
534
|
+
if !cdn['certificate']
|
|
535
|
+
foundcert, cert_domains = MU::Cloud::AWS.findSSLCertificate(name: a, region: cdn['region'], credentials: cdn['credentials'], raise_on_missing: false)
|
|
536
|
+
if !foundcert
|
|
537
|
+
foundcert, cert_domains = MU::Cloud::AWS.findSSLCertificate(name: a.sub(/^[^\.]+\./, '*.'), region: cdn['region'], credentials: cdn['credentials'], raise_on_missing: false)
|
|
538
|
+
end
|
|
539
|
+
if !foundcert
|
|
540
|
+
MU.log "Failed to find an ACM or IAM certificate matching #{a} for CloudFront distribution #{cdn['name']}", MU::ERR
|
|
541
|
+
ok = false
|
|
542
|
+
else
|
|
543
|
+
cdn['certificate'] = {
|
|
544
|
+
"id" => foundcert,
|
|
545
|
+
"credentials" => cdn['credentials']
|
|
546
|
+
}
|
|
547
|
+
MU.log "Auto-detected SSL certificate for CloudFront distribution #{cdn['name']} alias #{a}", MU::NOTICE, details: cdn['certificate']['id']
|
|
548
|
+
end
|
|
549
|
+
else
|
|
550
|
+
if !MU::Cloud::AWS.nameMatchesCertificate(a, cdn['certificate']['id'])
|
|
551
|
+
MU.log "Alias #{a} in CloudFront distro #{cdn['name']} does not appear to fit any domains on our SSL certificate", MU::ERR, details: cert_domains
|
|
552
|
+
ok = false
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
}
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
if cdn['dns_records'] and cdn['certificate']
|
|
559
|
+
cdn['dns_records'].each { |rec|
|
|
560
|
+
next if !rec['name']
|
|
561
|
+
dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec)
|
|
562
|
+
if MU::Cloud::AWS.nameMatchesCertificate(dnsname, cdn['certificate']['id'])
|
|
563
|
+
cdn['aliases'] ||= []
|
|
564
|
+
cdn['aliases'] << dnsname if !cdn['aliases'].include?(dnsname)
|
|
565
|
+
end
|
|
566
|
+
}
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
path_patterns = {}
|
|
570
|
+
cdn['behaviors'].each { |b|
|
|
571
|
+
b['path_pattern'] ||= "*"
|
|
572
|
+
path_patterns[b['path_pattern']] ||= 0
|
|
573
|
+
path_patterns[b['path_pattern']] += 1
|
|
574
|
+
}
|
|
575
|
+
path_patterns.each_pair { |pattern, origins|
|
|
576
|
+
if origins > 1
|
|
577
|
+
MU.log "CDN #{cdn['name']} has #{origins.to_s} uses of path_pattern '#{pattern}' in its behavior list (must be unique)", MU::ERR, details: cdn['behaviors']
|
|
578
|
+
ok = false
|
|
579
|
+
end
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
ok
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
private
|
|
586
|
+
|
|
587
|
+
def get_properties
|
|
588
|
+
params = {
|
|
589
|
+
default_root_object: @config['default_object'],
|
|
590
|
+
caller_reference: @mu_name, # eh, probably should be random
|
|
591
|
+
origins: {
|
|
592
|
+
quantity: @config['origins'].size,
|
|
593
|
+
items: []
|
|
594
|
+
},
|
|
595
|
+
comment: @deploy.deploy_id,
|
|
596
|
+
enabled: !(@config['disabled'])
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if @config['certificate']
|
|
600
|
+
params[:viewer_certificate] = {
|
|
601
|
+
ssl_support_method: "sni-only"
|
|
602
|
+
}
|
|
603
|
+
if @config['certificate']['id'] =~ /^arn:aws(?:-us-gov)?:iam/
|
|
604
|
+
params[:viewer_certificate][:iam_certificate_id] = @config['certificate']['id']
|
|
605
|
+
params[:viewer_certificate][:certificate_source] = "iam"
|
|
606
|
+
elsif @config['certificate']['id'] =~ /^arn:aws(?:-us-gov)?:acm/
|
|
607
|
+
params[:viewer_certificate][:acm_certificate_arn] = @config['certificate']['id']
|
|
608
|
+
params[:viewer_certificate][:certificate_source] = "acm"
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
@config['origins'].each { |o|
|
|
614
|
+
origin = {
|
|
615
|
+
id: o['name'],
|
|
616
|
+
}
|
|
617
|
+
sib_obj = nil
|
|
618
|
+
['bucket', 'endpoint', 'loadbalancer'].each { |sib_type|
|
|
619
|
+
if o[sib_type]
|
|
620
|
+
sib_obj = MU::Config::Ref.get(o[sib_type]).kitten(@deploy, cloud: "AWS")
|
|
621
|
+
if !sib_obj
|
|
622
|
+
raise MuError.new "Failed to resolve #{sib_type} referenced in CloudFront distribution #{@config['name']}", details: o[sib_type].to_h
|
|
623
|
+
end
|
|
624
|
+
break
|
|
625
|
+
end
|
|
626
|
+
}
|
|
627
|
+
if o['bucket']
|
|
628
|
+
origin[:domain_name] = sib_obj.cloud_desc["name"]+".s3.amazonaws.com"
|
|
629
|
+
origin[:origin_path] = o['path'] if o['path']
|
|
630
|
+
origin[:s3_origin_config] = {
|
|
631
|
+
origin_access_identity: @origin_access_identity
|
|
632
|
+
}
|
|
633
|
+
elsif o['endpoint']
|
|
634
|
+
origin[:domain_name] = sib_obj.cloud_id+".execute-api."+sib_obj.config['region']+".amazonaws.com"
|
|
635
|
+
origin[:custom_origin_config] = {
|
|
636
|
+
origin_protocol_policy: "https-only"
|
|
637
|
+
}
|
|
638
|
+
if sib_obj.config['deploy_to']
|
|
639
|
+
origin[:origin_path] ||= "/"+sib_obj.config['deploy_to']
|
|
640
|
+
end
|
|
641
|
+
elsif o['loadbalancer']
|
|
642
|
+
origin[:domain_name] = sib_obj.cloud_desc.dns_name
|
|
643
|
+
origin[:origin_path] = o['path'] if o['path']
|
|
644
|
+
else # XXX make sure parser guarantees these are present
|
|
645
|
+
origin[:domain_name] = o['domain_name']
|
|
646
|
+
origin[:origin_path] = o['path']
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
if o['custom_headers']
|
|
650
|
+
origin[:custom_headers] = {
|
|
651
|
+
quantity: o['custom_headers'].size,
|
|
652
|
+
items: o['custom_headers'].map { |h|
|
|
653
|
+
{
|
|
654
|
+
header_name: h['key'],
|
|
655
|
+
header_value: h['value']
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
[:connection_attempts, :connection_timeout].each { |field|
|
|
662
|
+
origin[field] ||= o[field.to_s]
|
|
663
|
+
}
|
|
664
|
+
if !origin[:s3_origin_config]
|
|
665
|
+
maplet = {
|
|
666
|
+
'protocol_policy' => :origin_protocol_policy,
|
|
667
|
+
'ssl_protocols' => :origin_ssl_protocols,
|
|
668
|
+
'http_port' => :http_port,
|
|
669
|
+
'https_port' => :https_port
|
|
670
|
+
}
|
|
671
|
+
maplet.each_pair { |field, paramfield|
|
|
672
|
+
next if !o[field]
|
|
673
|
+
origin[:custom_origin_config] ||= {}
|
|
674
|
+
origin[:custom_origin_config][paramfield] ||= if o[field.to_s].is_a?(Array)
|
|
675
|
+
{
|
|
676
|
+
quantity: o[field].size,
|
|
677
|
+
items: o[field]
|
|
678
|
+
}
|
|
679
|
+
else
|
|
680
|
+
o[field]
|
|
681
|
+
end
|
|
682
|
+
}
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
params[:origins][:items] << origin
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
# if we have any placeholder DNS records that are intended to be
|
|
689
|
+
# filled out with our runtime @mu_name, do so, and add an alias if
|
|
690
|
+
# applicable
|
|
691
|
+
if @config['dns_records']
|
|
692
|
+
@config['dns_records'].each { |rec|
|
|
693
|
+
if !rec['name']
|
|
694
|
+
rec['name'] = @mu_name.downcase
|
|
695
|
+
dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec)
|
|
696
|
+
if @config['certificate'] and MU::Cloud::AWS.nameMatchesCertificate(dnsname, @config['certificate']['id'])
|
|
697
|
+
@config['aliases'] ||= []
|
|
698
|
+
@config['aliases'] << dnsname if !@config['aliases'].include?(dnsname)
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
}
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
if @config['aliases']
|
|
705
|
+
params[:aliases] = {
|
|
706
|
+
items: @config['aliases'],
|
|
707
|
+
quantity: @config['aliases'].size
|
|
708
|
+
}
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# XXX config parser should guarantee a default behavior
|
|
712
|
+
@config['behaviors'].each { |b|
|
|
713
|
+
b['origin'] ||= @config['origins'].first['name']
|
|
714
|
+
behavior = {
|
|
715
|
+
target_origin_id: b['origin'],
|
|
716
|
+
viewer_protocol_policy: b['protocol_policy'],
|
|
717
|
+
min_ttl: b['min_ttl'],
|
|
718
|
+
max_ttl: b['max_ttl'],
|
|
719
|
+
default_ttl: b['default_ttl'],
|
|
720
|
+
}
|
|
721
|
+
behavior[:trusted_signers] = {
|
|
722
|
+
enabled: false,
|
|
723
|
+
quantity: 0,
|
|
724
|
+
# items: []
|
|
725
|
+
}
|
|
726
|
+
behavior[:forwarded_values] = {
|
|
727
|
+
query_string: b['forwarded_values']['query_string'],
|
|
728
|
+
cookies: {
|
|
729
|
+
forward: b['forwarded_values']['cookies']['forward']
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if b['forwarded_values']['cookies']['whitelisted_names']
|
|
733
|
+
behavior[:forwarded_values][:cookies][:whitelisted_names] = {
|
|
734
|
+
quantity: b['forwarded_values']['cookies']['whitelisted_names'].size,
|
|
735
|
+
items: b['forwarded_values']['cookies']['whitelisted_names']
|
|
736
|
+
}
|
|
737
|
+
end
|
|
738
|
+
['headers', 'query_string_cache_keys'].each { |field|
|
|
739
|
+
if b['forwarded_values'][field]
|
|
740
|
+
behavior[:forwarded_values][field.to_sym] = {
|
|
741
|
+
quantity: b['forwarded_values'][field].size,
|
|
742
|
+
items: b['forwarded_values'][field]
|
|
743
|
+
}
|
|
744
|
+
end
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if @config['behaviors'].size == 1 or b['path_pattern'] == "*"
|
|
748
|
+
params[:default_cache_behavior] = behavior
|
|
749
|
+
else
|
|
750
|
+
behavior[:path_pattern] = b['path_pattern']
|
|
751
|
+
params[:cache_behaviors] ||= {
|
|
752
|
+
quantity: (@config['behaviors'].size-1),
|
|
753
|
+
items: []
|
|
754
|
+
}
|
|
755
|
+
params[:cache_behaviors][:items] << behavior
|
|
756
|
+
end
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
params
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def get_bucketref_from_domain(domain_name)
|
|
763
|
+
buckets = MU::Cloud.resourceClass("AWS", "Bucket").find(credentials: @credentials, allregions: true, cloud_id: domain_name.sub(/\..*/, ''))
|
|
764
|
+
if buckets and buckets.size == 1
|
|
765
|
+
return MU::Config::Ref.get(
|
|
766
|
+
id: buckets.keys.first,
|
|
767
|
+
type: "buckets",
|
|
768
|
+
region: buckets.values.first["region"],
|
|
769
|
+
credentials: @credentials,
|
|
770
|
+
cloud: "AWS"
|
|
771
|
+
)
|
|
772
|
+
else
|
|
773
|
+
MU.log "Failed to locate or isolate a bucket object from #{domain_name}", MU::WARN, details: buckets.keys
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
nil
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
end
|