wombat-cli 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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