kitchen-oci 1.15.1 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e3fbf95593303d5f9538c59a7f07eb03dd1871b05b12ca0a82e5d2ed9b529b4
4
- data.tar.gz: d0c4e60f05ebaf18293ce93bad6f6a52d5d9657de9a0d34b4bb1d61ddaf3d57e
3
+ metadata.gz: 54d56f663b88d438c3b210755b53c04b1103445cd3f7736dd783679cda016bb7
4
+ data.tar.gz: d21f780ff87721d8027d2a4cbcbee1c449644515d61b36207d52d0478651be0a
5
5
  SHA512:
6
- metadata.gz: dac56f0b424e0b74ca273de0242d5f0883c95eee61c4e086a7cfa66c9382b9b933f5eb907099c83860f1a44c64c10277a0a656e56c755658a44b548b824c2875
7
- data.tar.gz: 19adf812199ecbba332985656ffbcc67252107f3f3d1ae2d9fbd1401a9c05e00342bf3d0ba2285f76388ba3075b111de0cb54dab43b94de8424c9ff3359aa04c
6
+ metadata.gz: 810d92317048752b4d2ad40b952aa8656153c14243fb19fbde92610c6dbc668094d49af4f130f6f3ab4b584e85199d08369ecb35d0f48bd627483b4d272254d9
7
+ data.tar.gz: b17e5a0695d27d9fab17fb3e415820bb2e62a81124a83bd506e6552f89bfc01d816e66fd3bf681a87629e661d3fed3d3c4b3902c697db854aabdef97136c61ab
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Author:: Justin Steele (<justin.steele@oracle.com>)
5
+ #
6
+ # Copyright:: (C) 2024, Stephen Pearson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ module Kitchen
21
+ module Driver
22
+ class Oci
23
+ # Api class that defines the various API classes used to interact with OCI
24
+ class Api
25
+ attr_reader :oci_config, :config
26
+ def initialize(oci_config, config)
27
+ @oci_config = oci_config
28
+ @config = config
29
+ end
30
+
31
+ def compute
32
+ generic_api(OCI::Core::ComputeClient)
33
+ end
34
+
35
+ def network
36
+ generic_api(OCI::Core::VirtualNetworkClient)
37
+ end
38
+
39
+ def dbaas
40
+ generic_api(OCI::Database::DatabaseClient)
41
+ end
42
+
43
+ def identity
44
+ generic_api(OCI::Identity::IdentityClient)
45
+ end
46
+
47
+ def blockstorage
48
+ generic_api(OCI::Core::BlockstorageClient)
49
+ end
50
+
51
+ private
52
+
53
+ def generic_api(klass)
54
+ params = {}
55
+ params[:proxy_settings] = api_proxy if api_proxy
56
+ params[:signer] = if config[:use_instance_principals]
57
+ OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
58
+ elsif config[:use_token_auth]
59
+ token_signer
60
+ end
61
+ params[:config] = oci_config unless config[:use_instance_principals]
62
+ klass.new(**params.compact)
63
+ end
64
+
65
+ def token_signer
66
+ pkey_content = oci_config.key_content || File.read(oci_config.key_file).strip
67
+ pkey = OpenSSL::PKey::RSA.new(pkey_content, oci_config.pass_phrase)
68
+
69
+ token = File.read(oci_config.security_token_file).strip
70
+ OCI::Auth::Signers::SecurityTokenSigner.new(token, pkey)
71
+ end
72
+
73
+ def proxy_config
74
+ if config[:proxy_url]
75
+ URI.parse(config[:proxy_url])
76
+ else
77
+ URI.parse("http://example.com").find_proxy
78
+ end
79
+ end
80
+
81
+ def api_proxy
82
+ prx = proxy_config
83
+ return unless prx
84
+
85
+ if prx.user
86
+ OCI::ApiClientProxySettings.new(prx.host, prx.port, prx.user,
87
+ prx.password)
88
+ else
89
+ OCI::ApiClientProxySettings.new(prx.host, prx.port)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Author:: Justin Steele (<justin.steele@oracle.com>)
4
+ #
5
+ # Copyright (C) 2024, Stephen Pearson
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+ module Driver
21
+ class Oci
22
+ # generic class for blockstorage
23
+ class Blockstorage < Oci
24
+ require_relative "api"
25
+ require_relative "config"
26
+ require_relative "models/iscsi"
27
+ require_relative "models/paravirtual"
28
+
29
+ attr_accessor :config, :state, :oci, :api, :volume_state, :volume_attachment_state
30
+
31
+ def initialize(config, state, oci, api, action = :create)
32
+ super()
33
+ @config = config
34
+ @state = state
35
+ @oci = oci
36
+ @api = api
37
+ @volume_state = {}
38
+ @volume_attachment_state = {}
39
+ oci.compartment if action == :create
40
+ end
41
+
42
+ def create_volume(volume)
43
+ info("Creating <#{volume[:name]}>...")
44
+ result = api.blockstorage.create_volume(volume_details(volume))
45
+ response = volume_response(result.data.id)
46
+ info("Finished creating <#{volume[:name]}>.")
47
+ [response, final_state(response)]
48
+ end
49
+
50
+ def attach_volume(volume_details, server_id)
51
+ info("Attaching <#{volume_details.display_name}>...")
52
+ attach_volume = api.compute.attach_volume(attachment_details(volume_details, server_id))
53
+ response = attachment_response(attach_volume.data.id)
54
+ info("Finished attaching <#{volume_details.display_name}>.")
55
+ final_state(response)
56
+ end
57
+
58
+ def delete_volume(volume)
59
+ info("Deleting <#{volume[:display_name]}>...")
60
+ api.blockstorage.delete_volume(volume[:id])
61
+ api.blockstorage.get_volume(volume[:id])
62
+ .wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_TERMINATED)
63
+ info("Finished deleting <#{volume[:display_name]}>.")
64
+ end
65
+
66
+ def detatch_volume(volume_attachment)
67
+ info("Detaching <#{attachment_name(volume_attachment)}>...")
68
+ api.compute.detach_volume(volume_attachment[:id])
69
+ api.compute.get_volume_attachment(volume_attachment[:id])
70
+ .wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_DETACHED)
71
+ info("Finished detaching <#{attachment_name(volume_attachment)}>.")
72
+ end
73
+
74
+ def detatch_and_delete
75
+ state[:volume_attachments].each do |att|
76
+ detatch_volume(att)
77
+ end
78
+
79
+ state[:volumes].each do |vol|
80
+ delete_volume(vol)
81
+ end
82
+ end
83
+
84
+ def final_state(response)
85
+ case response
86
+ when OCI::Core::Models::Volume
87
+ final_volume_state(response)
88
+ when OCI::Core::Models::VolumeAttachment
89
+ final_volume_attachment_state(response)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def volume_response(volume_id)
96
+ api.blockstorage.get_volume(volume_id)
97
+ .wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_AVAILABLE).data
98
+ end
99
+
100
+ def attachment_response(attachment_id)
101
+ api.compute.get_volume_attachment(attachment_id)
102
+ .wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_ATTACHED).data
103
+ end
104
+
105
+ def volume_details(volume)
106
+ OCI::Core::Models::CreateVolumeDetails.new(
107
+ compartment_id: oci.compartment,
108
+ availability_domain: config[:availability_domain],
109
+ display_name: volume[:name],
110
+ size_in_gbs: volume[:size_in_gbs],
111
+ vpus_per_gb: volume[:vpus_per_gb] || 10
112
+ )
113
+ end
114
+
115
+ def attachment_name(attachment)
116
+ attachment[:display_name].gsub(/(?:paravirtual|iscsi)-/, "")
117
+ end
118
+
119
+ def final_volume_state(response)
120
+ volume_state.store(:id, response.id)
121
+ volume_state.store(:display_name, response.display_name)
122
+ volume_state
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Author:: Justin Steele (<justin.steele@oracle.com>)
5
+ #
6
+ # Copyright (C) 2024, Stephen Pearson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ require_relative "api"
21
+
22
+ module Kitchen
23
+ module Driver
24
+ class Oci
25
+ # Config class that defines the oci config that will be used for the API calls
26
+ class Config
27
+ attr_reader :config
28
+
29
+ def initialize(driver_config)
30
+ setup_driver_config(driver_config)
31
+ @config = oci_config
32
+ end
33
+
34
+ def oci_config
35
+ # OCI::Config is missing this and we're definitely using compartment and security_token_file if specified in the config
36
+ OCI::Config.class_eval { attr_accessor :security_token_file } if @driver_config[:use_token_auth]
37
+ conf = config_loader(config_file_location: @driver_config[:oci_config_file], profile_name: @driver_config[:oci_profile_name])
38
+ @driver_config[:oci_config].each do |key, value|
39
+ conf.send("#{key}=", value) unless value.nil? || value.empty?
40
+ end
41
+ conf
42
+ end
43
+
44
+ def compartment
45
+ @compartment ||= @compartment_id
46
+ return @compartment if @compartment
47
+
48
+ raise "must specify either compartment_id or compartment_name" unless [@compartment_id, @compartment_name].any?
49
+
50
+ @compartment ||= compartment_id_by_name(@compartment_name)
51
+ raise "compartment not found" unless @compartment
52
+ end
53
+
54
+ private
55
+
56
+ def setup_driver_config(config)
57
+ @driver_config = config
58
+ @compartment_id = config[:compartment_id]
59
+ @compartment_name = config[:compartment_name]
60
+ end
61
+
62
+ def config_loader(opts = {})
63
+ OCI::ConfigFileLoader.load_config(**opts.compact)
64
+ rescue OCI::ConfigFileLoader::Errors::ConfigFileNotFoundError
65
+ OCI::Config.new
66
+ end
67
+
68
+ def tenancy
69
+ if @driver_config[:use_instance_principals]
70
+ sign = OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
71
+ sign.instance_variable_get "@tenancy_id"
72
+ else
73
+ config.tenancy
74
+ end
75
+ end
76
+
77
+ def compartment_id_by_name(name)
78
+ api = Oci::Api.new(config, @driver_config).identity
79
+ all_compartments(api, config.tenancy).select { |c| c.name == name }&.first&.id
80
+ end
81
+
82
+ def all_compartments(api, tenancy, compartments = [], page = nil)
83
+ current_compartments = api.list_compartments(tenancy, page: page)
84
+ next_page = current_compartments.next_page
85
+ compartments << current_compartments.data
86
+ all_compartments(api, tenancy, compartments, next_page) unless next_page.nil?
87
+ compartments.flatten
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Author:: Justin Steele (<justin.steele@oracle.com>)
5
+ #
6
+ # Copyright (C) 2024, Stephen Pearson
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ module Kitchen
21
+ module Driver
22
+ class Oci
23
+ # generic class for instance models
24
+ class Instance < Oci
25
+ require_relative "api"
26
+ require_relative "config"
27
+ require_relative "models/compute"
28
+ require_relative "models/dbaas"
29
+
30
+ attr_accessor :config, :state, :oci, :api
31
+
32
+ def initialize(config, state, oci, api, action)
33
+ super()
34
+ @config = config
35
+ @state = state
36
+ @oci = oci
37
+ @api = api
38
+ end
39
+
40
+ def compartment_id
41
+ launch_details.compartment_id = oci.compartment
42
+ end
43
+
44
+ def availability_domain
45
+ launch_details.availability_domain = config[:availability_domain]
46
+ end
47
+
48
+ def defined_tags
49
+ launch_details.defined_tags = config[:defined_tags]
50
+ end
51
+
52
+ def shape
53
+ launch_details.shape = config[:shape]
54
+ end
55
+
56
+ def freeform_tags
57
+ launch_details.freeform_tags = process_freeform_tags
58
+ end
59
+
60
+ def final_state(state, instance_id)
61
+ state.store(:server_id, instance_id)
62
+ state.store(:hostname, instance_ip(instance_id))
63
+ state
64
+ end
65
+
66
+ private
67
+
68
+ def public_ip_allowed?
69
+ subnet = api.network.get_subnet(config[:subnet_id]).data
70
+ !subnet.prohibit_public_ip_on_vnic
71
+ end
72
+
73
+ def random_password(special_chars)
74
+ (Array.new(5) { special_chars.sample } +
75
+ Array.new(5) { ("a".."z").to_a.sample } +
76
+ Array.new(5) { ("A".."Z").to_a.sample } +
77
+ Array.new(5) { ("0".."9").to_a.sample }).shuffle.join
78
+ end
79
+
80
+ def random_string(length)
81
+ Array.new(length) { ("a".."z").to_a.sample }.join
82
+ end
83
+
84
+ def random_number(length)
85
+ Array.new(length) { ("0".."9").to_a.sample }.join
86
+ end
87
+
88
+ def process_freeform_tags
89
+ tags = %w{run_list policyfile}
90
+ fft = config[:freeform_tags]
91
+ tags.each do |tag|
92
+ unless fft[tag.to_sym].nil? || fft[tag.to_sym].empty?
93
+ fft[tag] =
94
+ prov[tag.to_sym].join(",")
95
+ end
96
+ end
97
+ fft[:kitchen] = true
98
+ fft
99
+ end
100
+
101
+ def user_data
102
+ case config[:user_data]
103
+ when Array
104
+ Base64.encode64(multi_part_user_data.close.string).delete("\n")
105
+ when String
106
+ Base64.encode64(config[:user_data]).delete("\n")
107
+ end
108
+ end
109
+
110
+ def multi_part_user_data
111
+ boundary = "MIMEBOUNDARY_#{random_string(20)}"
112
+ msg = ["Content-Type: multipart/mixed; boundary=\"#{boundary}\"",
113
+ "MIME-Version: 1.0", ""]
114
+ msg += mime_parts(boundary)
115
+ txt = "#{msg.join("\n")}\n"
116
+ gzip = Zlib::GzipWriter.new(StringIO.new)
117
+ gzip << txt
118
+ end
119
+
120
+ def mime_parts(boundary)
121
+ msg = []
122
+ config[:user_data].each do |m|
123
+ msg << "--#{boundary}"
124
+ msg << "Content-Disposition: attachment; filename=\"#{m[:filename]}\""
125
+ msg << "Content-Transfer-Encoding: 7bit"
126
+ msg << "Content-Type: text/#{m[:type]}" << "Mime-Version: 1.0" << ""
127
+ msg << read_part(m) << ""
128
+ end
129
+ msg << "--#{boundary}--"
130
+ msg
131
+ end
132
+
133
+ def read_part(part)
134
+ if part[:path]
135
+ content = File.read part[:path]
136
+ elsif part[:inline]
137
+ content = part[:inline]
138
+ else
139
+ raise "Invalid user data"
140
+ end
141
+ content.split("\n")
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Author:: Justin Steele (<justin.steele@oracle.com>)
4
+ #
5
+ # Copyright (C) 2024, Stephen Pearson
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+ module Driver
21
+ class Oci
22
+ module Models
23
+ # Compute instance model
24
+ class Compute < Instance # rubocop:disable Metrics/ClassLength
25
+ attr_accessor :launch_details
26
+
27
+ def initialize(config, state, oci, api, action = :create)
28
+ super
29
+ @launch_details = OCI::Core::Models::LaunchInstanceDetails.new
30
+ end
31
+
32
+ def launch
33
+ process_windows_options
34
+ response = api.compute.launch_instance(launch_instance_details)
35
+ instance_id = response.data.id
36
+ api.compute.get_instance(instance_id).wait_until(:lifecycle_state, OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING )
37
+ final_state(state, instance_id)
38
+ end
39
+
40
+ def terminate
41
+ api.compute.terminate_instance(state[:server_id])
42
+ api.compute.get_instance(state[:server_id]).wait_until(:lifecycle_state, OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATING)
43
+ end
44
+
45
+ private
46
+
47
+ def launch_instance_details # rubocop:disable Metrics/MethodLength
48
+ compartment_id
49
+ availability_domain
50
+ defined_tags
51
+ shape
52
+ freeform_tags
53
+ hostname_display_name
54
+ instance_source_details
55
+ instance_metadata
56
+ preemptible_instance_config
57
+ shape_config
58
+ launch_details
59
+ end
60
+
61
+ def hostname_display_name
62
+ display_name = hostname
63
+ launch_details.display_name = display_name
64
+ launch_details.create_vnic_details = create_vnic_details(display_name)
65
+ end
66
+
67
+ def hostname
68
+ [config[:hostname_prefix], random_string(6)].compact.join("-")
69
+ end
70
+
71
+ def preemptible_instance_config
72
+ return unless config[:preemptible_instance]
73
+
74
+ launch_details.preemptible_instance_config = OCI::Core::Models::PreemptibleInstanceConfigDetails.new(
75
+ preemption_action:
76
+ OCI::Core::Models::TerminatePreemptionAction.new(
77
+ type: "TERMINATE", preserve_boot_volume: true
78
+ )
79
+ )
80
+ end
81
+
82
+ def shape_config
83
+ return if config[:shape_config].empty?
84
+
85
+ launch_details.shape_config = OCI::Core::Models::LaunchInstanceShapeConfigDetails.new(
86
+ ocpus: config[:shape_config][:ocpus],
87
+ memory_in_gbs: config[:shape_config][:memory_in_gbs],
88
+ baseline_ocpu_utilization: config[:shape_config][:baseline_ocpu_utilization] || "BASELINE_1_1"
89
+ )
90
+ end
91
+
92
+ def instance_source_details
93
+ launch_details.source_details = OCI::Core::Models::InstanceSourceViaImageDetails.new(
94
+ sourceType: "image",
95
+ imageId: config[:image_id],
96
+ bootVolumeSizeInGBs: config[:boot_volume_size_in_gbs]
97
+ )
98
+ end
99
+
100
+ def create_vnic_details(name)
101
+ OCI::Core::Models::CreateVnicDetails.new(
102
+ assign_public_ip: public_ip_allowed?,
103
+ display_name: name,
104
+ hostname_label: name,
105
+ nsg_ids: config[:nsg_ids],
106
+ subnetId: config[:subnet_id]
107
+ )
108
+ end
109
+
110
+ def pubkey
111
+ File.readlines(config[:ssh_keypath]).first.chomp
112
+ end
113
+
114
+ def instance_metadata
115
+ launch_details.metadata = metadata
116
+ end
117
+
118
+ def metadata
119
+ md = {}
120
+ inject_powershell
121
+ config[:custom_metadata]&.each { |k, v| md.store(k, v) }
122
+ md.store("ssh_authorized_keys", pubkey)
123
+ md.store("user_data", user_data) if config[:user_data] && !config[:user_data].empty?
124
+ md
125
+ end
126
+
127
+ def vnics(instance_id)
128
+ vnic_attachments(instance_id).map { |att| api.network.get_vnic(att.vnic_id).data }
129
+ end
130
+
131
+ def vnic_attachments(instance_id)
132
+ att = api.compute.list_vnic_attachments(oci.compartment, instance_id: instance_id).data
133
+ raise "Could not find any VNIC attachments" unless att.any?
134
+
135
+ att
136
+ end
137
+
138
+ def instance_ip(instance_id)
139
+ vnic = vnics(instance_id).select(&:is_primary).first
140
+ if public_ip_allowed?
141
+ config[:use_private_ip] ? vnic.private_ip : vnic.public_ip
142
+ else
143
+ vnic.private_ip
144
+ end
145
+ end
146
+
147
+ def process_windows_options
148
+ return unless windows_state?
149
+
150
+ state.store(:username, config[:winrm_user])
151
+ state.store(:password, config[:winrm_password] || random_password(%w{@ - ( ) .}))
152
+ end
153
+
154
+ def windows_state?
155
+ config[:setup_winrm] && config[:password].nil? && state[:password].nil?
156
+ end
157
+
158
+ def winrm_ps1
159
+ filename = File.join(__dir__, %w{.. .. .. .. .. tpl setup_winrm.ps1.erb})
160
+ tpl = ERB.new(File.read(filename))
161
+ tpl.result(binding)
162
+ end
163
+
164
+ def inject_powershell
165
+ return unless config[:setup_winrm]
166
+
167
+ data = winrm_ps1
168
+ config[:user_data] ||= []
169
+ config[:user_data] << {
170
+ type: "x-shellscript",
171
+ inline: data,
172
+ filename: "setup_winrm.ps1",
173
+ }
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end