wombat-cli 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +23 -23
  3. data/.travis.yml +22 -27
  4. data/CHANGELOG.md +438 -423
  5. data/DESIGN.md +49 -49
  6. data/Gemfile +5 -5
  7. data/README.md +146 -146
  8. data/Rakefile +26 -26
  9. data/bin/wombat +24 -24
  10. data/generator_files/Vagrantfile +120 -120
  11. data/generator_files/cookbooks/automate/.gitignore +16 -16
  12. data/generator_files/cookbooks/automate/.kitchen.ec2.yml +34 -34
  13. data/generator_files/cookbooks/automate/.kitchen.yml +24 -24
  14. data/generator_files/cookbooks/automate/Berksfile +6 -6
  15. data/generator_files/cookbooks/automate/README.md +4 -4
  16. data/generator_files/cookbooks/automate/chefignore +102 -102
  17. data/generator_files/cookbooks/automate/libraries/_helper.rb +52 -52
  18. data/generator_files/cookbooks/automate/libraries/delivery_api.rb +204 -204
  19. data/generator_files/cookbooks/automate/libraries/delivery_project.rb +31 -31
  20. data/generator_files/cookbooks/automate/libraries/dsl.rb +4 -4
  21. data/generator_files/cookbooks/automate/metadata.rb +11 -11
  22. data/generator_files/cookbooks/automate/recipes/default.rb +118 -124
  23. data/generator_files/cookbooks/automate/recipes/update-users.rb +48 -48
  24. data/generator_files/cookbooks/automate/templates/delivery.erb +6 -6
  25. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -26
  26. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/automate.key +27 -27
  27. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -25
  28. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/chef.key +27 -27
  29. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -26
  30. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -27
  31. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/private.pem +27 -27
  32. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/public.pub +1 -1
  33. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/metadata.rb +3 -3
  34. data/generator_files/cookbooks/automate/test/fixtures/cookbooks/mock_data/recipes/default.rb +27 -27
  35. data/generator_files/cookbooks/automate/test/integration/default/automate_spec.rb +56 -56
  36. data/generator_files/cookbooks/build_node/.gitignore +16 -16
  37. data/generator_files/cookbooks/build_node/.kitchen.ec2.yml +37 -37
  38. data/generator_files/cookbooks/build_node/.kitchen.yml +23 -23
  39. data/generator_files/cookbooks/build_node/Berksfile +8 -8
  40. data/generator_files/cookbooks/build_node/README.md +4 -4
  41. data/generator_files/cookbooks/build_node/chefignore +102 -102
  42. data/generator_files/cookbooks/build_node/metadata.rb +12 -12
  43. data/generator_files/cookbooks/build_node/recipes/default.rb +38 -38
  44. data/generator_files/cookbooks/build_node/templates/client.erb +2 -2
  45. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -26
  46. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/automate.key +27 -27
  47. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -25
  48. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/chef.key +27 -27
  49. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -26
  50. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -27
  51. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/private.pem +27 -27
  52. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/public.pub +1 -1
  53. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/metadata.rb +2 -2
  54. data/generator_files/cookbooks/build_node/test/fixtures/cookbooks/mock_data/recipes/default.rb +18 -18
  55. data/generator_files/cookbooks/build_node/test/integration/default/build-node_spec.rb +40 -40
  56. data/generator_files/cookbooks/chef_server/.gitignore +16 -16
  57. data/generator_files/cookbooks/chef_server/.kitchen.ec2.yml +34 -34
  58. data/generator_files/cookbooks/chef_server/.kitchen.yml +24 -24
  59. data/generator_files/cookbooks/chef_server/Berksfile +6 -6
  60. data/generator_files/cookbooks/chef_server/README.md +4 -4
  61. data/generator_files/cookbooks/chef_server/chefignore +102 -102
  62. data/generator_files/cookbooks/chef_server/metadata.rb +11 -11
  63. data/generator_files/cookbooks/chef_server/recipes/bootstrap_users.rb +91 -91
  64. data/generator_files/cookbooks/chef_server/recipes/default.rb +113 -113
  65. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -26
  66. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/automate.key +27 -27
  67. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -25
  68. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/chef.key +27 -27
  69. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -26
  70. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -27
  71. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/private.pem +27 -27
  72. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/public.pub +1 -1
  73. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/metadata.rb +2 -2
  74. data/generator_files/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/recipes/default.rb +23 -23
  75. data/generator_files/cookbooks/chef_server/test/integration/default/chef_server_spec.rb +50 -50
  76. data/generator_files/cookbooks/compliance/.gitignore +16 -16
  77. data/generator_files/cookbooks/compliance/.kitchen.ec2.yml +34 -34
  78. data/generator_files/cookbooks/compliance/.kitchen.yml +24 -24
  79. data/generator_files/cookbooks/compliance/Berksfile +7 -7
  80. data/generator_files/cookbooks/compliance/README.md +4 -4
  81. data/generator_files/cookbooks/compliance/chefignore +102 -102
  82. data/generator_files/cookbooks/compliance/metadata.rb +11 -11
  83. data/generator_files/cookbooks/compliance/recipes/default.rb +57 -57
  84. data/generator_files/cookbooks/compliance/spec/spec_helper.rb +2 -2
  85. data/generator_files/cookbooks/compliance/spec/unit/recipes/default_spec.rb +20 -20
  86. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -26
  87. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/automate.key +27 -27
  88. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -25
  89. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/chef.key +27 -27
  90. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -26
  91. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -27
  92. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/private.pem +27 -27
  93. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/public.pub +1 -1
  94. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/metadata.rb +4 -4
  95. data/generator_files/cookbooks/compliance/test/fixtures/cookbooks/mock_data/recipes/default.rb +21 -21
  96. data/generator_files/cookbooks/compliance/test/integration/default/compliance.rb +28 -28
  97. data/generator_files/cookbooks/infranodes/.gitignore +16 -16
  98. data/generator_files/cookbooks/infranodes/.kitchen.ec2.yml +48 -48
  99. data/generator_files/cookbooks/infranodes/.kitchen.yml +21 -21
  100. data/generator_files/cookbooks/infranodes/Berksfile +6 -6
  101. data/generator_files/cookbooks/infranodes/README.md +4 -4
  102. data/generator_files/cookbooks/infranodes/attributes/default.rb +2 -2
  103. data/generator_files/cookbooks/infranodes/chefignore +102 -102
  104. data/generator_files/cookbooks/infranodes/metadata.rb +13 -13
  105. data/generator_files/cookbooks/infranodes/recipes/default.rb +57 -57
  106. data/generator_files/cookbooks/infranodes/spec/spec_helper.rb +2 -2
  107. data/generator_files/cookbooks/infranodes/spec/unit/recipes/default_spec.rb +20 -20
  108. data/generator_files/cookbooks/infranodes/templates/default/client.rb.erb +5 -5
  109. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -26
  110. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/automate.key +27 -27
  111. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -25
  112. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/chef.key +27 -27
  113. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -26
  114. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -27
  115. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/private.pem +27 -27
  116. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/public.pub +1 -1
  117. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/metadata.rb +3 -3
  118. data/generator_files/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/recipes/default.rb +27 -27
  119. data/generator_files/cookbooks/infranodes/test/integration/default/infranodes_spec.rb +22 -22
  120. data/generator_files/cookbooks/infranodes/test/integration/helpers/serverspec/spec_helper.rb +8 -8
  121. data/generator_files/cookbooks/wombat/.gitignore +16 -16
  122. data/generator_files/cookbooks/wombat/.kitchen.yml +43 -43
  123. data/generator_files/cookbooks/wombat/Berksfile +5 -5
  124. data/generator_files/cookbooks/wombat/README.md +4 -4
  125. data/generator_files/cookbooks/wombat/attributes/default.rb +79 -80
  126. data/generator_files/cookbooks/wombat/attributes/packer.rb +18 -18
  127. data/generator_files/cookbooks/wombat/chefignore +102 -102
  128. data/generator_files/cookbooks/wombat/metadata.rb +13 -13
  129. data/generator_files/cookbooks/wombat/recipes/authorized-keys.rb +20 -20
  130. data/generator_files/cookbooks/wombat/recipes/default.rb +111 -111
  131. data/generator_files/cookbooks/wombat/recipes/etc-hosts.rb +51 -51
  132. data/generator_files/cookbooks/workstation/.gitignore +16 -16
  133. data/generator_files/cookbooks/workstation/.kitchen.azure.yml +45 -0
  134. data/generator_files/cookbooks/workstation/.kitchen.ec2.yml +46 -30
  135. data/generator_files/cookbooks/workstation/.kitchen.yml +42 -22
  136. data/generator_files/cookbooks/workstation/Berksfile +7 -7
  137. data/generator_files/cookbooks/workstation/README.md +3 -3
  138. data/generator_files/cookbooks/workstation/chefignore +106 -102
  139. data/generator_files/cookbooks/workstation/files/atom.apm.list +10 -7
  140. data/generator_files/cookbooks/workstation/files/atom.config.cson +6 -3
  141. data/generator_files/cookbooks/workstation/{templates/default/ise_profile.ps1.erb → files/ise_profile.ps1} +11 -11
  142. data/generator_files/cookbooks/workstation/libraries/home.rb +4 -4
  143. data/generator_files/cookbooks/workstation/metadata.rb +14 -14
  144. data/generator_files/cookbooks/workstation/recipes/browser.rb +53 -58
  145. data/generator_files/cookbooks/workstation/recipes/certs-keys.rb +41 -45
  146. data/generator_files/cookbooks/workstation/recipes/chef.rb +29 -28
  147. data/generator_files/cookbooks/workstation/recipes/default.rb +24 -21
  148. data/generator_files/cookbooks/workstation/recipes/dotnet.rb +19 -17
  149. data/generator_files/cookbooks/workstation/recipes/editor.rb +46 -18
  150. data/generator_files/cookbooks/workstation/recipes/profile.rb +14 -41
  151. data/generator_files/cookbooks/workstation/recipes/terminal.rb +11 -11
  152. data/generator_files/cookbooks/workstation/templates/default/bookmarks.html.erb +23 -23
  153. data/generator_files/cookbooks/workstation/templates/default/data_collector.rb.erb +2 -2
  154. data/generator_files/cookbooks/workstation/templates/default/knife.rb.erb +10 -10
  155. data/generator_files/cookbooks/workstation/templates/default/master_preferences.json.erb +28 -28
  156. data/generator_files/cookbooks/workstation/templates/default/ssh_config.erb +16 -16
  157. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -26
  158. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/automate.key +27 -27
  159. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/chef.crt +26 -26
  160. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/chef.key +27 -27
  161. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -26
  162. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -27
  163. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/private.pem +27 -27
  164. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/public.pub +1 -1
  165. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/metadata.rb +2 -2
  166. data/generator_files/cookbooks/workstation/test/fixtures/cookbooks/mock_data/recipes/default.rb +21 -21
  167. data/generator_files/cookbooks/workstation/test/integration/default/workstation_spec.rb +77 -37
  168. data/generator_files/packer/automate.json +136 -136
  169. data/generator_files/packer/build-node.json +142 -142
  170. data/generator_files/packer/chef-server.json +137 -137
  171. data/generator_files/packer/compliance.json +133 -133
  172. data/generator_files/packer/infranodes-windows.json +143 -143
  173. data/generator_files/packer/infranodes.json +134 -134
  174. data/generator_files/packer/scripts/PreSysprep.ps1 +9 -0
  175. data/generator_files/packer/workstation.json +160 -142
  176. data/generator_files/templates/arm.md.json.erb +754 -754
  177. data/generator_files/templates/arm.vhd.json.erb +630 -630
  178. data/generator_files/templates/bootstrap-aws.erb +39 -39
  179. data/generator_files/templates/cfn.json.erb +675 -674
  180. data/generator_files/wombat.yml +75 -74
  181. data/lib/wombat/aws.rb +67 -67
  182. data/lib/wombat/build.rb +392 -392
  183. data/lib/wombat/cli.rb +254 -254
  184. data/lib/wombat/common.rb +420 -420
  185. data/lib/wombat/crypto.rb +65 -65
  186. data/lib/wombat/delete.rb +67 -67
  187. data/lib/wombat/deploy.rb +128 -128
  188. data/lib/wombat/init.rb +32 -32
  189. data/lib/wombat/latest.rb +27 -27
  190. data/lib/wombat/output.rb +101 -101
  191. data/lib/wombat/update.rb +20 -20
  192. data/lib/wombat/version.rb +3 -3
  193. data/lib/wombat.rb +8 -8
  194. data/spec/functional/common_spec.rb +26 -26
  195. data/spec/spec_helper.rb +103 -103
  196. data/spec/unit/common_spec.rb +116 -116
  197. data/terraform/README.md +13 -13
  198. data/terraform/templates/terraform.tfvars.erb +12 -12
  199. data/terraform/wombat.tf +328 -328
  200. data/wombat-cli.gemspec +36 -36
  201. metadata +6 -4
data/lib/wombat/common.rb CHANGED
@@ -1,421 +1,421 @@
1
- require 'yaml'
2
- require 'json'
3
- require 'erb'
4
- require 'benchmark'
5
- require 'fileutils'
6
- require 'ms_rest_azure'
7
-
8
- module Wombat
9
- module Common
10
-
11
- def banner(msg)
12
- puts "==> #{msg}"
13
- end
14
-
15
- def info(msg)
16
- puts " #{msg}"
17
- end
18
-
19
- def warn(msg)
20
- puts ">>> #{msg}"
21
- end
22
-
23
- def duration(total)
24
- total = 0 if total.nil?
25
- minutes = (total / 60).to_i
26
- seconds = (total - (minutes * 60))
27
- format("%dm%.2fs", minutes, seconds)
28
- end
29
-
30
- def wombat
31
- @wombat_yml ||= ENV['WOMBAT_YML'] unless ENV['WOMBAT_YML'].nil?
32
- @wombat_yml ||= 'wombat.yml'
33
- if !File.exist?(@wombat_yml)
34
- warn('No wombat.yml found, copying example')
35
- gen_dir = "#{File.expand_path("../..", File.dirname(__FILE__))}/generator_files"
36
- FileUtils.cp_r "#{gen_dir}/wombat.yml", Dir.pwd
37
- end
38
- YAML.load(File.read(@wombat_yml))
39
- end
40
-
41
- def lock
42
- if !File.exist?('wombat.lock')
43
- warn('No wombat.lock found')
44
- return 1
45
- else
46
- JSON.parse(File.read('wombat.lock'))
47
- end
48
- end
49
-
50
- def bootstrap_aws
51
- @workstation_passwd = wombat['workstations']['password']
52
- rendered = ERB.new(File.read("#{conf['template_dir']}/bootstrap-aws.erb"), nil, '-').result(binding)
53
- Dir.mkdir("#{conf['packer_dir']}/scripts", 0755) unless File.exist?("#{conf['packer_dir']}/scripts")
54
- File.open("#{conf['packer_dir']}/scripts/bootstrap-aws.txt", 'w') { |file| file.puts rendered }
55
- banner("Generated: #{conf['packer_dir']}/scripts/bootstrap-aws.txt")
56
- end
57
-
58
- def parse_log(log, cloud)
59
- regex = case cloud
60
- when 'gcp'
61
- 'A disk image was created:'
62
- when 'azure'
63
-
64
- if !wombat['azure'].key?('use_managed_disks') || !wombat['azure']['use_managed_disks']
65
- '^OSDiskUri:'
66
- else
67
- '^ManagedDiskOSDiskUri:'
68
- end
69
-
70
- else
71
- "#{wombat['aws']['region']}:"
72
- end
73
-
74
- File.read(log).split("\n").grep(/#{regex}/) {|x| x.split[1]}.last
75
- end
76
-
77
- def infranodes
78
- unless wombat['infranodes'].nil?
79
- wombat['infranodes'].sort
80
- else
81
- puts 'No infranodes listed in wombat.yml'
82
- {}
83
- end
84
- end
85
-
86
- def build_nodes
87
- build_nodes = {}
88
- 1.upto(wombat['build-nodes']['count'].to_i) do |i|
89
- build_nodes["build-node-#{i}"] = i
90
- end
91
- build_nodes
92
- end
93
-
94
- def workstations
95
- workstations = {}
96
- 1.upto(wombat['workstations']['count'].to_i) do |i|
97
- workstations["workstation-#{i}"] = i
98
- end
99
- workstations
100
- end
101
-
102
- def create_infranodes_json
103
- infranodes_file_path = File.join(conf['files_dir'], 'infranodes-info.json')
104
- if File.exists?(infranodes_file_path) && is_valid_json?(infranodes_file_path)
105
- current_state = JSON(File.read(infranodes_file_path))
106
- else
107
- current_state = nil
108
- end
109
- return if current_state == infranodes # yay idempotence
110
- File.open(infranodes_file_path, 'w') do |f|
111
- f.puts JSON.pretty_generate(infranodes)
112
- end
113
- end
114
-
115
- def linux
116
- wombat['linux'].nil? ? 'ubuntu' : wombat['linux']
117
- end
118
-
119
- def conf
120
- conf = wombat['conf']
121
- conf ||= {}
122
- conf['files_dir'] ||= 'files'
123
- conf['key_dir'] ||= 'keys'
124
- conf['cookbook_dir'] ||= 'cookbooks'
125
- conf['packer_dir'] ||= 'packer'
126
- conf['log_dir'] ||= 'logs'
127
- conf['stack_dir'] ||= 'stacks'
128
- conf['template_dir'] ||= 'templates'
129
- conf['timeout'] ||= 7200
130
- conf['audio'] ||= false
131
- conf
132
- end
133
-
134
- def is_mac?
135
- (/darwin/ =~ RUBY_PLATFORM) != nil
136
- end
137
-
138
- def audio?
139
- is_mac? && conf['audio']
140
- end
141
-
142
- def logs
143
- path = "#{conf['log_dir']}/#{cloud}*.log"
144
- Dir.glob(path).reject { |l| !l.match(wombat['linux']) }
145
- end
146
-
147
- def calculate_templates
148
- globs = "*.json"
149
- Dir.chdir(conf['packer_dir']) do
150
- Array(globs).
151
- map { |glob| result = Dir.glob("#{glob}"); result.empty? ? glob : result }.
152
- flatten.
153
- sort.
154
- delete_if { |file| file =~ /\.variables\./ }.
155
- map { |template| template.sub(/\.json$/, '') }
156
- end
157
- end
158
-
159
- def update_lock(cloud)
160
- copy = {}
161
- copy = wombat
162
-
163
- # Check that the copy contains a key for the named cloud
164
- unless copy.key?(cloud)
165
- throw "The Cloud '#{cloud}' is not specified in Wombat"
166
- end
167
-
168
- # Determine the region/location/zone for the specific cloud
169
- case cloud
170
- when 'aws'
171
- region = copy['aws']['region']
172
- when 'azure'
173
- region = copy['azure']['location']
174
- when 'gce'
175
- region = copy['gce']['zone']
176
- end
177
-
178
- linux = copy['linux']
179
- copy['amis'] = { region => {} }
180
-
181
- if logs.length == 0
182
- warn('No logs found - skipping lock update')
183
- else
184
- logs.each do |log|
185
- case log
186
- when /build-node/
187
- copy['amis'][region]['build-node'] ||= {}
188
- num = log.split('-')[3]
189
- copy['amis'][region]['build-node'].store(num, parse_log(log, cloud))
190
- when /workstation/
191
- copy['amis'][region]['workstation'] ||= {}
192
- num = log.split('-')[2]
193
- copy['amis'][region]['workstation'].store(num, parse_log(log, cloud))
194
- when /infranodes/
195
- copy['amis'][region]['infranodes'] ||= {}
196
- name = log.split('-')[2]
197
- copy['amis'][region]['infranodes'].store(name, parse_log(log, cloud))
198
- else
199
- instance = log.match("#{cloud}-(.*)-#{linux}\.log")[1]
200
- copy['amis'][region].store(instance, parse_log(log, cloud))
201
- end
202
- end
203
- copy['last_updated'] = Time.now.gmtime.strftime('%Y%m%d%H%M%S')
204
- banner('Updating wombat.lock')
205
- File.open('wombat.lock', 'w') do |f|
206
- f.write(JSON.pretty_generate(copy))
207
- end
208
- end
209
- end
210
-
211
- def update_template(cloud)
212
- if lock == 1
213
- warn('No lock - skipping template creation')
214
- else
215
-
216
- @demo = lock['name']
217
- @version = lock['version']
218
- @ttl = lock['ttl']
219
-
220
- # Determine the region/location/zone for the specific cloud
221
- case cloud
222
- when 'aws'
223
- region = lock['aws']['region']
224
- template_files = {
225
- "cfn.json.erb": "#{conf['stack_dir']}/#{@demo}.json"
226
- }
227
- @chef_server_ami = lock['amis'][region]['chef-server']
228
- @automate_ami = lock['amis'][region]['automate']
229
- @compliance_ami = lock['amis'][region]['compliance']
230
- @availability_zone = lock['aws']['az']
231
- @iam_roles = lock['aws']['iam_roles']
232
- when 'azure'
233
- region = lock['azure']['location']
234
- @storage_account = lock['azure']['storage_account']
235
-
236
- template_files = {}
237
-
238
- # determine whether to use VHD or Managed Disks
239
- if !lock['azure'].key?('use_managed_disks') || !lock['azure']['use_managed_disks']
240
- template_files['arm.vhd.json.erb'] = format("%s/%s.json", conf['stack_dir'], @demo)
241
- else
242
- template_files['arm.md.json.erb'] = format("%s/%s.json", conf['stack_dir'], @demo)
243
- end
244
-
245
- @chef_server_uri = lock['amis'][region]['chef-server']
246
- @automate_uri = lock['amis'][region]['automate']
247
- @compliance_uri = lock['amis'][region]['compliance']
248
- @password = lock['workstations']['password']
249
- @public_key = File.read("#{conf['key_dir']}/public.pub").chomp
250
-
251
- # Set the Azure Tag used to identify Chef products in Azure
252
- @chef_tag = azure_provider_tag
253
- when 'gce'
254
- region = lock['gce']['zone']
255
- end
256
-
257
- if lock['amis'][region].key?('build-node')
258
- @build_nodes = lock['build-nodes']['count'].to_i
259
- @build_node_ami = {}
260
- 1.upto(@build_nodes) do |i|
261
- @build_node_ami[i] = lock['amis'][region]['build-node'][i.to_s]
262
- end
263
- end
264
-
265
- @infra = {}
266
- infranodes.each do |name, _rl|
267
- @infra[name] = lock['amis'][region]['infranodes'][name]
268
- end
269
-
270
- if lock['amis'][region].key?('workstation')
271
- @workstations = lock['workstations']['count'].to_i
272
- @workstation_ami = {}
273
- 1.upto(@workstations) do |i|
274
- @workstation_ami[i] = lock['amis'][region]['workstation'][i.to_s]
275
- end
276
- end
277
-
278
- # Iterate around each of the template files that have been defined and render it
279
- template_files.each do |template_file, destination|
280
- rendered_cfn = ERB.new(File.read("#{conf['template_dir']}/#{template_file}"), nil, '-').result(binding)
281
- Dir.mkdir(conf['stack_dir'], 0755) unless File.exist?(conf['stack_dir'])
282
- File.open("#{destination}", 'w') { |file| file.puts rendered_cfn }
283
- banner("Generated: #{destination}")
284
- end
285
- end
286
- end
287
-
288
- def is_valid_json?(file)
289
- begin
290
- JSON.parse(file)
291
- true
292
- rescue JSON::ParserError => e
293
- false
294
- end
295
- end
296
-
297
- # Return the Azure Provider tag that should be applied to resource
298
- def azure_provider_tag
299
- "33194f91-eb5f-4110-827a-e95f640a9e46".upcase
300
- end
301
-
302
- # Connect to Azure using environment variables
303
- #
304
- #
305
- def connect_azure
306
-
307
- # Create the connection to Azure using the information in the environment variables
308
- tenant_id = ENV['AZURE_TENANT_ID']
309
- client_id = ENV['AZURE_CLIENT_ID']
310
- client_secret = ENV['AZURE_CLIENT_SECRET']
311
-
312
- token_provider = MsRestAzure::ApplicationTokenProvider.new(tenant_id, client_id, client_secret)
313
- MsRest::TokenCredentials.new(token_provider)
314
- end
315
-
316
- # Track the progress of the deployment in Azure
317
- #
318
- # ===== Attributes
319
- #
320
- # * +rg_name+ - Name of the resource group being deployed to
321
- # * +deployment_name+ - Name of the deployment that is currently being processed
322
- def follow_azure_deployment(rg_name, deployment_name)
323
-
324
- end_provisioning_states = 'Canceled,Failed,Deleted,Succeeded'
325
- end_provisioning_state_reached = false
326
-
327
- until end_provisioning_state_reached
328
- list_outstanding_deployment_operations(rg_name, deployment_name)
329
- sleep 10
330
- deployment_provisioning_state = deployment_state(rg_name, deployment_name)
331
- end_provisioning_state_reached = end_provisioning_states.split(',').include?(deployment_provisioning_state)
332
- end
333
- info format("Resource Template deployment reached end state of %s", deployment_provisioning_state)
334
- end
335
-
336
- # Get a list of the outstanding deployment operations
337
- #
338
- # ===== Attributes
339
- #
340
- # * +rg_name+ - Name of the resource group being deployed to
341
- # * +deployment_name+ - Name of the deployment that is currently being processed
342
- def list_outstanding_deployment_operations(rg_name, deployment_name)
343
- end_operation_states = 'Failed,Succeeded'
344
- deployment_operations = resource_management_client.deployment_operations.list(rg_name, deployment_name)
345
- deployment_operations.each do |val|
346
- resource_provisioning_state = val.properties.provisioning_state
347
- unless val.properties.target_resource.nil?
348
- resource_name = val.properties.target_resource.resource_name
349
- resource_type = val.properties.target_resource.resource_type
350
- end
351
- end_operation_state_reached = end_operation_states.split(',').include?(resource_provisioning_state)
352
- unless end_operation_state_reached
353
- info format("resource %s '%s' provisioning status is %s", resource_type, resource_name, resource_provisioning_state)
354
- end
355
- end
356
- end
357
-
358
- # Get the state of the specified deployment
359
- #
360
- # ===== Attributes
361
- #
362
- # * +rg_name+ - Name of the resource group being deployed to
363
- # * +deployment_name+ - Name of the deployment that is currently being processed
364
- def deployment_state(rg_name, deployment_name)
365
- deployments = resource_management_client.deployments.get(rg_name, deployment_name)
366
- deployments.properties.provisioning_state
367
- end
368
-
369
- def create_resource_group(resource_management_client, name, location, owner = nil, rgtags = {})
370
-
371
- # Check that the resource group exists
372
- banner(format("Checking for resource group: %s", name))
373
- status = resource_management_client.resource_groups.check_existence(name)
374
- if status
375
- puts "resource group already exists"
376
- else
377
- puts format("creating new resource group in '%s'", location)
378
-
379
- # Set the parameters for the resource group
380
- resource_group = Azure::ARM::Resources::Models::ResourceGroup.new
381
- resource_group.location = location
382
-
383
- # Create hash to be used as tags on the resource group
384
- tags = {
385
- owner: ENV['USER'],
386
- provider: azure_provider_tag
387
- }
388
-
389
- # If an owner has been specified in the wombat file override the owner value
390
- if !owner.nil?
391
- tags[:owner] = owner
392
- end
393
-
394
- # Determine if there are any tags specified in the azure wmbat section that need to be added
395
- if !rgtags.nil? && rgtags.length > 0
396
-
397
- # Check to see if there are more than 15 tags in which case output a warning
398
- if rgtags.length > 14
399
- warn ('More than 15 tags have been specified, only the first 15 will be added. This is a restriction in Azure.')
400
- end
401
-
402
- # Iterate around the tags and add each one to the tags array, up to 15
403
- rgtags.each_with_index do |(key, value), index|
404
- tags[key] = value
405
-
406
- if index == 12
407
- break
408
- end
409
- end
410
-
411
- end
412
-
413
- # add the tags hash to the parameters
414
- resource_group.tags = rgtags
415
-
416
- # Create the resource group
417
- resource_management_client.resource_groups.create_or_update(name, resource_group)
418
- end
419
- end
420
- end
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'erb'
4
+ require 'benchmark'
5
+ require 'fileutils'
6
+ require 'ms_rest_azure'
7
+
8
+ module Wombat
9
+ module Common
10
+
11
+ def banner(msg)
12
+ puts "==> #{msg}"
13
+ end
14
+
15
+ def info(msg)
16
+ puts " #{msg}"
17
+ end
18
+
19
+ def warn(msg)
20
+ puts ">>> #{msg}"
21
+ end
22
+
23
+ def duration(total)
24
+ total = 0 if total.nil?
25
+ minutes = (total / 60).to_i
26
+ seconds = (total - (minutes * 60))
27
+ format("%dm%.2fs", minutes, seconds)
28
+ end
29
+
30
+ def wombat
31
+ @wombat_yml ||= ENV['WOMBAT_YML'] unless ENV['WOMBAT_YML'].nil?
32
+ @wombat_yml ||= 'wombat.yml'
33
+ if !File.exist?(@wombat_yml)
34
+ warn('No wombat.yml found, copying example')
35
+ gen_dir = "#{File.expand_path("../..", File.dirname(__FILE__))}/generator_files"
36
+ FileUtils.cp_r "#{gen_dir}/wombat.yml", Dir.pwd
37
+ end
38
+ YAML.load(File.read(@wombat_yml))
39
+ end
40
+
41
+ def lock
42
+ if !File.exist?('wombat.lock')
43
+ warn('No wombat.lock found')
44
+ return 1
45
+ else
46
+ JSON.parse(File.read('wombat.lock'))
47
+ end
48
+ end
49
+
50
+ def bootstrap_aws
51
+ @workstation_passwd = wombat['workstations']['password']
52
+ rendered = ERB.new(File.read("#{conf['template_dir']}/bootstrap-aws.erb"), nil, '-').result(binding)
53
+ Dir.mkdir("#{conf['packer_dir']}/scripts", 0755) unless File.exist?("#{conf['packer_dir']}/scripts")
54
+ File.open("#{conf['packer_dir']}/scripts/bootstrap-aws.txt", 'w') { |file| file.puts rendered }
55
+ banner("Generated: #{conf['packer_dir']}/scripts/bootstrap-aws.txt")
56
+ end
57
+
58
+ def parse_log(log, cloud)
59
+ regex = case cloud
60
+ when 'gcp'
61
+ 'A disk image was created:'
62
+ when 'azure'
63
+
64
+ if !wombat['azure'].key?('use_managed_disks') || !wombat['azure']['use_managed_disks']
65
+ '^OSDiskUri:'
66
+ else
67
+ '^ManagedDiskOSDiskUri:'
68
+ end
69
+
70
+ else
71
+ "#{wombat['aws']['region']}:"
72
+ end
73
+
74
+ File.read(log).split("\n").grep(/#{regex}/) {|x| x.split[1]}.last
75
+ end
76
+
77
+ def infranodes
78
+ unless wombat['infranodes'].nil?
79
+ wombat['infranodes'].sort
80
+ else
81
+ puts 'No infranodes listed in wombat.yml'
82
+ {}
83
+ end
84
+ end
85
+
86
+ def build_nodes
87
+ build_nodes = {}
88
+ 1.upto(wombat['build-nodes']['count'].to_i) do |i|
89
+ build_nodes["build-node-#{i}"] = i
90
+ end
91
+ build_nodes
92
+ end
93
+
94
+ def workstations
95
+ workstations = {}
96
+ 1.upto(wombat['workstations']['count'].to_i) do |i|
97
+ workstations["workstation-#{i}"] = i
98
+ end
99
+ workstations
100
+ end
101
+
102
+ def create_infranodes_json
103
+ infranodes_file_path = File.join(conf['files_dir'], 'infranodes-info.json')
104
+ if File.exists?(infranodes_file_path) && is_valid_json?(infranodes_file_path)
105
+ current_state = JSON(File.read(infranodes_file_path))
106
+ else
107
+ current_state = nil
108
+ end
109
+ return if current_state == infranodes # yay idempotence
110
+ File.open(infranodes_file_path, 'w') do |f|
111
+ f.puts JSON.pretty_generate(infranodes)
112
+ end
113
+ end
114
+
115
+ def linux
116
+ wombat['linux'].nil? ? 'ubuntu' : wombat['linux']
117
+ end
118
+
119
+ def conf
120
+ conf = wombat['conf']
121
+ conf ||= {}
122
+ conf['files_dir'] ||= 'files'
123
+ conf['key_dir'] ||= 'keys'
124
+ conf['cookbook_dir'] ||= 'cookbooks'
125
+ conf['packer_dir'] ||= 'packer'
126
+ conf['log_dir'] ||= 'logs'
127
+ conf['stack_dir'] ||= 'stacks'
128
+ conf['template_dir'] ||= 'templates'
129
+ conf['timeout'] ||= 7200
130
+ conf['audio'] ||= false
131
+ conf
132
+ end
133
+
134
+ def is_mac?
135
+ (/darwin/ =~ RUBY_PLATFORM) != nil
136
+ end
137
+
138
+ def audio?
139
+ is_mac? && conf['audio']
140
+ end
141
+
142
+ def logs
143
+ path = "#{conf['log_dir']}/#{cloud}*.log"
144
+ Dir.glob(path).reject { |l| !l.match(wombat['linux']) }
145
+ end
146
+
147
+ def calculate_templates
148
+ globs = "*.json"
149
+ Dir.chdir(conf['packer_dir']) do
150
+ Array(globs).
151
+ map { |glob| result = Dir.glob("#{glob}"); result.empty? ? glob : result }.
152
+ flatten.
153
+ sort.
154
+ delete_if { |file| file =~ /\.variables\./ }.
155
+ map { |template| template.sub(/\.json$/, '') }
156
+ end
157
+ end
158
+
159
+ def update_lock(cloud)
160
+ copy = {}
161
+ copy = wombat
162
+
163
+ # Check that the copy contains a key for the named cloud
164
+ unless copy.key?(cloud)
165
+ throw "The Cloud '#{cloud}' is not specified in Wombat"
166
+ end
167
+
168
+ # Determine the region/location/zone for the specific cloud
169
+ case cloud
170
+ when 'aws'
171
+ region = copy['aws']['region']
172
+ when 'azure'
173
+ region = copy['azure']['location']
174
+ when 'gce'
175
+ region = copy['gce']['zone']
176
+ end
177
+
178
+ linux = copy['linux']
179
+ copy['amis'] = { region => {} }
180
+
181
+ if logs.length == 0
182
+ warn('No logs found - skipping lock update')
183
+ else
184
+ logs.each do |log|
185
+ case log
186
+ when /build-node/
187
+ copy['amis'][region]['build-node'] ||= {}
188
+ num = log.split('-')[3]
189
+ copy['amis'][region]['build-node'].store(num, parse_log(log, cloud))
190
+ when /workstation/
191
+ copy['amis'][region]['workstation'] ||= {}
192
+ num = log.split('-')[2]
193
+ copy['amis'][region]['workstation'].store(num, parse_log(log, cloud))
194
+ when /infranodes/
195
+ copy['amis'][region]['infranodes'] ||= {}
196
+ name = log.split('-')[2]
197
+ copy['amis'][region]['infranodes'].store(name, parse_log(log, cloud))
198
+ else
199
+ instance = log.match("#{cloud}-(.*)-#{linux}\.log")[1]
200
+ copy['amis'][region].store(instance, parse_log(log, cloud))
201
+ end
202
+ end
203
+ copy['last_updated'] = Time.now.gmtime.strftime('%Y%m%d%H%M%S')
204
+ banner('Updating wombat.lock')
205
+ File.open('wombat.lock', 'w') do |f|
206
+ f.write(JSON.pretty_generate(copy))
207
+ end
208
+ end
209
+ end
210
+
211
+ def update_template(cloud)
212
+ if lock == 1
213
+ warn('No lock - skipping template creation')
214
+ else
215
+
216
+ @demo = lock['name']
217
+ @version = lock['version']
218
+ @ttl = lock['ttl']
219
+
220
+ # Determine the region/location/zone for the specific cloud
221
+ case cloud
222
+ when 'aws'
223
+ region = lock['aws']['region']
224
+ template_files = {
225
+ "cfn.json.erb": "#{conf['stack_dir']}/#{@demo}.json"
226
+ }
227
+ @chef_server_ami = lock['amis'][region]['chef-server']
228
+ @automate_ami = lock['amis'][region]['automate']
229
+ @compliance_ami = lock['amis'][region]['compliance']
230
+ @availability_zone = lock['aws']['az']
231
+ @iam_roles = lock['aws']['iam_roles']
232
+ when 'azure'
233
+ region = lock['azure']['location']
234
+ @storage_account = lock['azure']['storage_account']
235
+
236
+ template_files = {}
237
+
238
+ # determine whether to use VHD or Managed Disks
239
+ if !lock['azure'].key?('use_managed_disks') || !lock['azure']['use_managed_disks']
240
+ template_files['arm.vhd.json.erb'] = format("%s/%s.json", conf['stack_dir'], @demo)
241
+ else
242
+ template_files['arm.md.json.erb'] = format("%s/%s.json", conf['stack_dir'], @demo)
243
+ end
244
+
245
+ @chef_server_uri = lock['amis'][region]['chef-server']
246
+ @automate_uri = lock['amis'][region]['automate']
247
+ @compliance_uri = lock['amis'][region]['compliance']
248
+ @password = lock['workstations']['password']
249
+ @public_key = File.read("#{conf['key_dir']}/public.pub").chomp
250
+
251
+ # Set the Azure Tag used to identify Chef products in Azure
252
+ @chef_tag = azure_provider_tag
253
+ when 'gce'
254
+ region = lock['gce']['zone']
255
+ end
256
+
257
+ if lock['amis'][region].key?('build-node')
258
+ @build_nodes = lock['build-nodes']['count'].to_i
259
+ @build_node_ami = {}
260
+ 1.upto(@build_nodes) do |i|
261
+ @build_node_ami[i] = lock['amis'][region]['build-node'][i.to_s]
262
+ end
263
+ end
264
+
265
+ @infra = {}
266
+ infranodes.each do |name, _rl|
267
+ @infra[name] = lock['amis'][region]['infranodes'][name]
268
+ end
269
+
270
+ if lock['amis'][region].key?('workstation')
271
+ @workstations = lock['workstations']['count'].to_i
272
+ @workstation_ami = {}
273
+ 1.upto(@workstations) do |i|
274
+ @workstation_ami[i] = lock['amis'][region]['workstation'][i.to_s]
275
+ end
276
+ end
277
+
278
+ # Iterate around each of the template files that have been defined and render it
279
+ template_files.each do |template_file, destination|
280
+ rendered_cfn = ERB.new(File.read("#{conf['template_dir']}/#{template_file}"), nil, '-').result(binding)
281
+ Dir.mkdir(conf['stack_dir'], 0755) unless File.exist?(conf['stack_dir'])
282
+ File.open("#{destination}", 'w') { |file| file.puts rendered_cfn }
283
+ banner("Generated: #{destination}")
284
+ end
285
+ end
286
+ end
287
+
288
+ def is_valid_json?(file)
289
+ begin
290
+ JSON.parse(file)
291
+ true
292
+ rescue JSON::ParserError => e
293
+ false
294
+ end
295
+ end
296
+
297
+ # Return the Azure Provider tag that should be applied to resource
298
+ def azure_provider_tag
299
+ "33194f91-eb5f-4110-827a-e95f640a9e46".upcase
300
+ end
301
+
302
+ # Connect to Azure using environment variables
303
+ #
304
+ #
305
+ def connect_azure
306
+
307
+ # Create the connection to Azure using the information in the environment variables
308
+ tenant_id = ENV['AZURE_TENANT_ID']
309
+ client_id = ENV['AZURE_CLIENT_ID']
310
+ client_secret = ENV['AZURE_CLIENT_SECRET']
311
+
312
+ token_provider = MsRestAzure::ApplicationTokenProvider.new(tenant_id, client_id, client_secret)
313
+ MsRest::TokenCredentials.new(token_provider)
314
+ end
315
+
316
+ # Track the progress of the deployment in Azure
317
+ #
318
+ # ===== Attributes
319
+ #
320
+ # * +rg_name+ - Name of the resource group being deployed to
321
+ # * +deployment_name+ - Name of the deployment that is currently being processed
322
+ def follow_azure_deployment(rg_name, deployment_name)
323
+
324
+ end_provisioning_states = 'Canceled,Failed,Deleted,Succeeded'
325
+ end_provisioning_state_reached = false
326
+
327
+ until end_provisioning_state_reached
328
+ list_outstanding_deployment_operations(rg_name, deployment_name)
329
+ sleep 10
330
+ deployment_provisioning_state = deployment_state(rg_name, deployment_name)
331
+ end_provisioning_state_reached = end_provisioning_states.split(',').include?(deployment_provisioning_state)
332
+ end
333
+ info format("Resource Template deployment reached end state of %s", deployment_provisioning_state)
334
+ end
335
+
336
+ # Get a list of the outstanding deployment operations
337
+ #
338
+ # ===== Attributes
339
+ #
340
+ # * +rg_name+ - Name of the resource group being deployed to
341
+ # * +deployment_name+ - Name of the deployment that is currently being processed
342
+ def list_outstanding_deployment_operations(rg_name, deployment_name)
343
+ end_operation_states = 'Failed,Succeeded'
344
+ deployment_operations = resource_management_client.deployment_operations.list(rg_name, deployment_name)
345
+ deployment_operations.each do |val|
346
+ resource_provisioning_state = val.properties.provisioning_state
347
+ unless val.properties.target_resource.nil?
348
+ resource_name = val.properties.target_resource.resource_name
349
+ resource_type = val.properties.target_resource.resource_type
350
+ end
351
+ end_operation_state_reached = end_operation_states.split(',').include?(resource_provisioning_state)
352
+ unless end_operation_state_reached
353
+ info format("resource %s '%s' provisioning status is %s", resource_type, resource_name, resource_provisioning_state)
354
+ end
355
+ end
356
+ end
357
+
358
+ # Get the state of the specified deployment
359
+ #
360
+ # ===== Attributes
361
+ #
362
+ # * +rg_name+ - Name of the resource group being deployed to
363
+ # * +deployment_name+ - Name of the deployment that is currently being processed
364
+ def deployment_state(rg_name, deployment_name)
365
+ deployments = resource_management_client.deployments.get(rg_name, deployment_name)
366
+ deployments.properties.provisioning_state
367
+ end
368
+
369
+ def create_resource_group(resource_management_client, name, location, owner = nil, rgtags = {})
370
+
371
+ # Check that the resource group exists
372
+ banner(format("Checking for resource group: %s", name))
373
+ status = resource_management_client.resource_groups.check_existence(name)
374
+ if status
375
+ puts "resource group already exists"
376
+ else
377
+ puts format("creating new resource group in '%s'", location)
378
+
379
+ # Set the parameters for the resource group
380
+ resource_group = Azure::ARM::Resources::Models::ResourceGroup.new
381
+ resource_group.location = location
382
+
383
+ # Create hash to be used as tags on the resource group
384
+ tags = {
385
+ owner: ENV['USER'],
386
+ provider: azure_provider_tag
387
+ }
388
+
389
+ # If an owner has been specified in the wombat file override the owner value
390
+ if !owner.nil?
391
+ tags[:owner] = owner
392
+ end
393
+
394
+ # Determine if there are any tags specified in the azure wmbat section that need to be added
395
+ if !rgtags.nil? && rgtags.length > 0
396
+
397
+ # Check to see if there are more than 15 tags in which case output a warning
398
+ if rgtags.length > 14
399
+ warn ('More than 15 tags have been specified, only the first 15 will be added. This is a restriction in Azure.')
400
+ end
401
+
402
+ # Iterate around the tags and add each one to the tags array, up to 15
403
+ rgtags.each_with_index do |(key, value), index|
404
+ tags[key] = value
405
+
406
+ if index == 12
407
+ break
408
+ end
409
+ end
410
+
411
+ end
412
+
413
+ # add the tags hash to the parameters
414
+ resource_group.tags = rgtags
415
+
416
+ # Create the resource group
417
+ resource_management_client.resource_groups.create_or_update(name, resource_group)
418
+ end
419
+ end
420
+ end
421
421
  end