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.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/ansible/roles/mu-nat/tasks/main.yml +3 -0
  4. data/bin/mu-adopt +12 -1
  5. data/bin/mu-aws-setup +41 -7
  6. data/bin/mu-azure-setup +34 -0
  7. data/bin/mu-configure +214 -119
  8. data/bin/mu-gcp-setup +37 -2
  9. data/bin/mu-load-config.rb +2 -1
  10. data/bin/mu-node-manage +3 -0
  11. data/bin/mu-refresh-ssl +67 -0
  12. data/bin/mu-run-tests +28 -6
  13. data/bin/mu-self-update +30 -10
  14. data/bin/mu-upload-chef-artifacts +30 -26
  15. data/cloud-mu.gemspec +10 -8
  16. data/cookbooks/mu-master/attributes/default.rb +5 -1
  17. data/cookbooks/mu-master/metadata.rb +2 -2
  18. data/cookbooks/mu-master/recipes/default.rb +81 -26
  19. data/cookbooks/mu-master/recipes/init.rb +197 -62
  20. data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
  21. data/cookbooks/mu-master/recipes/vault.rb +78 -77
  22. data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
  23. data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
  24. data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
  25. data/cookbooks/mu-tools/attributes/default.rb +12 -0
  26. data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
  27. data/cookbooks/mu-tools/libraries/helper.rb +98 -4
  28. data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
  29. data/cookbooks/mu-tools/recipes/apply_security.rb +31 -9
  30. data/cookbooks/mu-tools/recipes/aws_api.rb +8 -2
  31. data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
  32. data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
  33. data/cookbooks/mu-tools/recipes/google_api.rb +7 -0
  34. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  35. data/cookbooks/mu-tools/resources/disk.rb +113 -42
  36. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  37. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  38. data/extras/Gemfile.lock.bootstrap +394 -0
  39. data/extras/bucketstubs/error.html +0 -0
  40. data/extras/bucketstubs/index.html +0 -0
  41. data/extras/clean-stock-amis +11 -3
  42. data/extras/generate-stock-images +6 -3
  43. data/extras/git_rpm/build.sh +20 -0
  44. data/extras/git_rpm/mugit.spec +53 -0
  45. data/extras/image-generators/AWS/centos7.yaml +19 -16
  46. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  47. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  48. data/extras/image-generators/VMWare/centos8.yaml +15 -0
  49. data/extras/openssl_rpm/build.sh +19 -0
  50. data/extras/openssl_rpm/mussl.spec +46 -0
  51. data/extras/python_rpm/muthon.spec +14 -4
  52. data/extras/ruby_rpm/muby.spec +9 -5
  53. data/extras/sqlite_rpm/build.sh +19 -0
  54. data/extras/sqlite_rpm/muqlite.spec +47 -0
  55. data/install/installer +7 -5
  56. data/modules/mommacat.ru +2 -2
  57. data/modules/mu.rb +14 -7
  58. data/modules/mu/adoption.rb +5 -5
  59. data/modules/mu/cleanup.rb +47 -25
  60. data/modules/mu/cloud.rb +29 -1
  61. data/modules/mu/cloud/dnszone.rb +0 -2
  62. data/modules/mu/cloud/machine_images.rb +1 -1
  63. data/modules/mu/cloud/providers.rb +6 -1
  64. data/modules/mu/cloud/resource_base.rb +16 -7
  65. data/modules/mu/cloud/ssh_sessions.rb +5 -1
  66. data/modules/mu/cloud/wrappers.rb +20 -7
  67. data/modules/mu/config.rb +28 -12
  68. data/modules/mu/config/bucket.rb +31 -2
  69. data/modules/mu/config/cache_cluster.rb +1 -1
  70. data/modules/mu/config/cdn.rb +100 -0
  71. data/modules/mu/config/container_cluster.rb +1 -1
  72. data/modules/mu/config/database.rb +3 -3
  73. data/modules/mu/config/dnszone.rb +4 -3
  74. data/modules/mu/config/endpoint.rb +1 -0
  75. data/modules/mu/config/firewall_rule.rb +1 -1
  76. data/modules/mu/config/function.rb +16 -7
  77. data/modules/mu/config/job.rb +89 -0
  78. data/modules/mu/config/notifier.rb +7 -18
  79. data/modules/mu/config/ref.rb +55 -9
  80. data/modules/mu/config/schema_helpers.rb +12 -3
  81. data/modules/mu/config/server.rb +11 -5
  82. data/modules/mu/config/server_pool.rb +2 -2
  83. data/modules/mu/config/vpc.rb +11 -10
  84. data/modules/mu/defaults/AWS.yaml +106 -106
  85. data/modules/mu/deploy.rb +40 -14
  86. data/modules/mu/groomers/chef.rb +2 -2
  87. data/modules/mu/master.rb +70 -3
  88. data/modules/mu/mommacat.rb +28 -9
  89. data/modules/mu/mommacat/daemon.rb +13 -7
  90. data/modules/mu/mommacat/naming.rb +2 -2
  91. data/modules/mu/mommacat/search.rb +16 -5
  92. data/modules/mu/mommacat/storage.rb +67 -32
  93. data/modules/mu/providers/aws.rb +298 -85
  94. data/modules/mu/providers/aws/alarm.rb +5 -5
  95. data/modules/mu/providers/aws/bucket.rb +284 -50
  96. data/modules/mu/providers/aws/cache_cluster.rb +26 -26
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/providers/aws/collection.rb +16 -16
  99. data/modules/mu/providers/aws/container_cluster.rb +84 -64
  100. data/modules/mu/providers/aws/database.rb +59 -55
  101. data/modules/mu/providers/aws/dnszone.rb +29 -12
  102. data/modules/mu/providers/aws/endpoint.rb +535 -50
  103. data/modules/mu/providers/aws/firewall_rule.rb +32 -26
  104. data/modules/mu/providers/aws/folder.rb +1 -1
  105. data/modules/mu/providers/aws/function.rb +300 -134
  106. data/modules/mu/providers/aws/group.rb +16 -14
  107. data/modules/mu/providers/aws/habitat.rb +4 -4
  108. data/modules/mu/providers/aws/job.rb +469 -0
  109. data/modules/mu/providers/aws/loadbalancer.rb +67 -45
  110. data/modules/mu/providers/aws/log.rb +17 -17
  111. data/modules/mu/providers/aws/msg_queue.rb +22 -13
  112. data/modules/mu/providers/aws/nosqldb.rb +99 -8
  113. data/modules/mu/providers/aws/notifier.rb +137 -65
  114. data/modules/mu/providers/aws/role.rb +119 -83
  115. data/modules/mu/providers/aws/search_domain.rb +166 -30
  116. data/modules/mu/providers/aws/server.rb +209 -118
  117. data/modules/mu/providers/aws/server_pool.rb +95 -130
  118. data/modules/mu/providers/aws/storage_pool.rb +19 -11
  119. data/modules/mu/providers/aws/user.rb +5 -5
  120. data/modules/mu/providers/aws/userdata/linux.erb +5 -4
  121. data/modules/mu/providers/aws/vpc.rb +109 -54
  122. data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
  123. data/modules/mu/providers/azure.rb +78 -12
  124. data/modules/mu/providers/azure/server.rb +20 -4
  125. data/modules/mu/providers/cloudformation/server.rb +1 -1
  126. data/modules/mu/providers/google.rb +21 -5
  127. data/modules/mu/providers/google/bucket.rb +1 -1
  128. data/modules/mu/providers/google/container_cluster.rb +1 -1
  129. data/modules/mu/providers/google/database.rb +1 -1
  130. data/modules/mu/providers/google/firewall_rule.rb +1 -1
  131. data/modules/mu/providers/google/folder.rb +7 -3
  132. data/modules/mu/providers/google/function.rb +66 -31
  133. data/modules/mu/providers/google/group.rb +1 -1
  134. data/modules/mu/providers/google/habitat.rb +1 -1
  135. data/modules/mu/providers/google/loadbalancer.rb +1 -1
  136. data/modules/mu/providers/google/role.rb +6 -3
  137. data/modules/mu/providers/google/server.rb +1 -1
  138. data/modules/mu/providers/google/server_pool.rb +1 -1
  139. data/modules/mu/providers/google/user.rb +1 -1
  140. data/modules/mu/providers/google/vpc.rb +28 -3
  141. data/modules/tests/aws-jobs-functions.yaml +46 -0
  142. data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
  143. data/modules/tests/centos6.yaml +4 -0
  144. data/modules/tests/centos7.yaml +4 -0
  145. data/modules/tests/ecs.yaml +2 -2
  146. data/modules/tests/eks.yaml +1 -1
  147. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  148. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  149. data/modules/tests/k8s.yaml +1 -1
  150. data/modules/tests/microservice_app.yaml +288 -0
  151. data/modules/tests/rds.yaml +5 -5
  152. data/modules/tests/regrooms/rds.yaml +5 -5
  153. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  154. data/modules/tests/super_complex_bok.yml +2 -2
  155. data/modules/tests/super_simple_bok.yml +2 -2
  156. metadata +42 -17
@@ -50,6 +50,24 @@ module MU
50
50
  "default" => "index.html",
51
51
  "description" => "If +web_enabled+, return this object when \"diretory\" (a path not ending in a key/object) is invoked."
52
52
  },
53
+ "upload" => {
54
+ "type" => "array",
55
+ "items" => {
56
+ "type" => "object",
57
+ "description" => "Upload objects to a bucket, where supported",
58
+ "required" => ["source", "destination"],
59
+ "properties" => {
60
+ "source" => {
61
+ "type" => "string",
62
+ "description" => "A file or directory to upload. If a directory is specified, it will be recursively mirrored to the bucket +destination+."
63
+ },
64
+ "destination" => {
65
+ "type" => "string",
66
+ "description" => "Path to which +source+ file(s) will be uploaded in the bucket, relative to +/+"
67
+ }
68
+ }
69
+ }
70
+ },
53
71
  "policies" => {
54
72
  "type" => "array",
55
73
  "items" => MU::Config::Role.policy_primitive(subobjects: true, grant_to: true, permissions_optional: true, targets_optional: true)
@@ -59,12 +77,23 @@ module MU
59
77
  end
60
78
 
61
79
  # Generic pre-processing of {MU::Config::BasketofKittens::buckets}, bare and unvalidated.
62
- # @param _bucket [Hash]: The resource to process and validate
80
+ # @param bucket [Hash]: The resource to process and validate
63
81
  # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
64
82
  # @return [Boolean]: True if validation succeeded, False otherwise
65
- def self.validate(_bucket, _configurator)
83
+ def self.validate(bucket, _configurator)
66
84
  ok = true
67
85
 
86
+ if bucket['upload']
87
+ bucket['upload'].each { |batch|
88
+ if !File.exists?(batch['source'])
89
+ MU.log "Bucket '#{bucket['name']}' specifies upload for file/directory that does not exist", MU::ERR, details: batch
90
+ ok = false
91
+ next
92
+ end
93
+ batch['source'] = File.realpath(File.expand_path(batch['source']))
94
+ }
95
+ end
96
+
68
97
  ok
69
98
  end
70
99
 
@@ -54,7 +54,7 @@ module MU
54
54
  "type" => "string",
55
55
  "default" => "redis"
56
56
  },
57
- "dns_records" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true),
57
+ "dns_records" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true, embedded_type: "cache_cluster"),
58
58
  "dns_sync_wait" => {
59
59
  "type" => "boolean",
60
60
  "description" => "Wait for DNS record to propagate in DNS Zone.",
@@ -0,0 +1,100 @@
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 Config
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/job.rb
18
+ class CDN
19
+
20
+ # Base configuration schema for a scheduled job
21
+ # @return [Hash]
22
+ def self.schema
23
+ {
24
+ "type" => "object",
25
+ "additionalProperties" => false,
26
+ "required" => ["origins"],
27
+ "properties" => {
28
+ "name" => {
29
+ "type" => "string"
30
+ },
31
+ "dns_records" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true, embedded_type: "cdn"),
32
+ "default_object" => {
33
+ "type" => "string",
34
+ "default" => "index.html"
35
+ },
36
+ "credentials" => MU::Config.credentials_primitive,
37
+ "aliases" => {
38
+ "type" => "array",
39
+ "items" => {
40
+ "type" => "string"
41
+ }
42
+ },
43
+ "origins" => {
44
+ "type" => "array",
45
+ "minItems" => 1,
46
+ "items" => {
47
+ "type" => "object",
48
+ "description" => "One or more back-end sources which this CDN will cache",
49
+ "required" => ["name"],
50
+ "properties" => {
51
+ "name" => {
52
+ "type" => "string",
53
+ "description" => "A unique identifying string which other components of this distribution may use to reference this origin"
54
+ },
55
+ "domain_name" => {
56
+ "type" => "string",
57
+ "description" => "Domain name of the back-end web server or other resource behind this CDN"
58
+ },
59
+ "path" => {
60
+ "type" => "string",
61
+ "default" => "",
62
+ "description" => "Optional path on back-end service to which to map front-end requests"
63
+ }
64
+ }
65
+ }
66
+ },
67
+ "behaviors" => {
68
+ "type" => "array",
69
+ "items" => {
70
+ "description" => "Customize the behavior of requests sent to one of this CDN's configured +origins+",
71
+ "type" => "object",
72
+ "properties" => {
73
+ "origin" => {
74
+ "type" => "string",
75
+ "description" => "Which of our +origins+ this set of behaviors should map to, by its +name+ field."
76
+ },
77
+ "path_pattern" => {
78
+ "type" => "string",
79
+ "description" => "The request path or paths for which this behavior should be invoked"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ end
87
+
88
+ # Generic pre-processing of {MU::Config::BasketofKittens::jobs}, bare and unvalidated.
89
+ # @param _job [Hash]: The resource to process and validate
90
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
91
+ # @return [Boolean]: True if validation succeeded, False otherwise
92
+ def self.validate(_job, _configurator)
93
+ ok = true
94
+
95
+ ok
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -48,7 +48,7 @@ module MU
48
48
  "properties" => {
49
49
  "version" => {
50
50
  "type" => "string",
51
- "default" => "1.14",
51
+ "default" => "1.15",
52
52
  "description" => "Version of Kubernetes control plane to deploy",
53
53
  },
54
54
  "max_pods" => {
@@ -62,7 +62,7 @@ module MU
62
62
  "default" => false
63
63
  },
64
64
  "member_of_cluster" => MU::Config::Ref.schema(type: "databases", desc: "Internal use"),
65
- "dns_records" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true),
65
+ "dns_records" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true, embedded_type: "database"),
66
66
  "dns_sync_wait" => {
67
67
  "type" => "boolean",
68
68
  "description" => "Wait for DNS record to propagate in DNS Zone.",
@@ -341,7 +341,7 @@ module MU
341
341
  "region" => db['region'],
342
342
  "credentials" => db['credentials'],
343
343
  }
344
- MU::Config.addDependency(replica, db["name"], "database", phase: "groom")
344
+ MU::Config.addDependency(replica, db["name"], "database", their_phase: "groom")
345
345
  read_replicas << replica
346
346
  end
347
347
  end
@@ -367,7 +367,7 @@ module MU
367
367
  "type" => "databases"
368
368
  }
369
369
  # AWS will figure out for us which database instance is the writer/master so we can create all of them concurrently.
370
- MU::Config.addDependency(node, db["name"], "database", phase: "groom")
370
+ MU::Config.addDependency(node, db["name"], "database", their_phase: "groom")
371
371
  cluster_nodes << node
372
372
 
373
373
  # Alarms are set on each DB cluster node, not on the cluster itself,
@@ -60,7 +60,7 @@ module MU
60
60
  # @param default_type [String]: The type of record to make default (e.g. An, CNAME, etc)
61
61
  # @param need_zone [Boolean]: Whether to explicitly require a zone be declared
62
62
  # @return [Hash]
63
- def self.records_primitive(need_target: true, default_type: nil, need_zone: false)
63
+ def self.records_primitive(need_target: true, default_type: nil, need_zone: false, embedded_type: nil)
64
64
  dns_records_primitive = {
65
65
  "type" => "array",
66
66
  "maxItems" => 100,
@@ -107,8 +107,9 @@ module MU
107
107
  },
108
108
  "mu_type" => {
109
109
  "type" => "string",
110
- "description" => "The Mu resource type to search the deployment for.",
111
- "enum" => ["loadbalancer", "server", "database", "cache_cluster"]
110
+ "description" => "The mu type of a resource being targeted.",
111
+ "enum" => embedded_type ? [embedded_type] : ["loadbalancer", "server", "database", "cache_cluster", "endpoint", "cdn"],
112
+ "default" => embedded_type
112
113
  },
113
114
  "target_type" => {
114
115
  "description" => "If the target is a public or a private resource. This only applies to servers/server_pools when using automatic DNS registration. If set to public but the target only has a private address, the private address will be used",
@@ -32,6 +32,7 @@ module MU
32
32
  "iam_role" => {"type" => "string"},
33
33
  "region" => MU::Config.region_primitive,
34
34
  "vpc" => MU::Config::VPC.reference(MU::Config::VPC::NO_SUBNETS, MU::Config::VPC::NO_NAT_OPTS),
35
+ "dns_records" => MU::Config::DNSZone.records_primitive(need_target: false, default_type: "CNAME", need_zone: true, embedded_type: "endpoint"),
35
36
  "methods" => {
36
37
  "type" => "array",
37
38
  "items" => {
@@ -119,7 +119,7 @@ module MU
119
119
  if acl_include['sgs']
120
120
  acl_include['sgs'].each { |sg_ref|
121
121
  if haveLitterMate?(sg_ref, "firewall_rules")
122
- MU::Config.addDependency(acl, sg_ref, "firewall_rule", no_create_wait: true)
122
+ MU::Config.addDependency(acl, sg_ref, "firewall_rule", my_phase: "groom")
123
123
  siblingfw = haveLitterMate?(sg_ref, "firewall_rules")
124
124
  if !siblingfw["#MU_VALIDATED"]
125
125
  # XXX raise failure somehow
@@ -71,6 +71,10 @@ module MU
71
71
  "zip_file" => {
72
72
  "type" => "string",
73
73
  "description" => "Path to a zipped deployment package to upload."
74
+ },
75
+ "path" => {
76
+ "type" => "string",
77
+ "description" => "Path to a directory that can be zipped into deployment package to upload."
74
78
  }
75
79
  }
76
80
  },
@@ -106,13 +110,18 @@ module MU
106
110
  if !function['code']
107
111
  ok = false
108
112
  end
109
- if function['code'] and function['code']['zip_file']
110
- if !File.readable?(function['code']['zip_file'])
111
- MU.log "Can't read Function deployment package #{function['code']['zip_file']}", MU::ERR
112
- ok = false
113
- else
114
- function['code']['zip_file'] = File.realpath(File.expand_path(function['code']['zip_file']))
115
- end
113
+
114
+ if function['code']
115
+ ['zip_file', 'path'].each { |src|
116
+ if function['code'][src]
117
+ if !File.readable?(function['code'][src]) and !Dir.exists?(function['code'][src])
118
+ MU.log "Function '#{function['name']}' specifies a deployment package that I can't read at #{function['code'][src]}", MU::ERR
119
+ ok = false
120
+ else
121
+ function['code'][src] = File.realpath(File.expand_path(function['code'][src]))
122
+ end
123
+ end
124
+ }
116
125
  end
117
126
 
118
127
  ok
@@ -0,0 +1,89 @@
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 Config
17
+ # Basket of Kittens config schema and parser logic. See modules/mu/providers/*/job.rb
18
+ class Job
19
+
20
+ # Base configuration schema for a scheduled job
21
+ # @return [Hash]
22
+ def self.schema
23
+ {
24
+ "type" => "object",
25
+ "additionalProperties" => false,
26
+ "description" => "A cloud provider-specific facility for triggered or scheduled tasks, such as AWS CloudWatch Events or Google Cloud Scheduler.",
27
+ "properties" => {
28
+ "name" => {
29
+ "type" => "string"
30
+ },
31
+ "region" => MU::Config.region_primitive,
32
+ "credentials" => MU::Config.credentials_primitive,
33
+ "description" => {
34
+ "type" => "string",
35
+ "description" => "Human-readable description field for this job (this will field be overriden with the Mu deploy id on most providers unless +scrub_mu_isms+ is set)"
36
+ },
37
+ "schedule" => {
38
+ "type" => "object",
39
+ "description" => "A schedule on which to invoke this task, typically unix crontab style.",
40
+ "properties" => {
41
+ "minute" => {
42
+ "type" => "string",
43
+ "description" => "The minute of the hour at which to invoke this job, typically an integer between 0 and 59. This will be validated by the cloud provider, where other more human-readable values may be supported.",
44
+ "default" => "0"
45
+ },
46
+ "hour" => {
47
+ "type" => "string",
48
+ "description" => "The hour at which to invoke this job, typically an integer between 0 and 23. This will be validated by the cloud provider, where other more human-readable values may be supported.",
49
+ "default" => "*"
50
+ },
51
+ "day_of_month" => {
52
+ "type" => "string",
53
+ "description" => "The day of the month which to invoke this job, typically an integer between 1 and 31. This will be validated by the cloud provider, where other more human-readable values may be supported.",
54
+ "default" => "*"
55
+ },
56
+ "month" => {
57
+ "type" => "string",
58
+ "description" => "The month in which to invoke this job, typically an integer between 1 and 12. This will be validated by the cloud provider, where other more human-readable values may be supported.",
59
+ "default" => "*"
60
+ },
61
+ "day_of_week" => {
62
+ "type" => "string",
63
+ "description" => "The day of the week on which to invoke this job, typically an integer between 0 and 6. This will be validated by the cloud provider, where other more human-readable values may be supported.",
64
+ "default" => "*"
65
+ },
66
+ "year" => {
67
+ "type" => "string",
68
+ "description" => "The year in which to invoke this job. Not honored by all cloud providers.",
69
+ "default" => "*"
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ end
76
+
77
+ # Generic pre-processing of {MU::Config::BasketofKittens::jobs}, bare and unvalidated.
78
+ # @param _job [Hash]: The resource to process and validate
79
+ # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
80
+ # @return [Boolean]: True if validation succeeded, False otherwise
81
+ def self.validate(_job, _configurator)
82
+ ok = true
83
+
84
+ ok
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -36,12 +36,12 @@ module MU
36
36
  "items" => {
37
37
  "type" => "object",
38
38
  "description" => "A list of people or resources which should receive notifications",
39
- "required" => ["endpoint"],
40
39
  "properties" => {
41
40
  "endpoint" => {
42
41
  "type" => "string",
43
- "description" => "The endpoint which should be subscribed to this notifier, typically an email address or SMS-enabled phone number."
44
- }
42
+ "description" => "Shorthand for an endpoint which should be subscribed to this notifier, typically an email address or SMS-enabled phone number. For complex cases, such as referencing an AWS Lambda function defined elsewhere in your Mu stack, use +resource+ instead."
43
+ },
44
+ "resource" => MU::Config::Ref.schema(desc: "A cloud resource that is a valid notification target for this notifier. For simple use cases, such as external email addresses or SMS, use +endpoint+ instead.")
45
45
  }
46
46
  }
47
47
  }
@@ -56,23 +56,12 @@ module MU
56
56
  def self.validate(notifier, _configurator)
57
57
  ok = true
58
58
 
59
+
59
60
  if notifier['subscriptions']
60
61
  notifier['subscriptions'].each { |sub|
61
- if !sub["type"]
62
- if sub["endpoint"].match(/^http:/i)
63
- sub["type"] = "http"
64
- elsif sub["endpoint"].match(/^https:/i)
65
- sub["type"] = "https"
66
- elsif sub["endpoint"].match(/^sqs:/i)
67
- sub["type"] = "sqs"
68
- elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
69
- sub["type"] = "sms"
70
- elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
71
- sub["type"] = "email"
72
- else
73
- MU.log "Notifier #{notifier['name']} subscription #{sub['endpoint']} did not specify a type, and I'm unable to guess one", MU::ERR
74
- ok = false
75
- end
62
+ if !sub['endpoint'] and !sub['resource']
63
+ MU.log "Notifier '#{notifier['name']}' must specify either resource or endpoint in subscription", MU::ERR, details: sub
64
+ ok = false
76
65
  end
77
66
  }
78
67
  end
@@ -121,6 +121,10 @@ module MU
121
121
  @deploy_id = @mommacat.deploy_id
122
122
  end
123
123
 
124
+ # canonicalize the 'type' argument
125
+ _shortclass, _cfg_name, cfg_plural, _classname, _attrs = MU::Cloud.getResourceNames(@type, false)
126
+ @type = cfg_plural if cfg_plural
127
+
124
128
  kitten(shallow: true) if @mommacat # try to populate the actual cloud object for this
125
129
  end
126
130
 
@@ -140,6 +144,13 @@ module MU
140
144
  end
141
145
  end
142
146
 
147
+ # Lets callers set attributes like a {Hash}
148
+ # @param attribute [String,Symbol]
149
+ def []=(attribute, value)
150
+ instance_variable_set("@#{attribute.to_s}".to_sym, value)
151
+ self.class.define_reader(attribute)
152
+ end
153
+
143
154
  # Unset an attribute. Sort of. We can't actually do that, so nil it out
144
155
  # and we get the behavior we want.
145
156
  def delete(attribute)
@@ -150,7 +161,7 @@ module MU
150
161
  # Base configuration schema for declared kittens referencing other cloud objects. This is essentially a set of filters that we're going to pass to {MU::MommaCat.findStray}.
151
162
  # @param aliases [Array<Hash>]: Key => value mappings to set backwards-compatibility aliases for attributes, such as the ubiquitous +vpc_id+ (+vpc_id+ => +id+).
152
163
  # @return [Hash]
153
- def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil, omit_fields: [])
164
+ def self.schema(aliases = [], type: nil, parent_obj: nil, desc: nil, omit_fields: [], any_type: false)
154
165
  parent_obj ||= caller[1].gsub(/.*?\/([^\.\/]+)\.rb:.*/, '\1')
155
166
  desc ||= "Reference a #{type ? "'#{type}' resource" : "resource" } from this #{parent_obj ? "'#{parent_obj}'" : "" } resource"
156
167
  schema = {
@@ -205,7 +216,9 @@ module MU
205
216
  }
206
217
  end
207
218
 
208
- if !type.nil?
219
+ if any_type
220
+ schema["properties"]["type"].delete("enum")
221
+ elsif !type.nil?
209
222
  schema["required"] = ["type"]
210
223
  schema["properties"]["type"]["default"] = type
211
224
  schema["properties"]["type"]["enum"] = [type]
@@ -225,6 +238,13 @@ module MU
225
238
  schema
226
239
  end
227
240
 
241
+ # Is our +@type+ attribute a Mu-supported type, or some rando string?
242
+ # @return [Boolean]
243
+ def is_mu_type?
244
+ _shortclass, _cfg_name, type, _classname, _attrs = MU::Cloud.getResourceNames(@type, false)
245
+ !type.nil?
246
+ end
247
+
228
248
  # Decompose into a plain-jane {MU::Config::BasketOfKittens} hash fragment,
229
249
  # of the sort that would have been used to declare this reference in the
230
250
  # first place.
@@ -266,10 +286,23 @@ module MU
266
286
  # called in a live deploy, which is to say that if called during initial
267
287
  # configuration parsing, results may be incorrect.
268
288
  # @param mommacat [MU::MommaCat]: A deploy object which will be searched for the referenced resource if provided, before restoring to broader, less efficient searches.
269
- def kitten(mommacat = @mommacat, shallow: false, debug: false)
270
- return nil if !@cloud or !@type
289
+ def kitten(mommacat = @mommacat, shallow: false, debug: false, cloud: nil)
290
+ cloud ||= @cloud
291
+ return nil if !cloud or !@type
292
+
293
+ _shortclass, _cfg_name, cfg_plural, _classname, _attrs = MU::Cloud.getResourceNames(@type, false)
294
+ if cfg_plural
295
+ @type = cfg_plural # make sure this is the thing we expect
296
+ else
297
+ return nil # we don't do non-muish resources
298
+ end
299
+
271
300
  loglevel = debug ? MU::NOTICE : MU::DEBUG
272
301
 
302
+ if debug
303
+ MU.log "this mf kitten", MU::WARN, details: caller
304
+ end
305
+
273
306
  if @obj
274
307
  @deploy_id ||= @obj.deploy_id
275
308
  @id ||= @obj.cloud_id
@@ -277,7 +310,7 @@ module MU
277
310
  return @obj
278
311
  end
279
312
 
280
- if mommacat and !caller.grep(/`findLitterMate'/) # XXX the dumbest
313
+ if mommacat and caller.grep(/`findLitterMate'/).empty? # XXX the dumbest
281
314
  MU.log "Looking for #{@type} #{@name} #{@id} in deploy #{mommacat.deploy_id}", loglevel
282
315
  begin
283
316
  @obj = mommacat.findLitterMate(type: @type, name: @name, cloud_id: @id, credentials: @credentials, debug: debug)
@@ -309,7 +342,7 @@ end
309
342
  end
310
343
  end
311
344
 
312
- if !@obj and !(@cloud == "Google" and @id and @type == "users" and MU::Cloud.resourceClass("Google", "User").cannedServiceAcctName?(@id)) and !shallow
345
+ if !@obj and !(cloud == "Google" and @id and @type == "users" and MU::Cloud.resourceClass("Google", "User").cannedServiceAcctName?(@id)) and !shallow
313
346
  try_deploy_id = @deploy_id
314
347
 
315
348
  begin
@@ -323,8 +356,20 @@ end
323
356
  [@habitat.to_s]
324
357
  end
325
358
 
359
+ MU.log "Ref#kitten calling findStray", loglevel, details: {
360
+ cloud: cloud,
361
+ type: @type,
362
+ name: @name,
363
+ cloud_id: @id,
364
+ deploy_id: try_deploy_id,
365
+ region: @region,
366
+ habitats: hab_arg,
367
+ credentials: @credentials,
368
+ dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type) or @id)
369
+ }
370
+
326
371
  found = MU::MommaCat.findStray(
327
- @cloud,
372
+ cloud,
328
373
  @type,
329
374
  name: @name,
330
375
  cloud_id: @id,
@@ -332,12 +377,13 @@ end
332
377
  region: @region,
333
378
  habitats: hab_arg,
334
379
  credentials: @credentials,
335
- dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type))
380
+ dummy_ok: (["habitats", "folders", "users", "groups", "vpcs"].include?(@type) or @id)
336
381
  )
382
+ MU.log "Ref#kitten results from findStray", loglevel, details: found
337
383
  @obj ||= found.first if found
338
384
  rescue MU::MommaCat::MultipleMatches => e
339
385
  if try_deploy_id.nil? and MU.deploy_id
340
- MU.log "Attempting to narrow down #{@cloud} #{@type} to #{MU.deploy_id}", MU::NOTICE
386
+ MU.log "Attempting to narrow down #{cloud} #{@type} to #{MU.deploy_id}", MU::NOTICE
341
387
  try_deploy_id = MU.deploy_id
342
388
  retry
343
389
  else