bolt 3.14.1 → 3.17.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 +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +137 -104
- data/guides/debugging.yaml +27 -0
- data/guides/inventory.yaml +23 -0
- data/guides/links.yaml +12 -0
- data/guides/logging.yaml +17 -0
- data/guides/module.yaml +18 -0
- data/guides/modulepath.yaml +24 -0
- data/guides/project.yaml +21 -0
- data/guides/targets.yaml +28 -0
- data/guides/transports.yaml +22 -0
- data/lib/bolt/analytics.rb +2 -19
- data/lib/bolt/application.rb +634 -0
- data/lib/bolt/bolt_option_parser.rb +28 -4
- data/lib/bolt/cli.rb +592 -788
- data/lib/bolt/fiber_executor.rb +7 -3
- data/lib/bolt/inventory/inventory.rb +68 -39
- data/lib/bolt/inventory.rb +2 -9
- data/lib/bolt/module_installer/puppetfile.rb +24 -10
- data/lib/bolt/outputter/human.rb +83 -32
- data/lib/bolt/outputter/json.rb +63 -38
- data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
- data/lib/bolt/pal.rb +31 -11
- data/lib/bolt/plan_creator.rb +84 -25
- data/lib/bolt/plan_future.rb +11 -6
- data/lib/bolt/plan_result.rb +1 -1
- data/lib/bolt/plugin/task.rb +1 -1
- data/lib/bolt/plugin.rb +11 -17
- data/lib/bolt/project.rb +0 -7
- data/lib/bolt/result_set.rb +2 -1
- data/lib/bolt/transport/local/connection.rb +17 -1
- data/lib/bolt/transport/orch/connection.rb +13 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +12 -0
- data/lib/bolt_server/schemas/action-apply.json +32 -0
- data/lib/bolt_server/schemas/action-apply_prep.json +19 -0
- data/lib/bolt_server/transport_app.rb +113 -24
- data/lib/bolt_spec/bolt_context.rb +1 -1
- data/lib/bolt_spec/run.rb +1 -1
- metadata +14 -3
- data/lib/bolt/secret.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '05192d9aa9b3da414b55a21bd75fe10238e8044eb49f75f92be7fac1d0576866'
|
4
|
+
data.tar.gz: 49302c6d080fda48080d5281c9480ce35a734caefa5be9f6f141aaea51f99b70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd0966b7be3a7bba5a1bb0f040a2e8bd8c22708fa1a79492f04b8448037105ac66e12af74969787d3ab4efa69bfcd393c51168154c548d7d35f6f36fc6648c0c
|
7
|
+
data.tar.gz: e7f2741c6e7a2416ebbc61f7f008e1a550bde1897e906e53a8df0dc837cd4fcb0718c783f301cdbde1703ed95a65530cbad9e727ae5978a6c414988bc0206e23
|
data/Puppetfile
CHANGED
@@ -35,7 +35,7 @@ mod 'puppetlabs-stdlib', '7.1.0'
|
|
35
35
|
mod 'puppetlabs-aws_inventory', '0.7.0'
|
36
36
|
mod 'puppetlabs-azure_inventory', '0.5.0'
|
37
37
|
mod 'puppetlabs-gcloud_inventory', '0.3.0'
|
38
|
-
mod 'puppetlabs-http_request', '0.3.
|
38
|
+
mod 'puppetlabs-http_request', '0.3.1'
|
39
39
|
mod 'puppetlabs-pkcs7', '0.1.2'
|
40
40
|
mod 'puppetlabs-secure_env_vars', '0.2.0'
|
41
41
|
mod 'puppetlabs-terraform', '0.6.1'
|
@@ -8,16 +8,18 @@ require 'bolt/task'
|
|
8
8
|
# installed using either the configured plugin or the `task` plugin with the
|
9
9
|
# `puppet_agent::install` task.
|
10
10
|
#
|
11
|
-
# Agent installation will be skipped if the target includes the `puppet-agent`
|
12
|
-
# property of its transport (PCP) or by explicitly setting
|
11
|
+
# Agent installation will be skipped if the target includes the `puppet-agent`
|
12
|
+
# feature, either as a property of its transport (PCP) or by explicitly setting
|
13
|
+
# it as a feature in Bolt's inventory.
|
13
14
|
#
|
14
15
|
# > **Note:** Not available in apply block
|
15
16
|
Puppet::Functions.create_function(:apply_prep) do
|
16
17
|
# @param targets A pattern or array of patterns identifying a set of targets.
|
17
18
|
# @param options Options hash.
|
19
|
+
# @option options [Boolean] _catch_errors Whether to catch raised errors.
|
18
20
|
# @option options [Array] _required_modules An array of modules to sync to the target.
|
19
21
|
# @option options [String] _run_as User to run as using privilege escalation.
|
20
|
-
# @return [
|
22
|
+
# @return [Bolt::ResultSet]
|
21
23
|
# @example Prepare targets by name.
|
22
24
|
# apply_prep('target1,target2')
|
23
25
|
dispatch :apply_prep do
|
@@ -25,138 +27,169 @@ Puppet::Functions.create_function(:apply_prep) do
|
|
25
27
|
optional_param 'Hash[String, Data]', :options
|
26
28
|
end
|
27
29
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
30
|
+
def apply_prep(target_spec, options = {})
|
31
|
+
unless Puppet[:tasks]
|
32
|
+
raise Puppet::ParseErrorWithIssue
|
33
|
+
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'apply_prep')
|
34
|
+
end
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
end
|
36
|
+
options = options.slice(*%w[_catch_errors _required_modules _run_as])
|
37
|
+
targets = inventory.get_targets(target_spec)
|
35
38
|
|
36
|
-
|
37
|
-
tasksig = script_compiler.task_signature(name)
|
38
|
-
raise Bolt::Error.new("Task '#{name}' could not be found", 'bolt/apply-prep') unless tasksig
|
39
|
+
executor.report_function_call(self.class.name)
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
executor.log_action('install puppet and gather facts', targets) do
|
42
|
+
executor.without_default_logging do
|
43
|
+
install_results = install_agents(targets, options)
|
44
|
+
facts_results = get_facts(install_results.ok_set.targets, options)
|
45
|
+
|
46
|
+
Bolt::ResultSet.new(install_results.error_set.results + facts_results.results)
|
47
|
+
end
|
44
48
|
end
|
49
|
+
end
|
45
50
|
|
46
|
-
|
51
|
+
def applicator
|
52
|
+
@applicator ||= Puppet.lookup(:apply_executor)
|
47
53
|
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
inventory.set_feature(target, 'puppet-agent')
|
55
|
+
def executor
|
56
|
+
@executor ||= Puppet.lookup(:bolt_executor)
|
52
57
|
end
|
53
|
-
# rubocop:enable Naming/AccessorMethodName
|
54
58
|
|
59
|
+
def inventory
|
60
|
+
@inventory ||= Puppet.lookup(:bolt_inventory)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Runs a task. This method is called by the puppet_library hook.
|
64
|
+
#
|
55
65
|
def run_task(targets, task, args = {}, options = {})
|
56
66
|
executor.run_task(targets, task, args, options)
|
57
67
|
end
|
58
68
|
|
59
|
-
# Returns true if the target has the puppet-agent feature defined, either from
|
60
|
-
|
69
|
+
# Returns true if the target has the puppet-agent feature defined, either from
|
70
|
+
# inventory or transport.
|
71
|
+
#
|
72
|
+
private def agent?(target)
|
61
73
|
inventory.features(target).include?('puppet-agent') ||
|
62
|
-
|
74
|
+
executor.transport(target.transport).provided_features.include?('puppet-agent') ||
|
75
|
+
target.remote?
|
63
76
|
end
|
64
77
|
|
65
|
-
|
66
|
-
|
78
|
+
# Generate the plugin tarball.
|
79
|
+
#
|
80
|
+
private def build_plugin_tarball(required_modules)
|
81
|
+
if required_modules.any?
|
82
|
+
Puppet.debug("Syncing only required modules: #{required_modules.join(',')}.")
|
83
|
+
end
|
84
|
+
|
85
|
+
tarball = applicator.build_plugin_tarball do |mod|
|
86
|
+
next unless required_modules.empty? || required_modules.include?(mod.name)
|
87
|
+
search_dirs = []
|
88
|
+
search_dirs << mod.plugins if mod.plugins?
|
89
|
+
search_dirs << mod.pluginfacts if mod.pluginfacts?
|
90
|
+
search_dirs
|
91
|
+
end
|
92
|
+
|
93
|
+
Puppet::Pops::Types::PSensitiveType::Sensitive.new(tarball)
|
67
94
|
end
|
68
95
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
96
|
+
# Install the puppet-agent package on targets that need it.
|
97
|
+
#
|
98
|
+
private def install_agents(targets, options)
|
99
|
+
results = []
|
100
|
+
|
101
|
+
agent_targets, agentless_targets = targets.partition { |target| agent?(target) }
|
102
|
+
|
103
|
+
agent_targets.each do |target|
|
104
|
+
Puppet.debug("Puppet Agent feature declared for #{target}")
|
105
|
+
results << Bolt::Result.new(target)
|
73
106
|
end
|
74
107
|
|
75
|
-
|
76
|
-
|
108
|
+
unless agentless_targets.empty?
|
109
|
+
hooks, errors = get_hooks(agentless_targets, options)
|
110
|
+
hook_results = run_hooks(hooks)
|
77
111
|
|
78
|
-
|
112
|
+
hook_results.each do |result|
|
113
|
+
next unless result.ok?
|
114
|
+
inventory.set_feature(result.target, 'puppet-agent')
|
115
|
+
end
|
79
116
|
|
80
|
-
|
117
|
+
results.concat(hook_results).concat(errors)
|
118
|
+
end
|
81
119
|
|
82
|
-
|
120
|
+
Bolt::ResultSet.new(results).tap do |resultset|
|
121
|
+
unless resultset.ok? || options['_catch_errors']
|
122
|
+
raise Bolt::RunFailure.new(resultset.error_set, 'apply_prep')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
83
126
|
|
84
|
-
|
85
|
-
|
86
|
-
|
127
|
+
# Retrieve facts from each target and add them to inventory.
|
128
|
+
#
|
129
|
+
private def get_facts(targets, options)
|
130
|
+
return Bolt::ResultSet.new([]) unless targets.any?
|
131
|
+
|
132
|
+
task = applicator.custom_facts_task
|
133
|
+
args = { 'plugins' => build_plugin_tarball(options.delete('_required_modules').to_a) }
|
134
|
+
results = run_task(targets, task, args, options)
|
135
|
+
|
136
|
+
unless results.ok? || options['_catch_errors']
|
137
|
+
raise Bolt::RunFailure.new(results, 'run_task', task.name)
|
87
138
|
end
|
88
139
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
140
|
+
results.each do |result|
|
141
|
+
next unless result.ok?
|
142
|
+
|
143
|
+
if unsupported_puppet?(result['clientversion'])
|
144
|
+
Bolt::Logger.deprecate(
|
145
|
+
"unsupported_puppet",
|
146
|
+
"Detected unsupported Puppet agent version #{result['clientversion']} on target "\
|
147
|
+
"#{result.target}. Bolt supports Puppet agent 6.0.0 and higher."
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
inventory.add_facts(result.target, result.value)
|
96
152
|
end
|
97
153
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
raise Bolt::RunFailure.new(set.error_set, 'apply_prep') unless set.ok
|
132
|
-
|
133
|
-
need_install_targets.each { |target| set_agent_feature(target) }
|
134
|
-
end
|
135
|
-
|
136
|
-
task = applicator.custom_facts_task
|
137
|
-
arguments = { 'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins) }
|
138
|
-
results = run_task(targets, task, arguments, options)
|
139
|
-
|
140
|
-
# TODO: Standardize RunFailure type with error above
|
141
|
-
raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
|
142
|
-
|
143
|
-
results.each do |result|
|
144
|
-
# Log a warning if the client version is < 6
|
145
|
-
if unsupported_puppet?(result['clientversion'])
|
146
|
-
Bolt::Logger.deprecate(
|
147
|
-
"unsupported_puppet",
|
148
|
-
"Detected unsupported Puppet agent version #{result['clientversion']} on target "\
|
149
|
-
"#{result.target}. Bolt supports Puppet agent 6.0.0 and higher."
|
150
|
-
)
|
151
|
-
end
|
152
|
-
|
153
|
-
inventory.add_facts(result.target, result.value)
|
154
|
-
end
|
154
|
+
results
|
155
|
+
end
|
156
|
+
|
157
|
+
# Return a list of targets and their puppet_library hooks.
|
158
|
+
#
|
159
|
+
private def get_hooks(targets, options)
|
160
|
+
hooks = []
|
161
|
+
errors = []
|
162
|
+
|
163
|
+
targets.each do |target|
|
164
|
+
plugin_opts = target.plugin_hooks.fetch('puppet_library').dup
|
165
|
+
plugin_name = plugin_opts.delete('plugin')
|
166
|
+
hook = inventory.plugins.get_hook(plugin_name, :puppet_library)
|
167
|
+
|
168
|
+
hooks << { 'target' => target,
|
169
|
+
'proc' => hook.call(plugin_opts.merge(options), target, self) }
|
170
|
+
rescue StandardError => e
|
171
|
+
errors << Bolt::Result.from_exception(target, e)
|
172
|
+
end
|
173
|
+
|
174
|
+
[hooks, errors]
|
175
|
+
end
|
176
|
+
|
177
|
+
# Runs the puppet_library hook for each target, returning the result
|
178
|
+
# of each.
|
179
|
+
#
|
180
|
+
private def run_hooks(hooks)
|
181
|
+
require 'concurrent'
|
182
|
+
pool = Concurrent::ThreadPoolExecutor.new
|
183
|
+
|
184
|
+
futures = hooks.map do |hook|
|
185
|
+
Concurrent::Future.execute(executor: pool) do
|
186
|
+
hook['proc'].call
|
155
187
|
end
|
156
188
|
end
|
157
189
|
|
158
|
-
|
159
|
-
|
190
|
+
futures.zip(hooks).map do |future, hook|
|
191
|
+
future.value || Bolt::Result.from_exception(hook['target'], future.reason)
|
192
|
+
end
|
160
193
|
end
|
161
194
|
|
162
195
|
# Returns true if the client's major version is < 6.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
topic: debugging
|
3
|
+
guide: |
|
4
|
+
When Bolt isn't behaving as expected, there are a few helpful commands and
|
5
|
+
logs that can help identify common issues. The first place to look is in
|
6
|
+
`<PROJECT>/bolt-debug.log`, which contains debug-level logs from the last Bolt
|
7
|
+
run. This log file includes where the Bolt project was loaded from, the
|
8
|
+
location of any configuration or inventory files that were loaded, and the
|
9
|
+
modulepath that modules were loaded from.
|
10
|
+
|
11
|
+
If you're having issues with loading targets or target configuration, you
|
12
|
+
can see the list of resolved Bolt target names by running `bolt inventory
|
13
|
+
show` on *nix systems or `Get-BoltInventory` in PowerShell. To see the
|
14
|
+
resolved configuration for each target, run the command with the `--detail` or
|
15
|
+
`-Detail` options.
|
16
|
+
|
17
|
+
Lastly, if you're having trouble loading Bolt content you can use `bolt
|
18
|
+
module show` on *nix systems or `Get-BoltModule` in PowerShell to see the list
|
19
|
+
of loaded modules, including where they were loaded from. You can also use
|
20
|
+
`bolt task show` or `Get-BoltTask` to list loaded tasks, and `bolt plan show`
|
21
|
+
or `Get-BoltPlan` to list loaded plans.
|
22
|
+
|
23
|
+
Visit the linked documentation for more in-depth troubleshooting help for
|
24
|
+
specific issues.
|
25
|
+
|
26
|
+
documentation:
|
27
|
+
- https://pup.pt/bolt-troubleshooting
|
@@ -0,0 +1,23 @@
|
|
1
|
+
---
|
2
|
+
topic: inventory
|
3
|
+
guide: |
|
4
|
+
The inventory describes the targets that you run Bolt commands on, along
|
5
|
+
with any data and configuration for the targets. Targets in an inventory can
|
6
|
+
belong to one or more groups, allowing you to share data and configuration
|
7
|
+
across multiple targets and to specify multiple targets for your Bolt
|
8
|
+
commands without the need to list each target individually.
|
9
|
+
|
10
|
+
In most cases, Bolt loads the inventory from an inventory file in your Bolt
|
11
|
+
project. The inventory file is a YAML file named 'inventory.yaml'. Because
|
12
|
+
Bolt loads the inventory file from a Bolt project, you must have an existing
|
13
|
+
project configuration file named 'bolt-project.yaml' alongside the inventory
|
14
|
+
file.
|
15
|
+
|
16
|
+
When Bolt loads inventory, it loads the entire inventory, not just the
|
17
|
+
groups and targets specified on the command line. If you've defined a
|
18
|
+
target in multiple groups, this might result in target configuration that
|
19
|
+
is different than expected.
|
20
|
+
|
21
|
+
documentation:
|
22
|
+
- https://pup.pt/bolt-inventory
|
23
|
+
- https://pup.pt/bolt-inventory-reference
|
data/guides/links.yaml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
---
|
2
|
+
topic: links
|
3
|
+
guide: |
|
4
|
+
Bolt documentation https://bolt.guide
|
5
|
+
Ask a question in #bolt https://slack.puppet.com/
|
6
|
+
Contribute at https://github.com/puppetlabs/bolt/
|
7
|
+
Getting Started Guide https://pup.pt/bolt-getting-started
|
8
|
+
Reference Documentation https://pup.pt/bolt-reference
|
9
|
+
Troubleshooting Bolt https://pup.pt/bolt-troubleshooting
|
10
|
+
Bolt Developer Updates https://pup.pt/bolt-dev-updates
|
11
|
+
Bolt Changelog https://pup.pt/bolt-changelog
|
12
|
+
Bolt Examples https://pup.pt/bolt-examples
|
data/guides/logging.yaml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
topic: logging
|
3
|
+
guide: |
|
4
|
+
Bolt prints messages both to the console and to log files. Messages can
|
5
|
+
either come from Bolt's 'outputter', which logs user-facing messages like
|
6
|
+
progress and results, or from the 'logger', which logs warnings, errors, and
|
7
|
+
log-structured output to log files. Both of these message streams are
|
8
|
+
configurable.
|
9
|
+
|
10
|
+
By default, Bolt logs to the console at 'warn' level and writes a log file to
|
11
|
+
'<project>/bolt-debug.log' at 'debug' level. Unless you are running a plan,
|
12
|
+
Bolt runs in verbose mode by default.
|
13
|
+
|
14
|
+
To learn more about projects, see the 'project' guide.
|
15
|
+
|
16
|
+
documentation:
|
17
|
+
- https://pup.pt/bolt-logging
|
data/guides/module.yaml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
topic: module
|
3
|
+
guide: |
|
4
|
+
Modules are shareable, reusable packages of Puppet content. They can include
|
5
|
+
tasks, plans, functions, and other types of content that you can use in your
|
6
|
+
project. You can download and install modules to your project from the
|
7
|
+
Puppet Forge or write your own modules. Bolt also ships with several helpful
|
8
|
+
modules pre-installed that are available to all of your projects.
|
9
|
+
|
10
|
+
Bolt makes it easy to manage the modules that your project depends on. You
|
11
|
+
can use Bolt commands to install a project's modules, add new modules to a
|
12
|
+
project, and view the modules that are available to the project.
|
13
|
+
|
14
|
+
To learn more about managing modules in a project, see the documentation.
|
15
|
+
To learn how modules are loaded by Bolt, see the 'modulepath' guide.
|
16
|
+
|
17
|
+
documentation:
|
18
|
+
- https://pup.pt/bolt-modules
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
topic: modulepath
|
3
|
+
guide: |
|
4
|
+
The modulepath is an ordered list of directories that Bolt loads modules
|
5
|
+
from. When Bolt runs a command, it automatically loads modules from the
|
6
|
+
modulepath.
|
7
|
+
|
8
|
+
While Bolt has a default modulepath, you can also configure your own
|
9
|
+
modulepath, which can include directories within the project or directories
|
10
|
+
elsewhere on your system. Regardless of whether your project uses a default
|
11
|
+
or configured modulepath, Bolt automatically adds directories to the
|
12
|
+
modulepath. This includes modules containing core Bolt content, which is
|
13
|
+
added to the beginning of the modulepath, and bundled content, which is
|
14
|
+
added to the end of the modulepath.
|
15
|
+
|
16
|
+
Modules loaded from a directory listed earlier in the modulepath take
|
17
|
+
precedence over modules with the same name loaded from a directory later in
|
18
|
+
the modulepath. Bolt will not warn or error when two modules share a name
|
19
|
+
and instead will ignore modules with a lower precedence.
|
20
|
+
|
21
|
+
To learn more about modules, see the 'module' guide.
|
22
|
+
|
23
|
+
documentation:
|
24
|
+
- https://pup.pt/bolt-project-reference#modulepath
|
data/guides/project.yaml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
topic: project
|
3
|
+
guide: |
|
4
|
+
A Bolt project is a directory that serves as the launching point for Bolt
|
5
|
+
and allows you to create a shareable orchestration application. Projects
|
6
|
+
typically include a project configuration file, an inventory file, and any
|
7
|
+
content you use in your project workflow, such as tasks and plans.
|
8
|
+
|
9
|
+
When you run Bolt, it runs in the context of a project. If the directory you
|
10
|
+
run Bolt from is not a project, Bolt attempts to find a project by
|
11
|
+
traversing the parent directories. If Bolt is unable to find a project, it
|
12
|
+
runs from the default project, located at '~/.puppetlabs/bolt'.
|
13
|
+
|
14
|
+
A directory is only considered a Bolt project when it has a project
|
15
|
+
configuration file named 'bolt-project.yaml'. Bolt doesn't load project data
|
16
|
+
and content, including inventory files, unless the data and content are part
|
17
|
+
of a project.
|
18
|
+
|
19
|
+
documentation:
|
20
|
+
- https://pup.pt/bolt-projects
|
21
|
+
- https://pup.pt/bolt-project-reference
|