cloud-mu 3.1.4 → 3.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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'])
|