bolt 2.11.0 → 2.15.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.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +3 -2
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -1
  6. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +52 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +65 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +4 -2
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -2
  10. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +65 -43
  11. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -1
  12. data/bolt-modules/file/lib/puppet/functions/file/read.rb +1 -1
  13. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +1 -1
  14. data/lib/bolt/analytics.rb +21 -2
  15. data/lib/bolt/applicator.rb +19 -7
  16. data/lib/bolt/apply_inventory.rb +4 -0
  17. data/lib/bolt/apply_target.rb +4 -0
  18. data/lib/bolt/bolt_option_parser.rb +4 -3
  19. data/lib/bolt/catalog.rb +81 -68
  20. data/lib/bolt/cli.rb +16 -5
  21. data/lib/bolt/config.rb +216 -75
  22. data/lib/bolt/config/transport/ssh.rb +130 -91
  23. data/lib/bolt/executor.rb +14 -1
  24. data/lib/bolt/inventory/group.rb +1 -1
  25. data/lib/bolt/inventory/inventory.rb +4 -0
  26. data/lib/bolt/inventory/target.rb +4 -0
  27. data/lib/bolt/outputter.rb +3 -0
  28. data/lib/bolt/outputter/rainbow.rb +80 -0
  29. data/lib/bolt/pal.rb +3 -0
  30. data/lib/bolt/project.rb +48 -11
  31. data/lib/bolt/resource_instance.rb +10 -3
  32. data/lib/bolt/shell/powershell/snippets.rb +8 -0
  33. data/lib/bolt/transport/local/connection.rb +2 -1
  34. data/lib/bolt/transport/ssh/connection.rb +35 -0
  35. data/lib/bolt/version.rb +1 -1
  36. data/lib/bolt_spec/bolt_context.rb +1 -1
  37. data/lib/bolt_spec/run.rb +1 -1
  38. metadata +21 -5
@@ -12,94 +12,132 @@ module Bolt
12
12
  # NOTE: All transport configuration options should have a corresponding schema definition
13
13
  # in schemas/bolt-transport-definitions.json
14
14
  OPTIONS = {
15
- "cleanup" => { type: TrueClass,
16
- external: true,
17
- desc: "Whether to clean up temporary files created on targets." },
18
- "connect-timeout" => { type: Integer,
19
- desc: "How long to wait when establishing connections." },
20
- "copy-command" => { external: true,
21
- desc: "Command to use when copying files using ssh-command. "\
22
- "Bolt runs `<copy-command> <src> <dest>`. **This option is experimental.**" },
23
- "disconnect-timeout" => { type: Integer,
24
- desc: "How long to wait before force-closing a connection." },
25
- "host" => { type: String,
26
- external: true,
27
- desc: "Host name." },
28
- "host-key-check" => { type: TrueClass,
29
- external: true,
30
- desc: "Whether to perform host key validation when connecting." },
31
- "extensions" => { type: Array,
32
- desc: "List of file extensions that are accepted for scripts or tasks on Windows. "\
33
- "Scripts with these file extensions rely on the target's file type "\
34
- "association to run. For example, if Python is installed on the system, "\
35
- "a `.py` script runs with `python.exe`. The extensions `.ps1`, `.rb`, and "\
36
- "`.pp` are always allowed and run via hard-coded executables." },
37
- "interpreters" => { type: Hash,
38
- external: true,
39
- desc: "A map of an extension name to the absolute path of an executable, "\
40
- "enabling you to override the shebang defined in a task executable. The "\
41
- "extension can optionally be specified with the `.` character (`.py` and "\
42
- "`py` both map to a task executable `task.py`) and the extension is case "\
43
- "sensitive. When a target's name is `localhost`, Ruby tasks run with the "\
44
- "Bolt Ruby interpreter by default." },
45
- "load-config" => { type: TrueClass,
46
- desc: "Whether to load system SSH configuration." },
47
- "login-shell" => { type: String,
48
- desc: "Which login shell Bolt should expect on the target. "\
49
- "Supported shells are #{LOGIN_SHELLS.join(', ')}. "\
50
- "**This option is experimental.**" },
51
- "password" => { type: String,
52
- desc: "Login password." },
53
- "port" => { type: Integer,
54
- external: true,
55
- desc: "Connection port." },
56
- "private-key" => { external: true,
57
- desc: "Either the path to the private key file to use for authentication, or a "\
58
- "hash with the key `key-data` and the contents of the private key." },
59
- "proxyjump" => { type: String,
60
- desc: "A jump host to proxy connections through, and an optional user to "\
61
- "connect with." },
62
- "run-as" => { type: String,
63
- external: true,
64
- desc: "A different user to run commands as after login." },
65
- "run-as-command" => { type: Array,
66
- external: true,
67
- desc: "The command to elevate permissions. Bolt appends the user and command "\
68
- "strings to the configured `run-as-command` before running it on the "\
69
- "target. This command must not require an interactive password prompt, "\
70
- "and the `sudo-password` option is ignored when `run-as-command` is "\
71
- "specified. The `run-as-command` must be specified as an array." },
72
- "script-dir" => { type: String,
73
- external: true,
74
- desc: "The subdirectory of the tmpdir to use in place of a randomized "\
75
- "subdirectory for uploading and executing temporary files on the "\
76
- "target. It's expected that this directory already exists as a subdir "\
77
- "of tmpdir, which is either configured or defaults to `/tmp`." },
78
- "ssh-command" => { external: true,
79
- desc: "Command and flags to use when SSHing. This enables the external "\
80
- "SSH transport which shells out to the specified command. "\
81
- "**This option is experimental.**" },
82
- "sudo-executable" => { type: String,
83
- external: true,
84
- desc: "The executable to use when escalating to the configured `run-as` "\
85
- "user. This is useful when you want to escalate using the configured "\
86
- "`sudo-password`, since `run-as-command` does not use `sudo-password` "\
87
- "or support prompting. The command executed on the target is "\
88
- "`<sudo-executable> -S -u <user> -p custom_bolt_prompt <command>`. "\
89
- "**This option is experimental.**" },
90
- "sudo-password" => { type: String,
91
- external: true,
92
- desc: "Password to use when changing users via `run-as`." },
93
- "tmpdir" => { type: String,
94
- external: true,
95
- desc: "The directory to upload and execute temporary files on the target." },
96
- "tty" => { type: TrueClass,
97
- desc: "Request a pseudo tty for the session. This option is generally "\
98
- "only used in conjunction with the `run-as` option when the sudoers "\
99
- "policy requires a `tty`." },
100
- "user" => { type: String,
101
- external: true,
102
- desc: "Login user." }
15
+ "cleanup" => { type: TrueClass,
16
+ external: true,
17
+ desc: "Whether to clean up temporary files created on targets." },
18
+ "connect-timeout" => { type: Integer,
19
+ desc: "How long to wait when establishing connections." },
20
+ "copy-command" => { external: true,
21
+ desc: "Command to use when copying files using ssh-command. "\
22
+ "Bolt runs `<copy-command> <src> <dest>`. **This option is "\
23
+ "experimental.**" },
24
+ "disconnect-timeout" => { type: Integer,
25
+ desc: "How long to wait before force-closing a connection." },
26
+ "encryption-algorithms" => { type: Array,
27
+ desc: "List of encryption algorithms to use when establishing a "\
28
+ "connection with a target. Supported algorithms are "\
29
+ "defined by the Ruby net-ssh library and can be viewed "\
30
+ "[here](https://github.com/net-ssh/net-ssh#supported-algorithms). "\
31
+ "All supported, non-deprecated algorithms are available by default when "\
32
+ "this option is not used. To reference all default algorithms "\
33
+ "when using this option, add 'defaults' to the list of supported "\
34
+ "algorithms." },
35
+ "extensions" => { type: Array,
36
+ desc: "List of file extensions that are accepted for scripts or tasks on "\
37
+ "Windows. Scripts with these file extensions rely on the target's file "\
38
+ "type association to run. For example, if Python is installed on the "\
39
+ "system, a `.py` script runs with `python.exe`. The extensions `.ps1`, "\
40
+ "`.rb`, and `.pp` are always allowed and run via hard-coded "\
41
+ "executables." },
42
+ "host" => { type: String,
43
+ external: true,
44
+ desc: "Host name." },
45
+ "host-key-algorithms" => { type: Array,
46
+ desc: "List of host key algorithms to use when establishing a connection "\
47
+ "with a target. Supported algorithms are defined by the Ruby net-ssh "\
48
+ "library "\
49
+ "([docs](https://github.com/net-ssh/net-ssh#supported-algorithms)). "\
50
+ "All supported, non-deprecated algorithms are available by default when "\
51
+ "this option is not used. To reference all default algorithms "\
52
+ "using this option, add 'defaults' to the list of supported "\
53
+ "algorithms." },
54
+ "host-key-check" => { type: TrueClass,
55
+ external: true,
56
+ desc: "Whether to perform host key validation when connecting." },
57
+ "interpreters" => { type: Hash,
58
+ external: true,
59
+ desc: "A map of an extension name to the absolute path of an executable, "\
60
+ "enabling you to override the shebang defined in a task executable. "\
61
+ "The extension can optionally be specified with the `.` character "\
62
+ "(`.py` and `py` both map to a task executable `task.py`) and the "\
63
+ "extension is case sensitive. When a target's name is `localhost`, "\
64
+ "Ruby tasks run with the Bolt Ruby interpreter by default." },
65
+ "kex-algorithms" => { type: Array,
66
+ desc: "List of key exchange algorithms to use when establishing a "\
67
+ "connection to a target. Supported algorithms are defined by the "\
68
+ "Ruby net-ssh library "\
69
+ "([docs](https://github.com/net-ssh/net-ssh#supported-algorithms)). "\
70
+ "All supported, non-deprecated algorithms are available by default when "\
71
+ "this option is not used. To reference all default algorithms "\
72
+ "using this option, add 'defaults' to the list of supported "\
73
+ "algorithms." },
74
+ "load-config" => { type: TrueClass,
75
+ desc: "Whether to load system SSH configuration." },
76
+ "login-shell" => { type: String,
77
+ desc: "Which login shell Bolt should expect on the target. "\
78
+ "Supported shells are #{LOGIN_SHELLS.join(', ')}. "\
79
+ "**This option is experimental.**" },
80
+ "mac-algorithms" => { type: Array,
81
+ desc: "List of message authentication code algorithms to use when "\
82
+ "establishing a connection to a target. Supported algorithms are "\
83
+ "defined by the Ruby net-ssh library "\
84
+ "([docs](https://github.com/net-ssh/net-ssh#supported-algorithms)). "\
85
+ "All supported, non-deprecated algorithms are available by default when "\
86
+ "this option is not used. To reference all default algorithms "\
87
+ "using this option, add 'defaults' to the list of supported "\
88
+ "algorithms." },
89
+ "password" => { type: String,
90
+ desc: "Login password." },
91
+ "port" => { type: Integer,
92
+ external: true,
93
+ desc: "Connection port." },
94
+ "private-key" => { external: true,
95
+ desc: "Either the path to the private key file to use for authentication, or "\
96
+ "a hash with the key `key-data` and the contents of the private key." },
97
+ "proxyjump" => { type: String,
98
+ desc: "A jump host to proxy connections through, and an optional user to "\
99
+ "connect with." },
100
+ "run-as" => { type: String,
101
+ external: true,
102
+ desc: "A different user to run commands as after login." },
103
+ "run-as-command" => { type: Array,
104
+ external: true,
105
+ desc: "The command to elevate permissions. Bolt appends the user and command "\
106
+ "strings to the configured `run-as-command` before running it on the "\
107
+ "target. This command must not require an interactive password prompt, "\
108
+ "and the `sudo-password` option is ignored when `run-as-command` is "\
109
+ "specified. The `run-as-command` must be specified as an array." },
110
+ "script-dir" => { type: String,
111
+ external: true,
112
+ desc: "The subdirectory of the tmpdir to use in place of a randomized "\
113
+ "subdirectory for uploading and executing temporary files on the "\
114
+ "target. It's expected that this directory already exists as a subdir "\
115
+ "of tmpdir, which is either configured or defaults to `/tmp`." },
116
+ "ssh-command" => { external: true,
117
+ desc: "Command and flags to use when SSHing. This enables the external "\
118
+ "SSH transport which shells out to the specified command. "\
119
+ "**This option is experimental.**" },
120
+ "sudo-executable" => { type: String,
121
+ external: true,
122
+ desc: "The executable to use when escalating to the configured `run-as` "\
123
+ "user. This is useful when you want to escalate using the configured "\
124
+ "`sudo-password`, since `run-as-command` does not use `sudo-password` "\
125
+ "or support prompting. The command executed on the target is "\
126
+ "`<sudo-executable> -S -u <user> -p custom_bolt_prompt <command>`. "\
127
+ "**This option is experimental.**" },
128
+ "sudo-password" => { type: String,
129
+ external: true,
130
+ desc: "Password to use when changing users via `run-as`." },
131
+ "tmpdir" => { type: String,
132
+ external: true,
133
+ desc: "The directory to upload and execute temporary files on the target." },
134
+ "tty" => { type: TrueClass,
135
+ desc: "Request a pseudo tty for the session. This option is generally "\
136
+ "only used in conjunction with the `run-as` option when the sudoers "\
137
+ "policy requires a `tty`." },
138
+ "user" => { type: String,
139
+ external: true,
140
+ desc: "Login user." }
103
141
  }.freeze
104
142
 
105
143
  DEFAULTS = {
@@ -142,10 +180,11 @@ module Bolt
142
180
  "Unsupported login-shell #{@config['login-shell']}. Supported shells are #{LOGIN_SHELLS.join(', ')}"
143
181
  end
144
182
 
145
- if (run_as_cmd = @config['run-as-command'])
146
- unless run_as_cmd.all? { |n| n.is_a?(String) }
183
+ %w[encryption-algorithms host-key-algorithms kex-algorithms mac-algorithms run-as-command].each do |opt|
184
+ next unless @config.key?(opt)
185
+ unless @config[opt].all? { |n| n.is_a?(String) }
147
186
  raise Bolt::ValidationError,
148
- "run-as-command must be an Array of Strings, received #{run_as_cmd.class} #{run_as_cmd.inspect}"
187
+ "#{opt} must be an Array of Strings, received #{@config[opt].inspect}"
149
188
  end
150
189
  end
151
190
 
@@ -34,7 +34,8 @@ module Bolt
34
34
 
35
35
  def initialize(concurrency = 1,
36
36
  analytics = Bolt::Analytics::NoopClient.new,
37
- noop = false)
37
+ noop = false,
38
+ modified_concurrency = false)
38
39
  # lazy-load expensive gem code
39
40
  require 'concurrent'
40
41
 
@@ -64,6 +65,9 @@ module Bolt
64
65
  Concurrent.global_immediate_executor
65
66
  end
66
67
  @logger.debug { "Started with #{concurrency} max thread(s)" }
68
+
69
+ @concurrency = concurrency
70
+ @warn_concurrency = modified_concurrency
67
71
  end
68
72
 
69
73
  def transport(transport)
@@ -102,6 +106,15 @@ module Bolt
102
106
  # defined by the transport. Yields each batch, along with the corresponding
103
107
  # transport, to the block in turn and returns an array of result promises.
104
108
  def queue_execute(targets)
109
+ if @warn_concurrency && targets.length > @concurrency
110
+ @warn_concurrency = false
111
+ @logger.warn("The ulimit is low, which may cause file limit issues. Default concurrency has been set to "\
112
+ "'#{@concurrency}' to mitigate those issues, which may cause Bolt to run slow. "\
113
+ "Disable this warning by configuring ulimit using 'ulimit -n <limit>' in your shell "\
114
+ "configuration, or by configuring Bolt's concurrency. "\
115
+ "See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details.")
116
+ end
117
+
105
118
  targets.group_by(&:transport).flat_map do |protocol, protocol_targets|
106
119
  transport = transport(protocol)
107
120
  report_transport(transport, protocol_targets.count)
@@ -16,7 +16,7 @@ module Bolt
16
16
  DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
17
17
  TARGET_KEYS = DATA_KEYS + %w[name alias uri]
18
18
  GROUP_KEYS = DATA_KEYS + %w[name groups targets]
19
- CONFIG_KEYS = Bolt::Config::TRANSPORT_CONFIG.keys + ['transport']
19
+ CONFIG_KEYS = Bolt::Config::INVENTORY_CONFIG.keys
20
20
 
21
21
  def initialize(input, plugins)
22
22
  @logger = Logging.logger[self]
@@ -323,6 +323,10 @@ module Bolt
323
323
  def resources(target)
324
324
  @targets[target.name].resources
325
325
  end
326
+
327
+ def resource(target, type, title)
328
+ @targets[target.name].resource(type, title)
329
+ end
326
330
  end
327
331
  end
328
332
  end
@@ -90,6 +90,10 @@ module Bolt
90
90
  end
91
91
  end
92
92
 
93
+ def resource(type, title)
94
+ resources[Bolt::ResourceInstance.format_reference(type, title)]
95
+ end
96
+
93
97
  def plugin_hooks
94
98
  # Merge plugin_hooks from the config file with any defined by the group
95
99
  # or assigned dynamically to the target
@@ -8,6 +8,8 @@ module Bolt
8
8
  Bolt::Outputter::Human.new(color, verbose, trace)
9
9
  when 'json'
10
10
  Bolt::Outputter::JSON.new(color, verbose, trace)
11
+ when 'rainbow'
12
+ Bolt::Outputter::Rainbow.new(color, verbose, trace)
11
13
  when nil
12
14
  raise "Cannot use outputter before parsing."
13
15
  end
@@ -24,4 +26,5 @@ end
24
26
 
25
27
  require 'bolt/outputter/human'
26
28
  require 'bolt/outputter/json'
29
+ require 'bolt/outputter/rainbow'
27
30
  require 'bolt/outputter/logger'
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/pal'
4
+ require 'paint'
5
+
6
+ module Bolt
7
+ class Outputter
8
+ class Rainbow < Bolt::Outputter::Human
9
+ def initialize(color, verbose, trace, stream = $stdout)
10
+ super
11
+ @line_color = 0
12
+ @color = 0
13
+ @state = :normal
14
+ end
15
+
16
+ # The algorithm is from lolcat (https://github.com/busyloop/lolcat)
17
+ # lolcat is released with WTFPL
18
+ def rainbow
19
+ red = Math.sin(0.3 * @color + 0) * 127 + 128
20
+ green = Math.sin(0.3 * @color + 2 * Math::PI / 3) * 127 + 128
21
+ blue = Math.sin(0.3 * @color + 4 * Math::PI / 3) * 127 + 128
22
+ @color += 1 / 8.0
23
+ format("%<red>02X%<green>02X%<blue>02X", red: red, green: green, blue: blue)
24
+ end
25
+
26
+ def colorize(color, string)
27
+ if @color && @stream.isatty
28
+ if %i[green rainbow].include?(color)
29
+ a = string.chars.map do |c|
30
+ case @state
31
+ when :normal
32
+ if c == "\e"
33
+ @state = :ansi
34
+ elsif c == "\n"
35
+ @line_color += 1
36
+ @color = @line_color
37
+ c
38
+ else
39
+ Paint[c, rainbow]
40
+ end
41
+ when :ansi
42
+ @state = :normal if c == 'm'
43
+ end
44
+ end
45
+ a.join('')
46
+ else
47
+ "\033[#{COLORS[color]}m#{string}\033[0m"
48
+ end
49
+ else
50
+ string
51
+ end
52
+ end
53
+
54
+ def print_summary(results, elapsed_time = nil)
55
+ ok_set = results.ok_set
56
+ unless ok_set.empty?
57
+ @stream.puts colorize(:rainbow, format('Successful on %<size>d target%<plural>s: %<names>s',
58
+ size: ok_set.size,
59
+ plural: ok_set.size == 1 ? '' : 's',
60
+ names: ok_set.targets.map(&:safe_name).join(',')))
61
+ end
62
+
63
+ error_set = results.error_set
64
+ unless error_set.empty?
65
+ @stream.puts colorize(:red,
66
+ format('Failed on %<size>d target%<plural>s: %<names>s',
67
+ size: error_set.size,
68
+ plural: error_set.size == 1 ? '' : 's',
69
+ names: error_set.targets.map(&:safe_name).join(',')))
70
+ end
71
+
72
+ total_msg = format('Ran on %<size>d target%<plural>s',
73
+ size: results.size,
74
+ plural: results.size == 1 ? '' : 's')
75
+ total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
76
+ @stream.puts colorize(:rainbow, total_msg)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -190,6 +190,7 @@ module Bolt
190
190
  # versions of "core" types, which are already present on the agent,
191
191
  # but may cause issues on Puppet 5 agents.
192
192
  @original_modulepath,
193
+ @project,
193
194
  pdb_client,
194
195
  @hiera_config,
195
196
  @max_compiles,
@@ -359,6 +360,7 @@ module Bolt
359
360
  name = param.name
360
361
  if signature_params.include?(name)
361
362
  params[name] = { 'type' => param.types.first }
363
+ params[name]['sensitive'] = param.types.first =~ /\ASensitive(\[.*\])?\z/ ? true : false
362
364
  params[name]['default_value'] = defaults[name] if defaults.key?(name)
363
365
  params[name]['description'] = param.text unless param.text.empty?
364
366
  else
@@ -390,6 +392,7 @@ module Bolt
390
392
  param.type_expr
391
393
  end
392
394
  params[name] = { 'type' => type_str }
395
+ params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
393
396
  params[name]['default_value'] = param.value
394
397
  params[name]['description'] = param.description if param.description
395
398
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'bolt/pal'
5
+ require 'bolt/config'
5
6
 
6
7
  module Bolt
7
8
  class Project
@@ -12,11 +13,14 @@ module Bolt
12
13
  "tasks" => "An array of task names to whitelist. Whitelisted plans are included in `bolt task show` output"
13
14
  }.freeze
14
15
 
15
- attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config,
16
- :puppetfile, :rerunfile, :type, :resource_types
16
+ attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
17
+ :puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file
17
18
 
18
19
  def self.default_project
19
- Project.new(File.join('~', '.puppetlabs', 'bolt'), 'user')
20
+ create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
21
+ # If homedir isn't defined use the system config path
22
+ rescue ArgumentError
23
+ create_project(Bolt::Config.system_path, 'system')
20
24
  end
21
25
 
22
26
  # Search recursively up the directory hierarchy for the Project. Look for a
@@ -26,9 +30,9 @@ module Bolt
26
30
  def self.find_boltdir(dir)
27
31
  dir = Pathname.new(dir)
28
32
  if (dir + BOLTDIR_NAME).directory?
29
- new(dir + BOLTDIR_NAME, 'embedded')
33
+ create_project(dir + BOLTDIR_NAME, 'embedded')
30
34
  elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
31
- new(dir, 'local')
35
+ create_project(dir, 'local')
32
36
  elsif dir.root?
33
37
  default_project
34
38
  else
@@ -36,9 +40,25 @@ module Bolt
36
40
  end
37
41
  end
38
42
 
39
- def initialize(path, type = 'option')
43
+ def self.create_project(path, type = 'option')
44
+ fullpath = Pathname.new(path).expand_path
45
+ project_file = File.join(fullpath, 'bolt-project.yaml')
46
+ data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
47
+ new(data, path, type)
48
+ end
49
+
50
+ def initialize(raw_data, path, type = 'option')
40
51
  @path = Pathname.new(path).expand_path
41
- @config_file = @path + 'bolt.yaml'
52
+ @project_file = @path + 'bolt-project.yaml'
53
+
54
+ @warnings = []
55
+ if (@path + 'bolt.yaml').file? && project_file?
56
+ msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
57
+ "Transport config should be set in inventory.yaml, all other config should be set in "\
58
+ "bolt-project.yaml."
59
+ @warnings << { msg: msg }
60
+ end
61
+
42
62
  @inventory_file = @path + 'inventory.yaml'
43
63
  @modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
44
64
  @hiera_config = @path + 'hiera.yaml'
@@ -47,9 +67,26 @@ module Bolt
47
67
  @resource_types = @path + '.resource_types'
48
68
  @type = type
49
69
 
50
- @project_file = @path + 'bolt-project.yaml'
51
- @data = Bolt::Util.read_optional_yaml_hash(File.expand_path(@project_file), 'project') || {}
52
- validate if load_as_module?
70
+ tc = Bolt::Config::INVENTORY_CONFIG.keys & raw_data.keys
71
+ if tc.any?
72
+ msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
73
+ @warnings << { msg: msg }
74
+ end
75
+
76
+ @data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_CONFIG.keys.include?(k) }
77
+
78
+ # Once bolt.yaml deprecation is removed, this attribute should be removed
79
+ # and replaced with .project_file in lib/bolt/config.rb
80
+ @config_file = if (Bolt::Config::OPTIONS.keys & @data.keys).any?
81
+ if (@path + 'bolt.yaml').file?
82
+ msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
83
+ @warnings << { msg: msg }
84
+ end
85
+ @project_file
86
+ else
87
+ @path + 'bolt.yaml'
88
+ end
89
+ validate if project_file?
53
90
  end
54
91
 
55
92
  def to_s
@@ -67,7 +104,7 @@ module Bolt
67
104
  end
68
105
  alias == eql?
69
106
 
70
- def load_as_module?
107
+ def project_file?
71
108
  @project_file.file?
72
109
  end
73
110