cloud-mu 3.1.4 → 3.1.5
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/ansible/roles/mu-windows/README.md +33 -0
- data/ansible/roles/mu-windows/defaults/main.yml +2 -0
- data/ansible/roles/mu-windows/handlers/main.yml +2 -0
- data/ansible/roles/mu-windows/meta/main.yml +53 -0
- data/ansible/roles/mu-windows/tasks/main.yml +20 -0
- data/ansible/roles/mu-windows/tests/inventory +2 -0
- data/ansible/roles/mu-windows/tests/test.yml +5 -0
- data/ansible/roles/mu-windows/vars/main.yml +2 -0
- data/cloud-mu.gemspec +4 -2
- data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +140 -144
- data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
- data/extras/image-generators/AWS/win2k12.yaml +16 -13
- data/extras/image-generators/AWS/win2k16.yaml +16 -13
- data/extras/image-generators/AWS/win2k19.yaml +19 -0
- data/modules/mu.rb +72 -9
- data/modules/mu/adoption.rb +14 -2
- data/modules/mu/cloud.rb +111 -10
- data/modules/mu/clouds/aws.rb +23 -7
- data/modules/mu/clouds/aws/container_cluster.rb +640 -692
- data/modules/mu/clouds/aws/dnszone.rb +49 -45
- data/modules/mu/clouds/aws/firewall_rule.rb +177 -214
- data/modules/mu/clouds/aws/role.rb +17 -8
- data/modules/mu/clouds/aws/search_domain.rb +1 -1
- data/modules/mu/clouds/aws/server.rb +734 -1027
- data/modules/mu/clouds/aws/userdata/windows.erb +2 -1
- data/modules/mu/clouds/aws/vpc.rb +297 -786
- data/modules/mu/clouds/aws/vpc_subnet.rb +286 -0
- data/modules/mu/clouds/google/bucket.rb +1 -1
- data/modules/mu/clouds/google/container_cluster.rb +21 -17
- data/modules/mu/clouds/google/function.rb +8 -2
- data/modules/mu/clouds/google/server.rb +102 -32
- data/modules/mu/clouds/google/vpc.rb +1 -1
- data/modules/mu/config.rb +12 -1
- data/modules/mu/config/server.yml +1 -0
- data/modules/mu/defaults/AWS.yaml +51 -28
- data/modules/mu/groomers/ansible.rb +54 -17
- data/modules/mu/groomers/chef.rb +13 -7
- data/modules/mu/master/ssl.rb +0 -1
- data/modules/mu/mommacat.rb +8 -0
- data/modules/tests/ecs.yaml +23 -0
- data/modules/tests/includes-and-params.yaml +2 -1
- data/modules/tests/server-with-scrub-muisms.yaml +1 -0
- data/modules/tests/win2k12.yaml +25 -0
- data/modules/tests/win2k16.yaml +25 -0
- data/modules/tests/win2k19.yaml +25 -0
- data/requirements.txt +1 -0
- metadata +50 -4
- data/extras/image-generators/AWS/windows.yaml +0 -18
- data/modules/tests/needwork/win2k12.yaml +0 -13
@@ -0,0 +1,286 @@
|
|
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
|
+
|
19
|
+
# Creation of Virtual Private Clouds and associated artifacts (routes, subnets, etc).
|
20
|
+
class VPC < MU::Cloud::VPC
|
21
|
+
|
22
|
+
# Subnets are almost a first-class resource. So let's kinda sorta treat
|
23
|
+
# them like one. This should only be invoked on objects that already
|
24
|
+
# exists in the cloud layer.
|
25
|
+
class Subnet < MU::Cloud::AWS::VPC
|
26
|
+
|
27
|
+
attr_reader :cloud_id
|
28
|
+
attr_reader :ip_block
|
29
|
+
attr_reader :mu_name
|
30
|
+
attr_reader :name
|
31
|
+
attr_reader :az
|
32
|
+
attr_reader :cloud_desc
|
33
|
+
|
34
|
+
# @param parent [MU::Cloud::AWS::VPC]: The parent VPC of this subnet.
|
35
|
+
# @param config [Hash<String>]:
|
36
|
+
def initialize(parent, config)
|
37
|
+
@parent = parent
|
38
|
+
@config = MU::Config.manxify(config)
|
39
|
+
@cloud_id = config['cloud_id']
|
40
|
+
@mu_name = config['mu_name']
|
41
|
+
@name = config['name']
|
42
|
+
@deploydata = config # This is a dummy for the sake of describe()
|
43
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_subnets(subnet_ids: [@cloud_id]).subnets.first
|
44
|
+
@az = resp.availability_zone
|
45
|
+
@ip_block = resp.cidr_block
|
46
|
+
@cloud_desc = resp # XXX this really isn't the cloud implementation's business
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return the cloud identifier for the default route of this subnet.
|
51
|
+
# @return [String,nil]
|
52
|
+
def defaultRoute
|
53
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_route_tables(
|
54
|
+
filters: [{name: "association.subnet-id", values: [@cloud_id]}]
|
55
|
+
)
|
56
|
+
if resp.route_tables.size == 0 # use default route table for the VPC
|
57
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_route_tables(
|
58
|
+
filters: [{name: "vpc-id", values: [@parent.cloud_id]}]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
resp.route_tables.each { |route_table|
|
62
|
+
route_table.routes.each { |route|
|
63
|
+
if route.destination_cidr_block =="0.0.0.0/0" and route.state != "blackhole"
|
64
|
+
return route.instance_id if !route.instance_id.nil?
|
65
|
+
return route.gateway_id if !route.gateway_id.nil?
|
66
|
+
return route.vpc_peering_connection_id if !route.vpc_peering_connection_id.nil?
|
67
|
+
return route.network_interface_id if !route.network_interface_id.nil?
|
68
|
+
end
|
69
|
+
}
|
70
|
+
}
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Is this subnet privately-routable only, or public?
|
75
|
+
# @return [Boolean]
|
76
|
+
def private?
|
77
|
+
return false if @cloud_id.nil?
|
78
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_route_tables(
|
79
|
+
filters: [{name: "association.subnet-id", values: [@cloud_id]}]
|
80
|
+
)
|
81
|
+
if resp.route_tables.size == 0 # use default route table for the VPC
|
82
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_route_tables(
|
83
|
+
filters: [{name: "vpc-id", values: [@parent.cloud_id]}]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
resp.route_tables.each { |route_table|
|
87
|
+
route_table.routes.each { |route|
|
88
|
+
return false if !route.gateway_id.nil? and route.gateway_id != "local" # you can have an IgW and route it to a subset of IPs instead of 0.0.0.0/0
|
89
|
+
if route.destination_cidr_block == "0.0.0.0/0"
|
90
|
+
return true if !route.instance_id.nil?
|
91
|
+
return true if route.nat_gateway_id
|
92
|
+
end
|
93
|
+
}
|
94
|
+
}
|
95
|
+
return true
|
96
|
+
end
|
97
|
+
end # VPC::Subnet class
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def create_subnets
|
102
|
+
return [] if @config['subnets'].nil? or @config['subnets'].empty?
|
103
|
+
nat_gateways = []
|
104
|
+
|
105
|
+
@eip_allocation_ids ||= []
|
106
|
+
|
107
|
+
subnetthreads = Array.new
|
108
|
+
|
109
|
+
azs = MU::Cloud::AWS.listAZs(region: @config['region'], credentials: @config['credentials'])
|
110
|
+
@config['subnets'].each { |subnet|
|
111
|
+
subnet_name = @config['name']+"-"+subnet['name']
|
112
|
+
az = subnet['availability_zone'] ? subnet['availability_zone'] : azs.op
|
113
|
+
MU.log "Creating Subnet #{subnet_name} (#{subnet['ip_block']}) in #{az}", details: subnet
|
114
|
+
|
115
|
+
subnetthreads << Thread.new {
|
116
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_subnet(
|
117
|
+
vpc_id: @cloud_id,
|
118
|
+
cidr_block: subnet['ip_block'],
|
119
|
+
availability_zone: az
|
120
|
+
).subnet
|
121
|
+
subnet_id = subnet['subnet_id'] = resp.subnet_id
|
122
|
+
|
123
|
+
tag_me(subnet_id, @mu_name+"-"+subnet['name'])
|
124
|
+
|
125
|
+
loop_if = Proc.new {
|
126
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_subnets(subnet_ids: [subnet_id]).subnets.first
|
127
|
+
(!resp or resp.state != "available")
|
128
|
+
}
|
129
|
+
|
130
|
+
MU.retrier([Aws::EC2::Errors::InvalidSubnetIDNotFound, NoMethodError], wait: 5, loop_if: loop_if) { |retries, _wait|
|
131
|
+
MU.log "Waiting for Subnet #{subnet_name} (#{subnet_id}) to become available", MU::NOTICE if retries > 0 and (retries % 3) == 0
|
132
|
+
}
|
133
|
+
|
134
|
+
if !subnet['route_table'].nil?
|
135
|
+
routes = {}
|
136
|
+
@config['route_tables'].each { |tbl|
|
137
|
+
routes[tbl['name']] = tbl
|
138
|
+
}
|
139
|
+
if routes[subnet['route_table']].nil?
|
140
|
+
raise "Subnet #{subnet_name} references nonexistent route #{subnet['route_table']}"
|
141
|
+
end
|
142
|
+
MU.log "Associating Route Table '#{subnet['route_table']}' (#{routes[subnet['route_table']]['route_table_id']}) with #{subnet_name}"
|
143
|
+
MU.retrier([Aws::EC2::Errors::InvalidRouteTableIDNotFound], wait: 10, max: 10) {
|
144
|
+
MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).associate_route_table(
|
145
|
+
route_table_id: routes[subnet['route_table']]['route_table_id'],
|
146
|
+
subnet_id: subnet_id
|
147
|
+
)
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
if subnet.has_key?("map_public_ips")
|
152
|
+
MU.retrier([Aws::EC2::Errors::InvalidSubnetIDNotFound], wait: 10, max: 10) {
|
153
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_subnet_attribute(
|
154
|
+
subnet_id: subnet_id,
|
155
|
+
map_public_ip_on_launch: {
|
156
|
+
value: subnet['map_public_ips'],
|
157
|
+
}
|
158
|
+
)
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
if subnet['is_public'] and subnet['create_nat_gateway']
|
163
|
+
nat_gateways << create_nat_gateway(subnet)
|
164
|
+
end
|
165
|
+
|
166
|
+
if subnet["enable_traffic_logging"]
|
167
|
+
loggroup = @deploy.findLitterMate(name: @config['name']+"loggroup", type: "logs")
|
168
|
+
logrole = @deploy.findLitterMate(name: @config['name']+"logrole", type: "roles")
|
169
|
+
MU.log "Enabling traffic logging on Subnet #{subnet_name} in VPC #{@mu_name} to log group #{loggroup.mu_name}"
|
170
|
+
MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_flow_logs(
|
171
|
+
resource_ids: [subnet_id],
|
172
|
+
resource_type: "Subnet",
|
173
|
+
traffic_type: subnet["traffic_type_to_log"],
|
174
|
+
log_group_name: loggroup.mu_name,
|
175
|
+
deliver_logs_permission_arn: logrole.cloudobj.arn
|
176
|
+
)
|
177
|
+
end
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
subnetthreads.each { |t|
|
182
|
+
t.join
|
183
|
+
}
|
184
|
+
|
185
|
+
nat_gateways
|
186
|
+
end
|
187
|
+
|
188
|
+
def allocate_eip_for_nat
|
189
|
+
MU::MommaCat.lock("nat-gateway-eipalloc")
|
190
|
+
|
191
|
+
eips = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_addresses(
|
192
|
+
filters: [
|
193
|
+
{
|
194
|
+
name: "domain",
|
195
|
+
values: ["vpc"]
|
196
|
+
}
|
197
|
+
]
|
198
|
+
).addresses
|
199
|
+
|
200
|
+
allocation_id = nil
|
201
|
+
eips.each { |eip|
|
202
|
+
next if !eip.association_id.nil? and !eip.association_id.empty?
|
203
|
+
if (eip.private_ip_address.nil? || eip.private_ip_address.empty?) and MU::MommaCat.lock(eip.allocation_id, true, true)
|
204
|
+
if !@eip_allocation_ids.include?(eip.allocation_id)
|
205
|
+
allocation_id = eip.allocation_id
|
206
|
+
break
|
207
|
+
end
|
208
|
+
end
|
209
|
+
}
|
210
|
+
|
211
|
+
if allocation_id.nil?
|
212
|
+
allocation_id = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).allocate_address(domain: "vpc").allocation_id
|
213
|
+
MU::MommaCat.lock(allocation_id, false, true)
|
214
|
+
end
|
215
|
+
|
216
|
+
@eip_allocation_ids << allocation_id
|
217
|
+
|
218
|
+
MU::MommaCat.unlock("nat-gateway-eipalloc")
|
219
|
+
|
220
|
+
allocation_id
|
221
|
+
end
|
222
|
+
|
223
|
+
def create_nat_gateway(subnet)
|
224
|
+
allocation_id = allocate_eip_for_nat
|
225
|
+
|
226
|
+
nat_gateway_id = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_nat_gateway(
|
227
|
+
subnet_id: subnet['subnet_id'],
|
228
|
+
allocation_id: allocation_id,
|
229
|
+
).nat_gateway.nat_gateway_id
|
230
|
+
|
231
|
+
ensure_unlock = Proc.new { MU::MommaCat.unlock(allocation_id, true) }
|
232
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_nat_gateways(nat_gateway_ids: [nat_gateway_id]).nat_gateways.first
|
233
|
+
loop_if = Proc.new {
|
234
|
+
resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_nat_gateways(nat_gateway_ids: [nat_gateway_id]).nat_gateways.first
|
235
|
+
resp.class != Aws::EC2::Types::NatGateway or resp.state == "pending"
|
236
|
+
}
|
237
|
+
|
238
|
+
MU.retrier([Aws::EmptyStructure, NoMethodError], wait: 5, max: 30, always: ensure_unlock, loop_if: loop_if) { |retries, _wait|
|
239
|
+
MU.log "Waiting for nat gateway #{nat_gateway_id} to become available (EIP allocation: #{allocation_id})" if retries % 5 == 0
|
240
|
+
}
|
241
|
+
|
242
|
+
raise MuError, "NAT Gateway failed #{nat_gateway_id}: #{resp}" if resp.state == "failed"
|
243
|
+
|
244
|
+
tag_me(nat_gateway_id)
|
245
|
+
|
246
|
+
{'id' => nat_gateway_id, 'availability_zone' => subnet['availability_zone']}
|
247
|
+
end
|
248
|
+
|
249
|
+
# Remove all subnets associated with the currently loaded deployment.
|
250
|
+
# @param noop [Boolean]: If true, will only print what would be done
|
251
|
+
# @param tagfilters [Array<Hash>]: EC2 tags to filter against when search for resources to purge
|
252
|
+
# @param region [String]: The cloud provider region
|
253
|
+
# @return [void]
|
254
|
+
def self.purge_subnets(noop = false, tagfilters = [{name: "tag:MU-ID", values: [MU.deploy_id]}], region: MU.curRegion, credentials: nil)
|
255
|
+
resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_subnets(
|
256
|
+
filters: tagfilters
|
257
|
+
)
|
258
|
+
subnets = resp.data.subnets
|
259
|
+
|
260
|
+
return if subnets.nil? or subnets.size == 0
|
261
|
+
|
262
|
+
subnets.each { |subnet|
|
263
|
+
on_retry = Proc.new {
|
264
|
+
MU::Cloud::AWS::VPC.purge_interfaces(noop, [{name: "subnet-id", values: [subnet.subnet_id]}], region: region, credentials: credentials)
|
265
|
+
}
|
266
|
+
|
267
|
+
MU.log "Deleting Subnet #{subnet.subnet_id}"
|
268
|
+
MU.retrier([Aws::EC2::Errors::DependencyViolation], ignoreme: [Aws::EC2::Errors::InvalidSubnetIDNotFound], max: 20, on_retry: on_retry) { |_retries, wait|
|
269
|
+
begin
|
270
|
+
if subnet.state != "available"
|
271
|
+
MU.log "Waiting for #{subnet.subnet_id} to be in a removable state...", MU::NOTICE
|
272
|
+
sleep wait
|
273
|
+
else
|
274
|
+
MU::Cloud::AWS.ec2(credentials: credentials, region: region).delete_subnet(subnet_id: subnet.subnet_id) if !noop
|
275
|
+
end
|
276
|
+
end while subnet.state != "available"
|
277
|
+
}
|
278
|
+
}
|
279
|
+
end
|
280
|
+
private_class_method :purge_subnets
|
281
|
+
|
282
|
+
end # VPC class
|
283
|
+
|
284
|
+
end #class
|
285
|
+
end
|
286
|
+
end #module
|
@@ -305,7 +305,7 @@ module MU
|
|
305
305
|
schema = {
|
306
306
|
"storage_class" => {
|
307
307
|
"type" => "string",
|
308
|
-
"enum" => ["MULTI_REGIONAL", "REGIONAL", "STANDARD", "NEARLINE", "COLDLINE", "DURABLE_REDUCED_AVAILABILITY"],
|
308
|
+
"enum" => ["MULTI_REGIONAL", "REGIONAL", "STANDARD", "NEARLINE", "COLDLINE", "DURABLE_REDUCED_AVAILABILITY", "ARCHIVE"],
|
309
309
|
"default" => "STANDARD"
|
310
310
|
},
|
311
311
|
"bucket_wide_acls" => {
|
@@ -567,22 +567,24 @@ module MU
|
|
567
567
|
bok['kubernetes']['network_policy_addon'] = true
|
568
568
|
end
|
569
569
|
|
570
|
-
if cloud_desc.ip_allocation_policy
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
570
|
+
if cloud_desc.ip_allocation_policy
|
571
|
+
if cloud_desc.ip_allocation_policy.use_ip_aliases
|
572
|
+
bok['ip_aliases'] = true
|
573
|
+
end
|
574
|
+
if cloud_desc.ip_allocation_policy.cluster_ipv4_cidr_block
|
575
|
+
bok['pod_ip_block'] = cloud_desc.ip_allocation_policy.cluster_ipv4_cidr_block
|
576
|
+
end
|
577
|
+
if cloud_desc.ip_allocation_policy.services_ipv4_cidr_block
|
578
|
+
bok['services_ip_block'] = cloud_desc.ip_allocation_policy.services_ipv4_cidr_block
|
579
|
+
end
|
579
580
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
581
|
+
if cloud_desc.ip_allocation_policy.create_subnetwork
|
582
|
+
bok['custom_subnet'] = {
|
583
|
+
"name" => (cloud_desc.ip_allocation_policy.subnetwork_name || cloud_desc.subnetwork)
|
584
|
+
}
|
585
|
+
if cloud_desc.ip_allocation_policy.node_ipv4_cidr_block
|
586
|
+
bok['custom_subnet']['node_ip_block'] = cloud_desc.ip_allocation_policy.node_ipv4_cidr_block
|
587
|
+
end
|
586
588
|
end
|
587
589
|
end
|
588
590
|
|
@@ -1029,7 +1031,8 @@ module MU
|
|
1029
1031
|
type: "habitats"
|
1030
1032
|
)
|
1031
1033
|
if cluster['service_account']['name'] and
|
1032
|
-
!cluster['service_account']['id']
|
1034
|
+
!cluster['service_account']['id'] and
|
1035
|
+
!cluster['service_account']['deploy_id']
|
1033
1036
|
cluster['dependencies'] ||= []
|
1034
1037
|
cluster['dependencies'] << {
|
1035
1038
|
"type" => "user",
|
@@ -1188,7 +1191,8 @@ module MU
|
|
1188
1191
|
labels["name"] = MU::Cloud::Google.nameStr(@mu_name)
|
1189
1192
|
|
1190
1193
|
labelset = MU::Cloud::Google.container(:SetLabelsRequest).new(
|
1191
|
-
resource_labels: labels
|
1194
|
+
resource_labels: labels,
|
1195
|
+
label_fingerprint: cloud_desc.label_fingerprint
|
1192
1196
|
)
|
1193
1197
|
MU::Cloud::Google.container(credentials: @config['credentials']).set_project_location_cluster_resource_labels(@cloud_id, labelset)
|
1194
1198
|
end
|
@@ -340,7 +340,7 @@ module example.com/cloudfunction
|
|
340
340
|
end
|
341
341
|
|
342
342
|
codefile = bok["project"]+"_"+bok["region"]+"_"+bok["name"]+".zip"
|
343
|
-
MU::Cloud::Google::Function.downloadPackage(@cloud_id, codefile, credentials: @config['credentials'])
|
343
|
+
return nil if !MU::Cloud::Google::Function.downloadPackage(@cloud_id, codefile, credentials: @config['credentials'])
|
344
344
|
bok['code'] = {
|
345
345
|
'zip_file' => codefile
|
346
346
|
}
|
@@ -417,7 +417,12 @@ module example.com/cloudfunction
|
|
417
417
|
bucket = Regexp.last_match[1]
|
418
418
|
path = Regexp.last_match[2]
|
419
419
|
|
420
|
-
|
420
|
+
begin
|
421
|
+
MU::Cloud::Google.storage(credentials: credentials).get_object(bucket, path, download_dest: zipfile)
|
422
|
+
rescue ::Google::Apis::ClientError => e
|
423
|
+
MU.log "Couldn't retrieve gs://#{bucket}/#{path} for #{function_id}", MU::WARN, details: e.inspect
|
424
|
+
return false
|
425
|
+
end
|
421
426
|
elsif cloud_desc.source_upload_url
|
422
427
|
resp = MU::Cloud::Google.function(credentials: credentials).generate_function_download_url(
|
423
428
|
function_id
|
@@ -428,6 +433,7 @@ module example.com/cloudfunction
|
|
428
433
|
f.close
|
429
434
|
end
|
430
435
|
end
|
436
|
+
true
|
431
437
|
end
|
432
438
|
|
433
439
|
# Upload a zipfile to our admin Cloud Storage bucket, for use by
|
@@ -164,19 +164,26 @@ module MU
|
|
164
164
|
def self.diskConfig(config, create = true, disk_as_url = true, credentials: nil)
|
165
165
|
disks = []
|
166
166
|
if config['image_id'].nil? and config['basis'].nil?
|
167
|
-
pp config.keys
|
168
167
|
raise MuError, "Can't generate disk configuration for server #{config['name']} without an image ID or basis specified"
|
169
168
|
end
|
170
169
|
|
171
170
|
img = fetchImage(config['image_id'] || config['basis']['launch_config']['image_id'], credentials: credentials)
|
172
171
|
|
173
|
-
#
|
172
|
+
# if img.source_disk and img.source_disk.match(/projects\/([^\/]+)\/zones\/([^\/]+)\/disks\/(.*)/)
|
173
|
+
# _junk, proj, az, name = Regexp.last_match
|
174
|
+
# disk_desc = MU::Cloud::Google.compute(credentials: credentials).get_disk(proj, az, name)
|
175
|
+
# pp disk_desc
|
176
|
+
# raise "nah"
|
177
|
+
# end
|
178
|
+
|
174
179
|
disktype = "projects/#{config['project']}/zones/#{config['availability_zone']}/diskTypes/pd-standard"
|
175
|
-
|
176
|
-
|
180
|
+
|
181
|
+
|
182
|
+
disktype.gsub!(/.*?\/([^\/])$/, '\1') if !disk_as_url
|
183
|
+
|
177
184
|
imageobj = MU::Cloud::Google.compute(:AttachedDiskInitializeParams).new(
|
178
185
|
source_image: img.self_link,
|
179
|
-
disk_size_gb:
|
186
|
+
disk_size_gb: img.disk_size_gb,
|
180
187
|
disk_type: disktype,
|
181
188
|
)
|
182
189
|
disks << MU::Cloud::Google.compute(:AttachedDisk).new(
|
@@ -329,7 +336,7 @@ next if !create
|
|
329
336
|
end
|
330
337
|
metadata["startup-script"] = @userdata if @userdata and !@userdata.empty?
|
331
338
|
|
332
|
-
deploykey = @config[
|
339
|
+
deploykey = @config["ssh_user"]+":"+@deploy.ssh_public_key
|
333
340
|
if metadata["ssh-keys"]
|
334
341
|
metadata["ssh-keys"] += "\n"+deploykey
|
335
342
|
else
|
@@ -502,7 +509,8 @@ next if !create
|
|
502
509
|
|
503
510
|
if @config['ssh_user'].nil?
|
504
511
|
if windows?
|
505
|
-
@config['ssh_user'] =
|
512
|
+
@config['ssh_user'] = @config['windows_admin_user']
|
513
|
+
@config['ssh_user'] ||= "Administrator"
|
506
514
|
else
|
507
515
|
@config['ssh_user'] = "root"
|
508
516
|
end
|
@@ -800,29 +808,6 @@ next if !create
|
|
800
808
|
|
801
809
|
# punchAdminNAT
|
802
810
|
|
803
|
-
# MU::Cloud::AWS::Server.tagVolumes(@cloud_id)
|
804
|
-
|
805
|
-
# If we have a loadbalancer configured, attach us to it
|
806
|
-
# if !@config['loadbalancers'].nil?
|
807
|
-
# if @loadbalancers.nil?
|
808
|
-
# raise MuError, "#{@mu_name} is configured to use LoadBalancers, but none have been loaded by dependencies()"
|
809
|
-
# end
|
810
|
-
# @loadbalancers.each { |lb|
|
811
|
-
# lb.registerNode(@cloud_id)
|
812
|
-
# }
|
813
|
-
# end
|
814
|
-
|
815
|
-
# Let us into any databases we depend on.
|
816
|
-
# This is probelmtic with autscaling - old ips are not removed, and access to the database can easily be given at the BoK level
|
817
|
-
# if @dependencies.has_key?("database")
|
818
|
-
# @dependencies['database'].values.each { |db|
|
819
|
-
# db.allowHost(@deploydata["private_ip_address"]+"/32")
|
820
|
-
# if @deploydata["public_ip_address"]
|
821
|
-
# db.allowHost(@deploydata["public_ip_address"]+"/32")
|
822
|
-
# end
|
823
|
-
# }
|
824
|
-
# end
|
825
|
-
|
826
811
|
@groomer.saveDeployData
|
827
812
|
|
828
813
|
begin
|
@@ -1017,9 +1002,93 @@ next if !create
|
|
1017
1002
|
end
|
1018
1003
|
|
1019
1004
|
# return [String]: A password string.
|
1020
|
-
def getWindowsAdminPassword
|
1005
|
+
def getWindowsAdminPassword(use_cache: true)
|
1006
|
+
@config['windows_auth_vault'] ||= {
|
1007
|
+
"vault" => @mu_name,
|
1008
|
+
"item" => "windows_credentials",
|
1009
|
+
"password_field" => "password"
|
1010
|
+
}
|
1011
|
+
|
1012
|
+
if use_cache
|
1013
|
+
begin
|
1014
|
+
win_admin_password = @groomer.getSecret(
|
1015
|
+
vault: @config['windows_auth_vault']['vault'],
|
1016
|
+
item: @config['windows_auth_vault']['item'],
|
1017
|
+
field: @config["windows_auth_vault"]["password_field"]
|
1018
|
+
)
|
1019
|
+
MU.log "RETURNINATING FROM CACHE", MU::WARN, details: win_admin_password
|
1020
|
+
return win_admin_password if win_admin_password
|
1021
|
+
rescue MU::Groomer::MuNoSuchSecret, MU::Groomer::RunError
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
require 'openssl/oaep'
|
1026
|
+
timeout = 300
|
1027
|
+
|
1028
|
+
serial_out = nil
|
1029
|
+
key = OpenSSL::PKey::RSA.generate 2048
|
1030
|
+
|
1031
|
+
missing_response = Proc.new {
|
1032
|
+
!serial_out or !serial_out.contents or serial_out.contents.empty? or JSON.parse(serial_out.contents)["userName"] != @config['windows_admin_username']
|
1033
|
+
}
|
1034
|
+
|
1035
|
+
did_metadata = false
|
1036
|
+
MU.retrier(loop_if: missing_response, wait: 10, max: timeout/10) {
|
1037
|
+
serial_out = MU::Cloud::Google.compute(credentials: @credentials).get_instance_serial_port_output(@project_id, @config['availability_zone'], @cloud_id, port: 4)
|
1038
|
+
|
1039
|
+
if missing_response.call and
|
1040
|
+
!cloud_desc(use_cache: false).metadata.items.map { |i| i.key }.include?("windows-keys")
|
1041
|
+
keybytes = Base64.decode64(key.public_key.export.gsub(/-----(?:BEGIN|END) PUBLIC KEY-----/, ''))
|
1042
|
+
modulus = keybytes.byteslice(33,256)
|
1043
|
+
exponent = keybytes.byteslice(291,3)
|
1044
|
+
keydata = {
|
1045
|
+
"userName" => @config['windows_admin_username'],
|
1046
|
+
"modulus" => Base64.strict_encode64(modulus),
|
1047
|
+
"exponent" => Base64.strict_encode64(exponent),
|
1048
|
+
"email" => MU.muCfg['mu_admin_email'],
|
1049
|
+
"expireOn" => (Time.now.utc+timeout).strftime('%Y-%m-%dT%H:%M:%SZ')
|
1050
|
+
}
|
1051
|
+
|
1052
|
+
new_items = cloud_desc.metadata.items.map { |item|
|
1053
|
+
MU::Cloud::Google.compute(:Metadata)::Item.new(
|
1054
|
+
key: item.key,
|
1055
|
+
value: item.value
|
1056
|
+
)
|
1057
|
+
}
|
1058
|
+
new_items.reject! { |item| item.key == "windows-keys" }
|
1059
|
+
new_items << MU::Cloud::Google.compute(:Metadata)::Item.new(
|
1060
|
+
key: "windows-keys",
|
1061
|
+
value: JSON.generate(keydata)
|
1062
|
+
)
|
1063
|
+
new_metadata = MU::Cloud::Google.compute(:Metadata).new(
|
1064
|
+
fingerprint: cloud_desc(use_cache: false).metadata.fingerprint,
|
1065
|
+
items: new_items
|
1066
|
+
)
|
1067
|
+
|
1068
|
+
MU::Cloud::Google.compute(credentials: @credentials).set_instance_metadata(@project_id, @config['availability_zone'], @cloud_id, new_metadata)
|
1069
|
+
end
|
1070
|
+
}
|
1071
|
+
|
1072
|
+
return nil if missing_response.call
|
1073
|
+
|
1074
|
+
pwdata = JSON.parse(serial_out.contents)
|
1075
|
+
if pwdata['encryptedPassword'] and pwdata['userName'] == @config['windows_admin_username']
|
1076
|
+
decrypted_pw = key.private_decrypt_oaep(Base64.strict_decode64(pwdata['encryptedPassword']))
|
1077
|
+
creds = {
|
1078
|
+
"username" => @config['windows_admin_username'],
|
1079
|
+
"password" => decrypted_pw,
|
1080
|
+
"sshd_username" => "sshd_service",
|
1081
|
+
"sshd_password" => decrypted_pw
|
1082
|
+
}
|
1083
|
+
@groomer.saveSecret(vault: @mu_name, item: "windows_credentials", data: creds, permissions: "name:#{@mu_name}")
|
1084
|
+
|
1085
|
+
return decrypted_pw
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
nil
|
1021
1089
|
end
|
1022
1090
|
|
1091
|
+
|
1023
1092
|
# Add a volume to this instance
|
1024
1093
|
# @param dev [String]: Device name to use when attaching to instance
|
1025
1094
|
# @param size [String]: Size (in gb) of the new volume
|
@@ -1272,7 +1341,7 @@ next if !create
|
|
1272
1341
|
"roles" => MU::Cloud::Google::User.schema(config)[1]["roles"],
|
1273
1342
|
"windows_admin_username" => {
|
1274
1343
|
"type" => "string",
|
1275
|
-
"default" => "
|
1344
|
+
"default" => "muadmin"
|
1276
1345
|
},
|
1277
1346
|
"create_image" => {
|
1278
1347
|
"properties" => {
|
@@ -1438,6 +1507,7 @@ next if !create
|
|
1438
1507
|
end
|
1439
1508
|
|
1440
1509
|
if server['service_account']
|
1510
|
+
server['service_account'] = server['service_account'].to_h
|
1441
1511
|
server['service_account']['cloud'] = "Google"
|
1442
1512
|
server['service_account']['habitat'] ||= server['project']
|
1443
1513
|
found = MU::Config::Ref.get(server['service_account'])
|