vanagon 0.17.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/bin/build +3 -1
  4. data/bin/build_host_info +3 -1
  5. data/bin/build_requirements +3 -1
  6. data/bin/inspect +3 -1
  7. data/bin/render +3 -1
  8. data/bin/repo +3 -1
  9. data/bin/ship +3 -1
  10. data/bin/sign +3 -1
  11. data/extras/completions/vanagon.bash +38 -0
  12. data/extras/completions/vanagon.zsh +41 -0
  13. data/lib/vanagon/cli.rb +12 -2
  14. data/lib/vanagon/cli/build.rb +12 -3
  15. data/lib/vanagon/cli/build_host_info.rb +12 -3
  16. data/lib/vanagon/cli/build_requirements.rb +13 -5
  17. data/lib/vanagon/cli/completion.rb +44 -0
  18. data/lib/vanagon/cli/inspect.rb +12 -3
  19. data/lib/vanagon/cli/list.rb +74 -0
  20. data/lib/vanagon/cli/render.rb +11 -2
  21. data/lib/vanagon/cli/ship.rb +5 -4
  22. data/lib/vanagon/cli/sign.rb +3 -2
  23. data/lib/vanagon/component.rb +13 -10
  24. data/lib/vanagon/component/dsl.rb +27 -20
  25. data/lib/vanagon/component/source.rb +2 -1
  26. data/lib/vanagon/component/source/git.rb +35 -10
  27. data/lib/vanagon/component/source/http.rb +3 -2
  28. data/lib/vanagon/component/source/local.rb +2 -1
  29. data/lib/vanagon/component/source/rewrite.rb +3 -2
  30. data/lib/vanagon/driver.rb +35 -34
  31. data/lib/vanagon/engine/always_be_scheduling.rb +12 -11
  32. data/lib/vanagon/engine/docker.rb +2 -1
  33. data/lib/vanagon/engine/ec2.rb +5 -4
  34. data/lib/vanagon/engine/hardware.rb +4 -3
  35. data/lib/vanagon/engine/pooler.rb +6 -5
  36. data/lib/vanagon/environment.rb +3 -2
  37. data/lib/vanagon/logger.rb +31 -0
  38. data/lib/vanagon/platform.rb +38 -5
  39. data/lib/vanagon/platform/deb.rb +2 -0
  40. data/lib/vanagon/platform/dsl.rb +23 -6
  41. data/lib/vanagon/platform/windows.rb +3 -1
  42. data/lib/vanagon/project.rb +25 -15
  43. data/lib/vanagon/project/dsl.rb +6 -5
  44. data/lib/vanagon/utilities.rb +5 -4
  45. data/resources/deb/control.erb +1 -1
  46. data/resources/deb/postinst.erb +24 -13
  47. data/resources/deb/postrm.erb +9 -6
  48. data/resources/deb/prerm.erb +18 -8
  49. data/resources/osx/postinstall.erb +5 -1
  50. data/resources/rpm/project.spec.erb +12 -12
  51. data/resources/solaris/10/depend.erb +2 -2
  52. data/resources/solaris/10/postinstall.erb +10 -2
  53. data/resources/solaris/11/p5m.erb +2 -2
  54. data/spec/lib/vanagon/cli_spec.rb +143 -0
  55. data/spec/lib/vanagon/component/dsl_spec.rb +54 -10
  56. data/spec/lib/vanagon/component/source/git_spec.rb +4 -4
  57. data/spec/lib/vanagon/component_spec.rb +15 -2
  58. data/spec/lib/vanagon/driver_spec.rb +1 -1
  59. data/spec/lib/vanagon/engine/always_be_scheduling_spec.rb +4 -4
  60. data/spec/lib/vanagon/platform_spec.rb +80 -0
  61. data/spec/lib/vanagon/utilities_spec.rb +4 -1
  62. metadata +37 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e09a1563ed504da68687d85b7c7a70e62028b7d1ba24568476d5cae338ae1ebf
4
- data.tar.gz: 9da1804bfbf40c86ac845ccf60a76c133b217357117dcd22d43d97c35299aa1d
3
+ metadata.gz: 6fb1135d000a2b468d3a998b6ef1e7547d26f835d06b67f2ba502fb710d1f06b
4
+ data.tar.gz: ffac33c290bc103a851d8bc9f468ec8a838465ac0e12125446c86a2b22cbde9e
5
5
  SHA512:
6
- metadata.gz: 19591339a372853aa653311214de761ead6dfd173218d68c0086691ede75da8d981079408afaf04e5fd362e2c9250c72ac9b1cb8cc3fdbeb16006a6510707176
7
- data.tar.gz: 74e995f55e05778820809bbed4c2dcd501896a8d4894e1a38921bb8057cee049d62c42e136e8929c84c9609790738d77be356ed55b499974b3399c6a032d724c
6
+ metadata.gz: 388e8520c4231c89b4170d3f99ac590caea60e98ebe5ccd2a0b5cee159c595b80b897d45c91dfda3b063da2c636420ae1c8e96a52eb87b17fef1d99d8848ba15
7
+ data.tar.gz: 1ca438d9b68285bfcf8b269d1fe8217736bad75d868ecb7452fac4a499dab15d3eae8200ab9a4860763c0a5217335f17b4f7561ce58c0984f331356be594607a
data/README.md CHANGED
@@ -197,8 +197,8 @@ Port of the system where redis is running. Defaults to *6379*.
197
197
  ##### `VANAGON_USE_MIRRORS`
198
198
  Controls whether component sources are downloaded directly from upstream URLs
199
199
  or from configured mirrors. Most Puppet projects using Vanagon default to
200
- fetching components from internal mirrors. Set this variable to `n` when
201
- building outside of the Puppet private network to download directly from
200
+ fetching components from internal mirrors. Set this variable to `n` or `false`
201
+ when building outside of the Puppet private network to download directly from
202
202
  upstream sources.
203
203
 
204
204
  ##### `VANAGON_RETRY_COUNT`
data/bin/build CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
data/bin/build_host_info CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
data/bin/inspect CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
data/bin/render CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
data/bin/repo CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  ENV["PROJECT_ROOT"] = Dir.pwd
4
6
 
5
7
  # Begin warning: This ship script is an internal tool.
@@ -21,7 +23,7 @@ when 'rpm'
21
23
  when 'deb'
22
24
  Pkg::Util::RakeUtils.invoke_task('pl:jenkins:deb_repos')
23
25
  when 'none'
24
- $stdout.puts "Skipping repo generation since repo target is set to 'none'"
26
+ VanagonLogger.warn "Skipping repo generation since repo target is set to 'none'"
25
27
  else
26
28
  Pkg::Util::RakeUtils.invoke_task('pl:jenkins:rpm_repos')
27
29
  Pkg::Util::RakeUtils.invoke_task('pl:jenkins:deb_repos')
data/bin/ship CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
data/bin/sign CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'vanagon/logger'
4
+
3
5
  script = File.basename($0)
4
6
 
5
- warn "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
7
+ VanagonLogger.info "#{script}: Warning: use of stand alone '#{script}' command is deprecated and may be removed.
6
8
  Use: 'vanagon #{script}' instead."
7
9
 
8
10
  exec "vanagon", script, *ARGV
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+
3
+ _vanagon()
4
+ {
5
+ local cur prev projects commands template_arg_commands
6
+
7
+ # COMREPLY is an array variable used to store completions
8
+ # the completion mechanism uses COMPRELY to display its contents as completions
9
+ # COMP_WORDS is an array of all the words typed after the name of the program
10
+ # COMP_CWORD is an index of the COMP_WORDS array pointing to the current word
11
+ COMPREPLY=()
12
+ cur="${COMP_WORDS[COMP_CWORD]}"
13
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
14
+ projects=($({ vanagon list -r | sed 1d; } 2>/dev/null))
15
+ commands="build build_host_info build_requirements completion inspect list render sign ship help"
16
+ template_arg_commands="build build_host_info build_requirements inspect render "
17
+
18
+ # completes with a project if the previous word was a command in template_arg_commands
19
+ if [[ $template_arg_commands =~ (^| )$prev($| ) ]] ; then
20
+ _vanagon_avail_templates_projects=$({ vanagon list -r | sed 1d; } 2>/dev/null)
21
+ # compgen generates completions filtered based on what has been typed by the user
22
+ COMPREPLY=( $(compgen -W "${_vanagon_avail_templates_projects}" -- "${cur}") )
23
+ fi
24
+
25
+ # allows multiple platforms to be tab completed
26
+ if [[ ${#COMP_WORDS[@]} -gt 3 ]] ; then
27
+ _vanagon_avail_templates_platforms=$({ vanagon list -l | sed 1d; } 2>/dev/null)
28
+ COMPREPLY=( $(compgen -W "${_vanagon_avail_templates_platforms}" -- "${cur}") )
29
+ fi
30
+
31
+ if [[ $1 == $prev ]] ; then
32
+ # only show top level commands we are at root
33
+ COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
34
+ fi
35
+ }
36
+
37
+ # assign tab complete function `_vanagon ` to `vanagon` command
38
+ complete -F _vanagon vanagon
@@ -0,0 +1,41 @@
1
+ _vanagon()
2
+ {
3
+ local line commands template_arg_commands projects
4
+
5
+ commands="build build_host_info build_requirements completion inspect list render sign ship help"
6
+ template_arg_commands=("build" "build_host_info" "build_requirements" "inspect" "render")
7
+ projects=($({ vanagon list -r | sed 1d; } 2>/dev/null))
8
+
9
+ # '%p:globbed-files:' sets completion to only offer files matching a
10
+ # described pattern.
11
+ zstyle ':completion:*' file-patterns '%p:globbed-files:'
12
+
13
+ # arguments function provides potential completions to zsh
14
+ # specs are of the form n:message:action
15
+ _arguments -C \
16
+ ": :(${commands})" \
17
+ "*::arg:->args"
18
+
19
+ # (Ie)prevents "invalid subscript"
20
+ if ((template_arg_commands[(Ie)$line[1]])); then
21
+ _vanagon_template_sub_projects
22
+ fi
23
+ if [[ $projects =~ (^| )$line[2]($| ) ]]; then
24
+ _vanagon_template_sub_platforms
25
+ fi
26
+ }
27
+
28
+ _vanagon_template_sub_projects()
29
+ {
30
+ # -W look in certain path but don't append path to tab compelte
31
+ # -g enables file matching pattern
32
+ # (:r) removes the file extension `.rb` from the completion
33
+ _arguments "1: :_files -W $(PWD)/configs/projects/ -g '*.rb(:r)'"
34
+ }
35
+
36
+ _vanagon_template_sub_platforms()
37
+ {
38
+ _arguments "*: :_files -W $(PWD)/configs/platforms/ -g '*.rb(:r)'"
39
+ }
40
+ # compdef registeres the completion function: compdef <function-name> <program>
41
+ compdef _vanagon vanagon
data/lib/vanagon/cli.rb CHANGED
@@ -8,11 +8,15 @@ require 'vanagon/extensions/hashable'
8
8
  require 'vanagon/cli/build'
9
9
  require 'vanagon/cli/build_host_info'
10
10
  require 'vanagon/cli/build_requirements'
11
+ require 'vanagon/cli/completion'
11
12
  require 'vanagon/cli/inspect'
13
+ require 'vanagon/cli/list'
12
14
  require 'vanagon/cli/render'
13
15
  require 'vanagon/cli/ship'
14
16
  require 'vanagon/cli/sign'
15
17
 
18
+ require 'vanagon/logger'
19
+
16
20
 
17
21
  class Vanagon
18
22
  class InvalidArgument < StandardError
@@ -27,7 +31,9 @@ class Vanagon
27
31
  build build a package given a project and platform
28
32
  build_host_info print information about build hosts
29
33
  build_requirements print external packages required to build project
34
+ completion outputs path to tab completion script
30
35
  inspect a build dry-run, printing lots of information about the build
36
+ list shows a list of available projects and platforms
31
37
  render create local versions of packaging artifacts for project
32
38
  sign sign a package
33
39
  ship upload a package to a distribution server
@@ -46,10 +52,14 @@ class Vanagon
46
52
  @sub_parser = Vanagon::CLI::BuildHostInfo.new
47
53
  when 'build_requirements'
48
54
  @sub_parser = Vanagon::CLI::BuildRequirements.new
55
+ when 'completion'
56
+ @sub_parser = Vanagon::CLI::Completion.new
49
57
  when 'inspect'
50
58
  @sub_parser = Vanagon::CLI::Inspect.new
51
59
  when 'render'
52
60
  @sub_parser = Vanagon::CLI::Render.new
61
+ when 'list'
62
+ @sub_parser = Vanagon::CLI::List.new
53
63
  when 'sign'
54
64
  @sub_parser = Vanagon::CLI::Sign.new
55
65
  when 'ship'
@@ -58,7 +68,7 @@ class Vanagon
58
68
  puts DOCUMENTATION
59
69
  exit 0
60
70
  else
61
- warn "vanagon: Error: unknown command: \"#{sub_command}\"\n\n#{DOCUMENTATION}"
71
+ VanagonLogger.error "vanagon: Error: unknown command: \"#{sub_command}\"\n\n#{DOCUMENTATION}"
62
72
  exit 1
63
73
  end
64
74
 
@@ -87,7 +97,7 @@ class Vanagon
87
97
  def parse_options(argv)
88
98
  Docopt.docopt(DOCUMENTATION, { argv: argv, options_first: true })
89
99
  rescue Docopt::Exit => e
90
- puts e.message
100
+ VanagonLogger.error e.message
91
101
  exit 1
92
102
  end
93
103
  end
@@ -1,4 +1,5 @@
1
1
  require 'docopt'
2
+ require 'vanagon/logger'
2
3
 
3
4
  class Vanagon
4
5
  class CLI
@@ -10,7 +11,7 @@ class Vanagon
10
11
  Options:
11
12
  -h, --help Display help
12
13
  -c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
13
- -e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
14
+ -e, --engine ENGINE Custom engine to use [default: always_be_scheduling]
14
15
  -o, --only-build COMPONENT,COMPONENT,...
15
16
  Only build listed COMPONENTs
16
17
  -p, --preserve [RULE] Rule for VM preservation: never, on-failure, always
@@ -19,12 +20,20 @@ class Vanagon
19
20
  -s, --skipcheck Skip the "check" stage when building components
20
21
  -w, --workdir DIRECTORY Working directory on the local host
21
22
  -v, --verbose Only here for backwards compatibility. Does nothing.
23
+
24
+ Engines:
25
+ always_be_scheduling: default engine using Puppet's ABS infrastructure
26
+ docker: a docker container on the local host
27
+ ec2: an Amazon EC2 instance
28
+ hardware: a dedicated hardware device
29
+ local: the local machine, cannot be used with a target
30
+ pooler: [deprecated] Puppet's vmpooler
22
31
  DOCOPT
23
32
 
24
33
  def parse(argv)
25
34
  Docopt.docopt(DOCUMENTATION, { argv: argv })
26
35
  rescue Docopt::Exit => e
27
- puts e.message
36
+ VanagonLogger.error e.message
28
37
  exit 1
29
38
  end
30
39
 
@@ -38,7 +47,7 @@ class Vanagon
38
47
 
39
48
  platform_list.zip(target_list).each do |pair|
40
49
  platform, target = pair
41
- artifact = Vanagon::Driver.new(platform, project, options.merge({ 'target' => target }))
50
+ artifact = Vanagon::Driver.new(platform, project, options.merge({ :target => target }))
42
51
  artifact.run
43
52
  end
44
53
  end
@@ -1,4 +1,5 @@
1
1
  require 'docopt'
2
+ require 'vanagon/logger'
2
3
 
3
4
  class Vanagon
4
5
  class CLI
@@ -10,15 +11,23 @@ class Vanagon
10
11
  Options:
11
12
  -h, --help Display help
12
13
  -c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
13
- -e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
14
+ -e, --engine ENGINE Custom engine to use [default: always_be_scheduling]
14
15
  -w, --workdir DIRECTORY Working directory on the local host
15
16
  -v, --verbose Only here for backwards compatibility. Does nothing.
17
+
18
+ Engines:
19
+ always_be_scheduling: default engine using Puppet's ABS infrastructure
20
+ docker: a docker container on the local host
21
+ ec2: an Amazon EC2 instance
22
+ hardware: a dedicated hardware device
23
+ local: the local machine, cannot be used with a target
24
+ pooler: [deprecated] Puppet's vmpooler
16
25
  DOCOPT
17
26
 
18
27
  def parse(argv)
19
28
  Docopt.docopt(DOCUMENTATION, { argv: argv })
20
29
  rescue Docopt::Exit => e
21
- puts e.message
30
+ VanagonLogger.error e.message
22
31
  exit 1
23
32
  end
24
33
 
@@ -28,7 +37,7 @@ class Vanagon
28
37
 
29
38
  platforms.each do |platform|
30
39
  driver = Vanagon::Driver.new(platform, project, options)
31
- $stdout.puts JSON.generate(driver.build_host_info)
40
+ VanagonLogger.warn JSON.generate(driver.build_host_info)
32
41
  end
33
42
  end
34
43
 
@@ -1,5 +1,6 @@
1
1
  require 'docopt'
2
2
  require 'json'
3
+ require 'vanagon/logger'
3
4
 
4
5
  class Vanagon
5
6
  class CLI
@@ -11,15 +12,23 @@ class Vanagon
11
12
  Options:
12
13
  -h, --help Display help
13
14
  -c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
14
- -e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
15
+ -e, --engine ENGINE Custom engine to use [default: always_be_scheduling]
15
16
  -w, --workdir DIRECTORY Working directory on the local host
16
17
  -v, --verbose Only here for backwards compatibility. Does nothing.
18
+
19
+ Engines:
20
+ always_be_scheduling: default engine using Puppet's ABS infrastructure
21
+ docker: a docker container on the local host
22
+ ec2: an Amazon EC2 instance
23
+ hardware: a dedicated hardware device
24
+ local: the local machine, cannot be used with a target
25
+ pooler: [deprecated] Puppet's vmpooler
17
26
  DOCOPT
18
27
 
19
28
  def parse(argv)
20
29
  Docopt.docopt(DOCUMENTATION, { argv: argv })
21
30
  rescue Docopt::Exit => e
22
- puts e.message
31
+ VanagonLogger.error e.message
23
32
  exit 1
24
33
  end
25
34
 
@@ -39,9 +48,8 @@ class Vanagon
39
48
  end
40
49
  end
41
50
 
42
- $stdout.puts
43
- $stdout.puts "**** External packages required to build #{project} on #{platform}: ***"
44
- $stdout.puts JSON.pretty_generate(build_requirements.flatten.uniq.sort)
51
+ VanagonLogger.warn "**** External packages required to build #{project} on #{platform}: ***"
52
+ VanagonLogger.warn JSON.pretty_generate(build_requirements.flatten.uniq.sort)
45
53
  end
46
54
 
47
55
  def options_translate(docopt_options)
@@ -0,0 +1,44 @@
1
+ require 'docopt'
2
+ require 'vanagon/logger'
3
+
4
+ class Vanagon
5
+ class CLI
6
+ class Completion < Vanagon::CLI
7
+ DOCUMENTATION = <<~DOCOPT.freeze
8
+ Usage:
9
+ completion [options]
10
+
11
+ Options:
12
+ -h, --help Display help
13
+ -s, --shell SHELL Specify shell for completion script [default: bash]
14
+ DOCOPT
15
+
16
+ def parse(argv)
17
+ Docopt.docopt(DOCUMENTATION, { argv: argv })
18
+ rescue Docopt::Exit => e
19
+ VanagonLogger.error e.message
20
+ exit 1
21
+ end
22
+
23
+ def run(options)
24
+ shell = options[:shell].downcase.strip
25
+ completion_file = File.expand_path(File.join('..', '..', '..', '..', 'extras', 'completions', "vanagon.#{shell}"), __FILE__)
26
+
27
+ if File.exist?(completion_file)
28
+ VanagonLogger.warn completion_file
29
+ exit 0
30
+ else
31
+ VanagonLogger.error "Could not find completion file for '#{shell}': No such file #{completion_file}"
32
+ exit 1
33
+ end
34
+ end
35
+
36
+ def options_translate(docopt_options)
37
+ translations = {
38
+ '--shell' => :shell,
39
+ }
40
+ return docopt_options.map { |k, v| [translations[k], v] }.to_h
41
+ end
42
+ end
43
+ end
44
+ end