bolt 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80377af090a1f1d5fd9515eac97623064459ce9f
4
- data.tar.gz: 1b73aeaa5dce3a2c3b37581905212e00c0ed2afa
3
+ metadata.gz: d4e27492b22291a233b5fb9dfbb8e8648a15ce19
4
+ data.tar.gz: 9b2d163aa823b02cd7b6cfc6031846841d7c7b30
5
5
  SHA512:
6
- metadata.gz: 445a3d714f38f2e8ab548df6c8b7b7ec777f407ec44625533c7fe1b1983210f5d82c648d149f9c1ba3de295b53422af27e89d1f1267ac2980d08bd12fabd4d66
7
- data.tar.gz: d0ce607c758b9431c48e978d5ca1e67470a2afd3be1ce35e5694e5a3bed5f82487c5c082daaf19cf1880c2fac16ec9297efaf8105c4b9fc7ab8970dc69eab5ca
6
+ metadata.gz: b3d53aafec9ab3aa09d30dd5d45624aa74a20835373c70f174508f4986c33470b5b45350f40014e534b18f59a6267303395f2075cdb1269231b112c8d9ec1765
7
+ data.tar.gz: 664b5acf2aa442bf7e24dd76b61723819a4eb0523b34118bd0976f6c696db03319e979ad40521af7334bebf06cdd545b071b3eef87047b0b5ced11988f6e49a5
data/lib/bolt/cli.rb CHANGED
@@ -97,9 +97,7 @@ HELP
97
97
  def initialize(argv)
98
98
  @argv = argv
99
99
  @options = {
100
- nodes: [],
101
- insecure: false,
102
- transport: 'ssh'
100
+ nodes: []
103
101
  }
104
102
  @config = Bolt::Config.new
105
103
  @parser = create_option_parser(@options)
@@ -137,7 +135,6 @@ HELP
137
135
  results[:password] = password
138
136
  end
139
137
  end
140
- results[:concurrency] = 100
141
138
  opts.on('--private-key KEY',
142
139
  "Private ssh key to authenticate with (Optional)") do |key|
143
140
  results[:key] = key
@@ -157,16 +154,10 @@ HELP
157
154
  results[:task_options] = parse_params(params)
158
155
  end
159
156
 
160
- results[:format] = 'human'
161
157
  opts.on('--format FORMAT',
162
158
  "Output format to use: human or json") do |format|
163
- if %w[human json].include? format
164
- results[:format] = format
165
- else
166
- raise Bolt::CLIError, "Unsupported format: #{format}"
167
- end
159
+ results[:format] = format
168
160
  end
169
- results[:insecure] = false
170
161
  opts.on('-k', '--insecure',
171
162
  "Whether to connect insecurely ") do |insecure|
172
163
  results[:insecure] = insecure
@@ -183,10 +174,6 @@ HELP
183
174
  "Program to execute for privilege escalation. " \
184
175
  "Currently only sudo is supported.") do |program|
185
176
  options[:sudo] = program || 'sudo'
186
- if options[:sudo] != 'sudo'
187
- raise Bolt::CLIError,
188
- "Only 'sudo' is supported for privilege escalation."
189
- end
190
177
  end
191
178
  opts.on('--sudo-password [PASSWORD]',
192
179
  'Password for privilege escalation') do |password|
@@ -198,6 +185,10 @@ HELP
198
185
  results[:sudo_password] = password
199
186
  end
200
187
  end
188
+ opts.on('--configfile CONFIG_PATH',
189
+ 'Specify where to load the config file from') do |path|
190
+ results[:configfile] = path
191
+ end
201
192
  opts.on_tail('--[no-]tty',
202
193
  "Request a pseudo TTY on nodes that support it") do |tty|
203
194
  results[:tty] = tty
@@ -227,6 +218,7 @@ HELP
227
218
  parser.permute(@argv)
228
219
  end
229
220
 
221
+ # Shortcut to handle help before other errors may be generated
230
222
  options[:mode] = remaining.shift
231
223
 
232
224
  if options[:mode] == 'help'
@@ -234,24 +226,20 @@ HELP
234
226
  options[:mode] = remaining.shift
235
227
  end
236
228
 
237
- options[:action] = remaining.shift
238
- options[:object] = remaining.shift
239
-
240
- if options[:debug]
241
- @config[:log_level] = Logger::DEBUG
242
- elsif options[:verbose]
243
- @config[:log_level] = Logger::INFO
244
- end
245
-
246
- @config[:key] = options[:key]
247
-
248
- @config[:format] = options[:format]
249
-
250
229
  if options[:help]
251
230
  print_help(options[:mode])
252
231
  raise Bolt::CLIExit
253
232
  end
254
233
 
234
+ @config.load_file(options[:configfile])
235
+ @config.update_from_cli(options)
236
+ @config.validate
237
+
238
+ # This section handles parsing non-flag options which are
239
+ # mode specific rather then part of the config
240
+ options[:action] = remaining.shift
241
+ options[:object] = remaining.shift
242
+
255
243
  task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
256
244
  if options[:task_options]
257
245
  unless task_options.empty?
@@ -356,7 +344,7 @@ HELP
356
344
  raise Bolt::CLIError, "Option '--nodes' must be specified"
357
345
  end
358
346
 
359
- if %w[task plan].include?(options[:mode]) && options[:modulepath].nil?
347
+ if %w[task plan].include?(options[:mode]) && @config[:modulepath].nil?
360
348
  raise Bolt::CLIError,
361
349
  "Option '--modulepath' must be specified when running" \
362
350
  " a task or plan"
@@ -372,11 +360,6 @@ HELP
372
360
  end
373
361
 
374
362
  def execute(options)
375
- %i[concurrency user password tty insecure transport
376
- sudo sudo_password run_as].each do |key|
377
- config[key] = options[key]
378
- end
379
-
380
363
  if options[:mode] == 'plan' || options[:mode] == 'task'
381
364
  begin
382
365
  require_relative '../../vendored/require_vendored'
@@ -420,7 +403,7 @@ HELP
420
403
  when 'task'
421
404
  task_name = options[:object]
422
405
 
423
- path, metadata = load_task_data(task_name, options[:modulepath])
406
+ path, metadata = load_task_data(task_name, @config[:modulepath])
424
407
  input_method = metadata['input_method']
425
408
 
426
409
  input_method ||= 'both'
@@ -455,7 +438,7 @@ HELP
455
438
  result = Puppet.override(bolt_executor: executor) do
456
439
  run_plan(options[:object],
457
440
  options[:task_options],
458
- options[:modulepath])
441
+ @config[:modulepath])
459
442
  end
460
443
  outputter.print_plan(result)
461
444
  rescue Puppet::Error
@@ -527,7 +510,7 @@ HELP
527
510
  cli << "--#{setting}" << dir
528
511
  end
529
512
  Puppet.initialize_settings(cli)
530
- Puppet::Pal.in_tmp_environment('bolt', modulepath: [BOLTLIB_PATH] + modulepath) do |pal|
513
+ Puppet::Pal.in_tmp_environment('bolt', modulepath: [BOLTLIB_PATH] + modulepath, facts: {}) do |pal|
531
514
  pal.with_script_compiler do |compiler|
532
515
  compiler.call_function('run_plan', plan, args)
533
516
  end
data/lib/bolt/config.rb CHANGED
@@ -1,37 +1,140 @@
1
1
  require 'logger'
2
+ require 'yaml'
2
3
 
3
4
  module Bolt
4
5
  Config = Struct.new(
5
6
  :concurrency,
6
7
  :format,
7
- :insecure,
8
8
  :log_destination,
9
9
  :log_level,
10
- :password,
11
- :run_as,
12
- :sudo,
13
- :sudo_password,
10
+ :modulepath,
14
11
  :transport,
15
- :key,
16
- :tty,
17
- :user
12
+ :transports
18
13
  ) do
14
+
19
15
  DEFAULTS = {
20
16
  concurrency: 100,
21
- tty: false,
22
- insecure: false,
23
17
  transport: 'ssh',
18
+ format: 'human',
24
19
  log_level: Logger::WARN,
25
20
  log_destination: STDERR
26
21
  }.freeze
27
22
 
23
+ TRANSPORT_OPTIONS = %i[insecure password run_as sudo sudo_password key tty user].freeze
24
+
25
+ TRANSPORT_DEFAULTS = {
26
+ insecure: false,
27
+ tty: false
28
+ }.freeze
29
+
30
+ TRANSPORTS = %i[ssh winrm pcp].freeze
31
+
28
32
  def initialize(**kwargs)
29
33
  super()
30
34
  DEFAULTS.merge(kwargs).each { |k, v| self[k] = v }
35
+
36
+ self[:transports] ||= {}
37
+ TRANSPORTS.each do |transport|
38
+ unless self[:transports][transport]
39
+ self[:transports][transport] = {}
40
+ end
41
+ TRANSPORT_DEFAULTS.each do |k, v|
42
+ unless self[:transports][transport][k]
43
+ self[:transports][transport][k] = v
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def default_path
50
+ path = ['.puppetlabs', 'bolt.yml']
51
+ root_path = '~'
52
+ File.join(root_path, *path)
53
+ end
54
+
55
+ def read_config_file(path)
56
+ path_passed = path
57
+ path ||= default_path
58
+ path = File.expand_path(path)
59
+ # safe_load doesn't work with psych in ruby 2.0
60
+ # The user controls the configfile so this isn't a problem
61
+ # rubocop:disable YAMLLoad
62
+ File.open(path, "r:UTF-8") { |f| YAML.load(f.read) }
63
+ rescue Errno::ENOENT
64
+ if path_passed
65
+ raise Bolt::CLIError, "Could not read config file: #{path}"
66
+ end
67
+ # In older releases of psych SyntaxError is not a subclass of Exception
68
+ rescue Psych::SyntaxError
69
+ raise Bolt::CLIError, "Could not parse config file: #{path}"
70
+ rescue Psych::Exception
71
+ raise Bolt::CLIError, "Could not parse config file: #{path}"
72
+ rescue IOError, SystemCallError
73
+ raise Bolt::CLIError, "Could not read config file: #{path}"
31
74
  end
32
75
 
33
- def escalate?
34
- sudo || run_as
76
+ def update_from_file(data)
77
+ if data['modulepath']
78
+ self[:modulepath] = data['modulepath'].split(File::PATH_SEPARATOR)
79
+ end
80
+
81
+ if data['concurrency']
82
+ self[:concurrency] = data['concurrency']
83
+ end
84
+
85
+ if data['format']
86
+ self[:format] = data['format'] if data['format']
87
+ end
88
+
89
+ if data['ssh']
90
+ if data['ssh']['private-key']
91
+ self[:transports][:ssh][:key] = data['ssh']['private-key']
92
+ end
93
+ if data['ssh']['insecure']
94
+ self[:transports][:ssh][:insecure] = data['ssh']['insecure']
95
+ end
96
+ end
97
+ # if data['pcp']
98
+ # end
99
+ # if data['winrm']
100
+ # end
101
+ end
102
+
103
+ def load_file(path)
104
+ data = read_config_file(path)
105
+ update_from_file(data) if data
106
+ end
107
+
108
+ def update_from_cli(options)
109
+ %i[concurrency transport format modulepath].each do |key|
110
+ self[key] = options[key] if options[key]
111
+ end
112
+
113
+ if options[:debug]
114
+ self[:log_level] = Logger::DEBUG
115
+ elsif options[:verbose]
116
+ self[:log_level] = Logger::INFO
117
+ end
118
+
119
+ TRANSPORT_OPTIONS.each do |key|
120
+ # TODO: We should eventually make these transport specific
121
+ TRANSPORTS.each do |transport|
122
+ self[:transports][transport][key] = options[key] if options[key]
123
+ end
124
+ end
125
+ end
126
+
127
+ def validate
128
+ TRANSPORTS.each do |transport|
129
+ tconf = self[:transports][transport]
130
+ if tconf[:sudo] && tconf[:sudo] != 'sudo'
131
+ raise Bolt::CLIError, "Only 'sudo' is supported for privilege escalation."
132
+ end
133
+ end
134
+
135
+ unless %w[human json].include? self[:format]
136
+ raise Bolt::CLIError, "Unsupported format: '#{self[:format]}'"
137
+ end
35
138
  end
36
139
  end
37
140
  end
data/lib/bolt/node.rb CHANGED
@@ -35,15 +35,17 @@ module Bolt
35
35
  config: Bolt::Config.new)
36
36
  @host = host
37
37
  @port = port
38
- @user = user || config[:user]
39
- @password = password || config[:password]
40
- @key = config[:key]
41
- @tty = config[:tty]
42
- @insecure = config[:insecure]
43
38
  @uri = uri
44
- @sudo = config[:sudo]
45
- @sudo_password = config[:sudo_password]
46
- @run_as = config[:run_as]
39
+
40
+ transport_conf = config[:transports][protocol.to_sym]
41
+ @user = user || transport_conf[:user]
42
+ @password = password || transport_conf[:password]
43
+ @key = transport_conf[:key]
44
+ @tty = transport_conf[:tty]
45
+ @insecure = transport_conf[:insecure]
46
+ @sudo = transport_conf[:sudo]
47
+ @sudo_password = transport_conf[:sudo_password]
48
+ @run_as = transport_conf[:run_as]
47
49
 
48
50
  @logger = init_logger(config[:log_destination], config[:log_level])
49
51
  @transport_logger = init_logger(config[:log_destination], Logger::WARN)
@@ -73,7 +75,7 @@ module Bolt
73
75
  end
74
76
 
75
77
  def run_script(script, arguments)
76
- @logger.info { "Running script: #{command}" }
78
+ @logger.info { "Running script: #{script}" }
77
79
  _run_script(script, arguments)
78
80
  end
79
81
 
@@ -11,6 +11,10 @@ module Bolt
11
11
 
12
12
  def disconnect; end
13
13
 
14
+ def protocol
15
+ 'pcp'
16
+ end
17
+
14
18
  def make_client
15
19
  OrchestratorClient.new({}, true)
16
20
  end
data/lib/bolt/node/ssh.rb CHANGED
@@ -14,6 +14,10 @@ module Bolt
14
14
  }
15
15
  end
16
16
 
17
+ def protocol
18
+ 'ssh'
19
+ end
20
+
17
21
  def connect
18
22
  options = {
19
23
  logger: @transport_logger,
@@ -1,9 +1,14 @@
1
1
  require 'winrm'
2
2
  require 'winrm-fs'
3
3
  require 'bolt/result'
4
+ require 'base64'
4
5
 
5
6
  module Bolt
6
7
  class WinRM < Node
8
+ def protocol
9
+ 'winrm'
10
+ end
11
+
7
12
  def initialize(host, port, user, password, shell: :powershell, **kwargs)
8
13
  super(host, port, user, password, **kwargs)
9
14
 
@@ -48,6 +53,9 @@ $ENV:RUBYLIB = "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\puppet\\lib;" +
48
53
  "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\hiera\\lib;" +
49
54
  $ENV:RUBYLIB
50
55
 
56
+ Add-Type -AssemblyName System.ServiceModel.Web, System.Runtime.Serialization
57
+ $utf8 = [System.Text.Encoding]::UTF8
58
+
51
59
  function Invoke-Interpreter
52
60
  {
53
61
  [CmdletBinding()]
@@ -140,6 +148,183 @@ function Invoke-Interpreter
140
148
  }
141
149
  }
142
150
  }
151
+
152
+ function Write-Stream {
153
+ PARAM(
154
+ [Parameter(Position=0)] $stream,
155
+ [Parameter(ValueFromPipeline=$true)] $string
156
+ )
157
+ PROCESS {
158
+ $bytes = $utf8.GetBytes($string)
159
+ $stream.Write( $bytes, 0, $bytes.Length )
160
+ }
161
+ }
162
+
163
+ function Convert-JsonToXml {
164
+ PARAM([Parameter(ValueFromPipeline=$true)] [string[]] $json)
165
+ BEGIN {
166
+ $mStream = New-Object System.IO.MemoryStream
167
+ }
168
+ PROCESS {
169
+ $json | Write-Stream -Stream $mStream
170
+ }
171
+ END {
172
+ $mStream.Position = 0
173
+ try {
174
+ $jsonReader = [System.Runtime.Serialization.Json.JsonReaderWriterFactory]::CreateJsonReader($mStream,[System.Xml.XmlDictionaryReaderQuotas]::Max)
175
+ $xml = New-Object Xml.XmlDocument
176
+ $xml.Load($jsonReader)
177
+ $xml
178
+ } finally {
179
+ $jsonReader.Close()
180
+ $mStream.Dispose()
181
+ }
182
+ }
183
+ }
184
+
185
+ Function ConvertFrom-Xml {
186
+ [CmdletBinding(DefaultParameterSetName="AutoType")]
187
+ PARAM(
188
+ [Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=1)] [Xml.XmlNode] $xml,
189
+ [Parameter(Mandatory=$true,ParameterSetName="ManualType")] [Type] $Type,
190
+ [Switch] $ForceType
191
+ )
192
+ PROCESS{
193
+ if (Get-Member -InputObject $xml -Name root) {
194
+ return $xml.root.Objects | ConvertFrom-Xml
195
+ } elseif (Get-Member -InputObject $xml -Name Objects) {
196
+ return $xml.Objects | ConvertFrom-Xml
197
+ }
198
+ $propbag = @{}
199
+ foreach ($name in Get-Member -InputObject $xml -MemberType Properties | Where-Object{$_.Name -notmatch "^__|type"} | Select-Object -ExpandProperty name) {
200
+ Write-Debug "$Name Type: $($xml.$Name.type)" -Debug:$false
201
+ $propbag."$Name" = Convert-Properties $xml."$name"
202
+ }
203
+ if (!$Type -and $xml.HasAttribute("__type")) { $Type = $xml.__Type }
204
+ if ($ForceType -and $Type) {
205
+ try {
206
+ $output = New-Object $Type -Property $propbag
207
+ } catch {
208
+ $output = New-Object PSObject -Property $propbag
209
+ $output.PsTypeNames.Insert(0, $xml.__type)
210
+ }
211
+ } elseif ($propbag.Count -ne 0) {
212
+ $output = New-Object PSObject -Property $propbag
213
+ if ($Type) {
214
+ $output.PsTypeNames.Insert(0, $Type)
215
+ }
216
+ }
217
+ return $output
218
+ }
219
+ }
220
+
221
+ Function Convert-Properties {
222
+ PARAM($InputObject)
223
+ switch ($InputObject.type) {
224
+ "object" {
225
+ return (ConvertFrom-Xml -Xml $InputObject)
226
+ }
227
+ "string" {
228
+ $MightBeADate = $InputObject.get_InnerText() -as [DateTime]
229
+ ## Strings that are actually dates (*grumble* JSON is crap)
230
+ if ($MightBeADate -and $propbag."$Name" -eq $MightBeADate.ToString("G")) {
231
+ return $MightBeADate
232
+ } else {
233
+ return $InputObject.get_InnerText()
234
+ }
235
+ }
236
+ "number" {
237
+ $number = $InputObject.get_InnerText()
238
+ if ($number -eq ($number -as [int])) {
239
+ return $number -as [int]
240
+ } elseif ($number -eq ($number -as [double])) {
241
+ return $number -as [double]
242
+ } else {
243
+ return $number -as [decimal]
244
+ }
245
+ }
246
+ "boolean" {
247
+ return [bool]::parse($InputObject.get_InnerText())
248
+ }
249
+ "null" {
250
+ return $null
251
+ }
252
+ "array" {
253
+ [object[]]$Items = $(foreach( $item in $InputObject.GetEnumerator() ) {
254
+ Convert-Properties $item
255
+ })
256
+ return $Items
257
+ }
258
+ default {
259
+ return $InputObject
260
+ }
261
+ }
262
+ }
263
+
264
+ Function ConvertFrom-Json2 {
265
+ [CmdletBinding()]
266
+ PARAM(
267
+ [Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=1)] [string] $InputObject,
268
+ [Parameter(Mandatory=$true)] [Type] $Type,
269
+ [Switch] $ForceType
270
+ )
271
+ PROCESS {
272
+ $null = $PSBoundParameters.Remove("InputObject")
273
+ [Xml.XmlElement]$xml = (Convert-JsonToXml $InputObject).Root
274
+ if ($xml) {
275
+ if ($xml.Objects) {
276
+ $xml.Objects.Item.GetEnumerator() | ConvertFrom-Xml @PSBoundParameters
277
+ } elseif ($xml.Item -and $xml.Item -isnot [System.Management.Automation.PSParameterizedProperty]) {
278
+ $xml.Item | ConvertFrom-Xml @PSBoundParameters
279
+ } else {
280
+ $xml | ConvertFrom-Xml @PSBoundParameters
281
+ }
282
+ } else {
283
+ Write-Error "Failed to parse JSON with JsonReader" -Debug:$false
284
+ }
285
+ }
286
+ }
287
+
288
+ function ConvertFrom-PSCustomObject
289
+ {
290
+ PARAM([Parameter(ValueFromPipeline = $true)] $InputObject)
291
+ PROCESS {
292
+ if ($null -eq $InputObject) { return $null }
293
+
294
+ if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {
295
+ $collection = @(
296
+ foreach ($object in $InputObject) { ConvertFrom-PSCustomObject $object }
297
+ )
298
+
299
+ $collection
300
+ } elseif ($InputObject -is [System.Management.Automation.PSCustomObject]) {
301
+ $hash = @{}
302
+ foreach ($property in $InputObject.PSObject.Properties) {
303
+ $hash[$property.Name] = ConvertFrom-PSCustomObject $property.Value
304
+ }
305
+
306
+ $hash
307
+ } else {
308
+ $InputObject
309
+ }
310
+ }
311
+ }
312
+
313
+ function Get-ContentAsJson
314
+ {
315
+ [CmdletBinding()]
316
+ PARAM(
317
+ [Parameter(Mandatory = $true)] $Text,
318
+ [Parameter(Mandatory = $false)] [Text.Encoding] $Encoding = [Text.Encoding]::UTF8
319
+ )
320
+
321
+ # using polyfill cmdlet on PS2, so pass type info
322
+ if ($PSVersionTable.PSVersion -lt [Version]'3.0') {
323
+ $Text | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject
324
+ } else {
325
+ $Text | ConvertFrom-Json | ConvertFrom-PSCustomObject
326
+ }
327
+ }
143
328
  PS
144
329
  if result.exit_code != 0
145
330
  raise BaseError.new("Could not initialize shell: #{result.stderr.string}", "SHELL_INIT_ERROR")
@@ -333,15 +518,25 @@ catch
333
518
  end
334
519
 
335
520
  with_remote_file(task) do |remote_path|
336
- if powershell_file?(remote_path) && stdin.nil?
337
- # NOTE: cannot redirect STDIN to a .ps1 script inside of PowerShell
338
- # must create new powershell.exe process like other interpreters
339
- # fortunately, using PS with stdin input_method should never happen
340
- output = execute("try { &""#{remote_path}"" } catch { exit 1 }")
341
- else
342
- path, args = *process_from_extension(remote_path)
343
- output = execute_process(path, args, stdin)
344
- end
521
+ output =
522
+ if powershell_file?(remote_path) && stdin.nil?
523
+ # NOTE: cannot redirect STDIN to a .ps1 script inside of PowerShell
524
+ # must create new powershell.exe process like other interpreters
525
+ # fortunately, using PS with stdin input_method should never happen
526
+ if input_method == 'powershell'
527
+ execute(<<-PS)
528
+ $private:taskArgs = Get-ContentAsJson (
529
+ $utf8.GetString([System.Convert]::FromBase64String('#{Base64.encode64(JSON.dump(arguments))}'))
530
+ )
531
+ try { & "#{remote_path}" @taskArgs } catch { exit 1 }
532
+ PS
533
+ else
534
+ execute(%(try { & "#{remote_path}" } catch { exit 1 }))
535
+ end
536
+ else
537
+ path, args = *process_from_extension(remote_path)
538
+ execute_process(path, args, stdin)
539
+ end
345
540
  Bolt::TaskResult.from_output(output)
346
541
  end
347
542
  # TODO: we should rely on the executor for this
data/lib/bolt/node_uri.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'addressable'
1
+ require 'addressable/uri'
2
2
 
3
3
  module Bolt
4
4
  class NodeURI
data/lib/bolt/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Bolt
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '0.10.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-29 00:00:00.000000000 Z
11
+ date: 2017-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable