bolt 3.24.0 → 3.25.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 +7 -4
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +14 -4
- data/lib/bolt/applicator.rb +2 -3
- data/lib/bolt/apply_result.rb +24 -8
- data/lib/bolt/inventory/group.rb +11 -4
- data/lib/bolt/inventory/target.rb +9 -0
- data/lib/bolt/module_installer/resolver.rb +1 -1
- data/lib/bolt/module_installer/specs/git_spec.rb +54 -102
- data/lib/bolt/module_installer/specs/id/base.rb +116 -0
- data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
- data/lib/bolt/module_installer/specs/id/github.rb +90 -0
- data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
- data/lib/bolt/module_installer/specs.rb +5 -3
- data/lib/bolt/module_installer.rb +2 -2
- data/lib/bolt/result.rb +3 -1
- data/lib/bolt/version.rb +1 -1
- metadata +16 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe3e07dae1f3e80d3b4ade7c9d78749f54c592e246b2fd82e6264ab908b8baa1
|
4
|
+
data.tar.gz: 44af49cb408f92496c9795d892bc2e43e04788dd6ead42070a04e258958f00ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d158d8a0d8c72c99a34464ad4de0df9e7b04a83b1509b048c5b650baccbea9a06f99dfd512bfc8bdc91f5065e22148b76814a895766717bfe9f2c34bd81e55f
|
7
|
+
data.tar.gz: eca8af5592551ff9a273ad508831f9a1e07edf0327aa29dcc7078e5fb44743033c92388d79ab22ae2d3c5ea9574984b0c50849544159ab8dd32c2090a0dad939
|
data/Puppetfile
CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
|
|
6
6
|
|
7
7
|
# Core modules used by 'apply'
|
8
8
|
mod 'puppetlabs-service', '2.2.0'
|
9
|
-
mod 'puppetlabs-puppet_agent', '4.
|
9
|
+
mod 'puppetlabs-puppet_agent', '4.12.1'
|
10
10
|
mod 'puppetlabs-facts', '1.4.0'
|
11
11
|
|
12
12
|
# Core types and providers for Puppet 6
|
@@ -23,13 +23,16 @@ mod 'puppetlabs-zone_core', '1.0.3'
|
|
23
23
|
|
24
24
|
# Useful additional modules
|
25
25
|
mod 'puppetlabs-package', '2.2.0'
|
26
|
-
mod 'puppetlabs-powershell_task_helper', '0.1.0'
|
27
26
|
mod 'puppetlabs-puppet_conf', '1.3.0'
|
28
|
-
mod 'puppetlabs-python_task_helper', '0.5.0'
|
29
27
|
mod 'puppetlabs-reboot', '4.2.0'
|
28
|
+
mod 'puppetlabs-stdlib', '8.4.0'
|
29
|
+
|
30
|
+
# Task helpers
|
31
|
+
mod 'puppetlabs-powershell_task_helper', '0.1.0'
|
30
32
|
mod 'puppetlabs-ruby_task_helper', '0.6.1'
|
31
33
|
mod 'puppetlabs-ruby_plugin_helper', '0.2.0'
|
32
|
-
mod 'puppetlabs-
|
34
|
+
mod 'puppetlabs-python_task_helper', '0.5.0'
|
35
|
+
mod 'puppetlabs-bash_task_helper', '2.0.0'
|
33
36
|
|
34
37
|
# Plugin modules
|
35
38
|
mod 'puppetlabs-aws_inventory', '0.7.0'
|
@@ -12,11 +12,20 @@
|
|
12
12
|
# keys](applying_manifest_blocks.md#result-keys).
|
13
13
|
# @param target
|
14
14
|
# The target the result is from.
|
15
|
+
# @param error
|
16
|
+
# An Error object constructed from the `_error` field of the result's value.
|
17
|
+
# @param catalog
|
18
|
+
# The Puppet catalog used to configure the target. The catalog describes the
|
19
|
+
# desired state of the target. The catalog is masked with the `Sensitive` data
|
20
|
+
# type to protect any sensitive information in the catalog from being printed
|
21
|
+
# to the console or logs. Using this function automatically unwraps the
|
22
|
+
# catalog. For more information about catalogs and the catalog compilation
|
23
|
+
# process, see [Catalog
|
24
|
+
# compilation](https://puppet.com/docs/puppet/latest/subsystem_catalog_compilation.html).
|
25
|
+
|
15
26
|
#
|
16
27
|
# @!method action
|
17
28
|
# The action performed. `ApplyResult.action` always returns the string `apply`.
|
18
|
-
# @!method error
|
19
|
-
# Returns an Error object constructed from the `_error` field of the result's value.
|
20
29
|
# @!method message
|
21
30
|
# The `_output` field of the result's value.
|
22
31
|
# @!method ok
|
@@ -30,10 +39,11 @@ Puppet::DataTypes.create_type('ApplyResult') do
|
|
30
39
|
interface <<-PUPPET
|
31
40
|
attributes => {
|
32
41
|
'report' => Hash[String[1], Data],
|
33
|
-
'target' => Target
|
42
|
+
'target' => Target,
|
43
|
+
'error' => Optional[Error],
|
44
|
+
'catalog' => Optional[Hash]
|
34
45
|
},
|
35
46
|
functions => {
|
36
|
-
error => Callable[[], Optional[Error]],
|
37
47
|
ok => Callable[[], Boolean],
|
38
48
|
message => Callable[[], Optional[String]],
|
39
49
|
action => Callable[[], String],
|
data/lib/bolt/applicator.rb
CHANGED
@@ -280,7 +280,6 @@ module Bolt
|
|
280
280
|
result
|
281
281
|
end
|
282
282
|
else
|
283
|
-
|
284
283
|
arguments = {
|
285
284
|
'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
|
286
285
|
'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
|
@@ -291,7 +290,7 @@ module Bolt
|
|
291
290
|
|
292
291
|
callback = proc do |event|
|
293
292
|
if event[:type] == :node_result
|
294
|
-
event = event.merge(result: ApplyResult.from_task_result(event[:result]))
|
293
|
+
event = event.merge(result: ApplyResult.from_task_result(event[:result], catalog))
|
295
294
|
end
|
296
295
|
@executor.publish_event(event)
|
297
296
|
end
|
@@ -299,7 +298,7 @@ module Bolt
|
|
299
298
|
options[:run_as] = @executor.run_as if @executor.run_as && !options.key?(:run_as)
|
300
299
|
|
301
300
|
results = transport.batch_task(batch, catalog_apply_task, arguments, options, &callback)
|
302
|
-
Array(results).map { |result| ApplyResult.from_task_result(result) }
|
301
|
+
Array(results).map { |result| ApplyResult.from_task_result(result, catalog) }
|
303
302
|
end
|
304
303
|
end
|
305
304
|
end
|
data/lib/bolt/apply_result.rb
CHANGED
@@ -65,23 +65,30 @@ module Bolt
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
def self.from_task_result(result)
|
68
|
+
def self.from_task_result(result, catalog = nil)
|
69
69
|
if (puppet_missing = puppet_missing_error(result))
|
70
70
|
new(result.target,
|
71
71
|
error: puppet_missing,
|
72
|
-
report: result.value.reject { |k| k == '_error' }
|
72
|
+
report: result.value.reject { |k| k == '_error' },
|
73
|
+
catalog: catalog)
|
73
74
|
elsif !result.ok?
|
74
|
-
new(result.target,
|
75
|
+
new(result.target,
|
76
|
+
error: result.error_hash,
|
77
|
+
catalog: catalog)
|
75
78
|
elsif (invalid_report = invalid_report_error(result))
|
76
79
|
new(result.target,
|
77
80
|
error: invalid_report,
|
78
|
-
report: result.value.reject { |k| %w[_error _output].include?(k) }
|
81
|
+
report: result.value.reject { |k| %w[_error _output].include?(k) },
|
82
|
+
catalog: catalog)
|
79
83
|
elsif (resource_error = resource_error(result))
|
80
84
|
new(result.target,
|
81
85
|
error: resource_error,
|
82
|
-
report: result.value.reject { |k| k == '_error' }
|
86
|
+
report: result.value.reject { |k| k == '_error' },
|
87
|
+
catalog: catalog)
|
83
88
|
else
|
84
|
-
new(result.target,
|
89
|
+
new(result.target,
|
90
|
+
report: result.value,
|
91
|
+
catalog: catalog)
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
@@ -89,16 +96,21 @@ module Bolt
|
|
89
96
|
def _pcore_init_hash
|
90
97
|
{ 'target' => @target,
|
91
98
|
'error' => value['_error'],
|
92
|
-
'report' => value['report']
|
99
|
+
'report' => value['report'],
|
100
|
+
'catalog' => catalog }
|
93
101
|
end
|
94
102
|
|
95
|
-
def initialize(target, error: nil, report: nil)
|
103
|
+
def initialize(target, error: nil, report: nil, catalog: nil)
|
96
104
|
@target = target
|
97
105
|
@value = {}
|
98
106
|
@action = 'apply'
|
99
107
|
@value['report'] = report if report
|
100
108
|
@value['_error'] = error if error
|
101
109
|
@value['_output'] = metrics_message if metrics_message
|
110
|
+
|
111
|
+
if catalog
|
112
|
+
@value['_sensitive'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new({ 'catalog' => catalog })
|
113
|
+
end
|
102
114
|
end
|
103
115
|
|
104
116
|
def event_metrics
|
@@ -131,6 +143,10 @@ module Bolt
|
|
131
143
|
@value['report']
|
132
144
|
end
|
133
145
|
|
146
|
+
def catalog
|
147
|
+
sensitive.unwrap['catalog'] if sensitive
|
148
|
+
end
|
149
|
+
|
134
150
|
def generic_value
|
135
151
|
{}
|
136
152
|
end
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -10,8 +10,10 @@ module Bolt
|
|
10
10
|
class Group
|
11
11
|
attr_accessor :name, :groups
|
12
12
|
|
13
|
-
#
|
14
|
-
|
13
|
+
# Illegal characters that are not permitted in group names or aliases.
|
14
|
+
# These characters are delimiters for target and group names and allowing
|
15
|
+
# them would cause unexpected behavior.
|
16
|
+
ILLEGAL_CHARS = /[\s,]/.freeze
|
15
17
|
|
16
18
|
# NOTE: All keys should have a corresponding schema property in schemas/bolt-inventory.schema.json
|
17
19
|
DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
|
@@ -41,7 +43,10 @@ module Bolt
|
|
41
43
|
@name = @plugins.resolve_references(input['name'])
|
42
44
|
|
43
45
|
raise ValidationError.new("Group name must be a String, not #{@name.inspect}", nil) unless @name.is_a?(String)
|
44
|
-
|
46
|
+
|
47
|
+
if (illegal_char = @name.match(ILLEGAL_CHARS))
|
48
|
+
raise ValidationError.new("Illegal character '#{illegal_char}' in group name '#{@name}'", @name)
|
49
|
+
end
|
45
50
|
|
46
51
|
validate_group_input(input)
|
47
52
|
|
@@ -167,7 +172,9 @@ module Bolt
|
|
167
172
|
|
168
173
|
def insert_alia(target_name, aliases)
|
169
174
|
aliases.each do |alia|
|
170
|
-
|
175
|
+
if (illegal_char = alia.match(ILLEGAL_CHARS))
|
176
|
+
raise ValidationError.new("Illegal character '#{illegal_char}' in alias '#{alia}'", @name)
|
177
|
+
end
|
171
178
|
|
172
179
|
if (found = @aliases[alia])
|
173
180
|
raise ValidationError.new(alias_conflict(alia, found, target_name), @name)
|
@@ -8,6 +8,11 @@ module Bolt
|
|
8
8
|
class Target
|
9
9
|
attr_reader :name, :uri, :safe_name, :target_alias, :resources
|
10
10
|
|
11
|
+
# Illegal characters that are not permitted in target names.
|
12
|
+
# These characters are delimiters for target and group names and allowing
|
13
|
+
# them would cause unexpected behavior.
|
14
|
+
ILLEGAL_CHARS = /[\s,]/.freeze
|
15
|
+
|
11
16
|
def initialize(target_data, inventory)
|
12
17
|
unless target_data['name'] || target_data['uri']
|
13
18
|
raise Bolt::Inventory::ValidationError.new("Target must have either a name or uri", nil)
|
@@ -150,6 +155,10 @@ module Bolt
|
|
150
155
|
raise Bolt::Inventory::ValidationError.new("Target name must be ASCII characters: #{@name}", nil)
|
151
156
|
end
|
152
157
|
|
158
|
+
if (illegal_char = @name.match(ILLEGAL_CHARS))
|
159
|
+
raise ValidationError.new("Illegal character '#{illegal_char}' in target name '#{@name}'", nil)
|
160
|
+
end
|
161
|
+
|
153
162
|
unless transport.nil? || Bolt::TRANSPORTS.include?(transport.to_sym)
|
154
163
|
raise Bolt::UnknownTransportError.new(transport, uri)
|
155
164
|
end
|
@@ -49,7 +49,7 @@ module Bolt
|
|
49
49
|
spec_searcher_configuration: spec_searcher_config(config)
|
50
50
|
)
|
51
51
|
rescue StandardError => e
|
52
|
-
raise Bolt::Error.new(e.message, 'bolt/module-resolver-error')
|
52
|
+
raise Bolt::Error.new("Unable to resolve modules: #{e.message}", 'bolt/module-resolver-error')
|
53
53
|
end
|
54
54
|
|
55
55
|
# Create the Puppetfile object.
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'open3'
|
4
6
|
require 'set'
|
5
7
|
|
6
8
|
require_relative '../../../bolt/error'
|
9
|
+
require_relative '../../../bolt/logger'
|
10
|
+
require_relative '../../../bolt/module_installer/specs/id/gitclone'
|
11
|
+
require_relative '../../../bolt/module_installer/specs/id/github'
|
12
|
+
require_relative '../../../bolt/module_installer/specs/id/gitlab'
|
7
13
|
|
8
14
|
# This class represents a Git module specification.
|
9
15
|
#
|
@@ -17,12 +23,19 @@ module Bolt
|
|
17
23
|
|
18
24
|
attr_reader :git, :ref, :resolve, :type
|
19
25
|
|
20
|
-
def initialize(init_hash)
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@git
|
24
|
-
@ref
|
25
|
-
@
|
26
|
+
def initialize(init_hash, config = {})
|
27
|
+
@logger = Bolt::Logger.logger(self)
|
28
|
+
@resolve = init_hash.key?('resolve') ? init_hash['resolve'] : true
|
29
|
+
@git = init_hash['git']
|
30
|
+
@ref = init_hash['ref']
|
31
|
+
@name = parse_name(init_hash['name'])
|
32
|
+
@proxy = config.dig('proxy')
|
33
|
+
@type = :git
|
34
|
+
|
35
|
+
unless @resolve == true || @resolve == false
|
36
|
+
raise Bolt::ValidationError,
|
37
|
+
"Option 'resolve' for module spec #{@git} must be a Boolean"
|
38
|
+
end
|
26
39
|
|
27
40
|
if @name.nil? && @resolve == false
|
28
41
|
raise Bolt::ValidationError,
|
@@ -30,9 +43,9 @@ module Bolt
|
|
30
43
|
"must include a 'name' key when 'resolve' is false."
|
31
44
|
end
|
32
45
|
|
33
|
-
unless @
|
46
|
+
unless valid_url?(@git)
|
34
47
|
raise Bolt::ValidationError,
|
35
|
-
"
|
48
|
+
"Invalid URI #{@git}. Valid URIs must begin with 'git@', 'http://', or 'https://'."
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
@@ -57,22 +70,6 @@ module Bolt
|
|
57
70
|
match[:name]
|
58
71
|
end
|
59
72
|
|
60
|
-
# Gets the repo from the git URL.
|
61
|
-
#
|
62
|
-
private def parse_git(git)
|
63
|
-
return [git, nil] unless @resolve
|
64
|
-
|
65
|
-
repo = if git.start_with?('git@github.com:')
|
66
|
-
git.split('git@github.com:').last.split('.git').first
|
67
|
-
elsif git.start_with?('https://github.com')
|
68
|
-
git.split('https://github.com/').last.split('.git').first
|
69
|
-
else
|
70
|
-
raise Bolt::ValidationError, invalid_git_msg(git)
|
71
|
-
end
|
72
|
-
|
73
|
-
[git, repo]
|
74
|
-
end
|
75
|
-
|
76
73
|
# Returns true if the specification is satisfied by the module.
|
77
74
|
#
|
78
75
|
def satisfied_by?(mod)
|
@@ -88,14 +85,6 @@ module Bolt
|
|
88
85
|
}
|
89
86
|
end
|
90
87
|
|
91
|
-
# Returns an error message that the provided repo is not a git repo or
|
92
|
-
# is private.
|
93
|
-
#
|
94
|
-
private def invalid_git_msg(repo_name)
|
95
|
-
"#{repo_name} is not a public GitHub repository. See https://pup.pt/no-resolve "\
|
96
|
-
"for information on how to install this module."
|
97
|
-
end
|
98
|
-
|
99
88
|
# Returns a PuppetfileResolver::Model::GitModule object for resolving.
|
100
89
|
#
|
101
90
|
def to_resolver_module
|
@@ -107,90 +96,53 @@ module Bolt
|
|
107
96
|
end
|
108
97
|
end
|
109
98
|
|
110
|
-
#
|
111
|
-
# resolved since Bolt does not always need to know a Git module's name.
|
99
|
+
# Returns the module's name.
|
112
100
|
#
|
113
101
|
def name
|
114
|
-
@name ||=
|
115
|
-
url = "https://raw.githubusercontent.com/#{@repo}/#{sha}/metadata.json"
|
116
|
-
response = make_request(:Get, url)
|
117
|
-
|
118
|
-
case response
|
119
|
-
when Net::HTTPOK
|
120
|
-
body = JSON.parse(response.body)
|
121
|
-
|
122
|
-
unless body.key?('name')
|
123
|
-
raise Bolt::Error.new(
|
124
|
-
"Missing name in metadata.json at #{git}. This is not a valid module.",
|
125
|
-
"bolt/missing-module-name-error"
|
126
|
-
)
|
127
|
-
end
|
128
|
-
|
129
|
-
parse_name(body['name'])
|
130
|
-
else
|
131
|
-
raise Bolt::Error.new(
|
132
|
-
"Missing metadata.json at #{git}. This is not a valid module.",
|
133
|
-
"bolt/missing-module-metadata-error"
|
134
|
-
)
|
135
|
-
end
|
136
|
-
end
|
102
|
+
@name ||= parse_name(id.name)
|
137
103
|
end
|
138
104
|
|
139
|
-
#
|
140
|
-
# Bolt does not always need to know a Git module's SHA.
|
105
|
+
# Returns the SHA for the module's ref.
|
141
106
|
#
|
142
107
|
def sha
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
unless ENV['GITHUB_TOKEN']
|
161
|
-
message += "To increase your rate limit, set the GITHUB_TOKEN environment "\
|
162
|
-
"variable with a GitHub personal access token."
|
163
|
-
end
|
164
|
-
|
165
|
-
raise Bolt::Error.new(message, 'bolt/github-api-rate-limit-error')
|
166
|
-
when Net::HTTPNotFound
|
167
|
-
raise Bolt::Error.new(invalid_git_msg(git), "bolt/missing-git-repository-error")
|
168
|
-
else
|
108
|
+
id.sha
|
109
|
+
end
|
110
|
+
|
111
|
+
# Gets the ID for the module based on the specified ref and git URL.
|
112
|
+
# This is lazily resolved since Bolt does not always need this information,
|
113
|
+
# and requesting it is expensive.
|
114
|
+
#
|
115
|
+
private def id
|
116
|
+
@id ||= begin
|
117
|
+
# The request methods here return an ID object if the module name and SHA
|
118
|
+
# were found and nil otherwise. This lets Bolt try multiple methods for
|
119
|
+
# finding the module name and SHA, and short circuiting as soon as it does.
|
120
|
+
module_id = Bolt::ModuleInstaller::Specs::ID::GitHub.request(@git, @ref, @proxy) ||
|
121
|
+
Bolt::ModuleInstaller::Specs::ID::GitLab.request(@git, @ref, @proxy) ||
|
122
|
+
Bolt::ModuleInstaller::Specs::ID::GitClone.request(@git, @ref, @proxy)
|
123
|
+
|
124
|
+
unless module_id
|
169
125
|
raise Bolt::Error.new(
|
170
|
-
"
|
171
|
-
"
|
126
|
+
"Unable to locate metadata and calculate SHA for ref #{@ref} at #{@git}. This may "\
|
127
|
+
"not be a valid module. For more information about how Bolt attempted to locate "\
|
128
|
+
"this information, check the debugging logs.",
|
129
|
+
'bolt/missing-module-metadata-error'
|
172
130
|
)
|
173
131
|
end
|
132
|
+
|
133
|
+
module_id
|
174
134
|
end
|
175
135
|
end
|
176
136
|
|
177
|
-
#
|
137
|
+
# Returns true if the URL is valid.
|
178
138
|
#
|
179
|
-
private def
|
180
|
-
|
181
|
-
|
182
|
-
uri = URI.parse(url)
|
183
|
-
opts = { use_ssl: uri.scheme == 'https' }
|
139
|
+
private def valid_url?(url)
|
140
|
+
return true if url.start_with?('git@')
|
184
141
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
rescue StandardError => e
|
190
|
-
raise Bolt::Error.new(
|
191
|
-
"Failed to connect to #{uri}: #{e.message}",
|
192
|
-
"bolt/http-connect-error"
|
193
|
-
)
|
142
|
+
uri = URI.parse(url)
|
143
|
+
uri.is_a?(URI::HTTP) && uri.host
|
144
|
+
rescue URI::InvalidURIError
|
145
|
+
false
|
194
146
|
end
|
195
147
|
end
|
196
148
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
require_relative '../../../../bolt/error'
|
7
|
+
require_relative '../../../../bolt/logger'
|
8
|
+
|
9
|
+
module Bolt
|
10
|
+
class ModuleInstaller
|
11
|
+
class Specs
|
12
|
+
class ID
|
13
|
+
class Base
|
14
|
+
attr_reader :name, :sha
|
15
|
+
|
16
|
+
# @param name [String] The module's name.
|
17
|
+
# @param sha [String] The ref's SHA1.
|
18
|
+
#
|
19
|
+
def initialize(name, sha)
|
20
|
+
@name = name
|
21
|
+
@sha = sha
|
22
|
+
end
|
23
|
+
|
24
|
+
# Request the name and SHA for a module and ref.
|
25
|
+
# This method must return either an ID object or nil. The GitSpec
|
26
|
+
# class relies on this class to return an ID object to indicate
|
27
|
+
# the module was found, or nil to indicate that it should try to
|
28
|
+
# find it another way (such as cloning the repo).
|
29
|
+
#
|
30
|
+
# @param git [String] The URL to the git repo.
|
31
|
+
# @param ref [String] The ref to checkout.
|
32
|
+
# @param proxy [String] A proxy to use when making requests.
|
33
|
+
#
|
34
|
+
def self.request(git, ref, proxy)
|
35
|
+
name, sha = name_and_sha(git, ref, proxy)
|
36
|
+
name && sha ? new(name, sha) : nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Stub method for retrieving the module's name and SHA. Must
|
40
|
+
# be implemented by all sub classes.
|
41
|
+
#
|
42
|
+
private_class_method def self.name_and_sha(_git, _ref, _proxy)
|
43
|
+
raise NotImplementedError, 'Class does not implemented #name_and_sha'
|
44
|
+
end
|
45
|
+
|
46
|
+
# Makes a HTTP request.
|
47
|
+
#
|
48
|
+
# @param url [String] The URL to make the request to.
|
49
|
+
# @param proxy [String] A proxy to use when making the request.
|
50
|
+
# @param headers [Hash] Headers to send with the request.
|
51
|
+
#
|
52
|
+
private_class_method def self.make_request(url, proxy, headers = {})
|
53
|
+
uri = URI.parse(url)
|
54
|
+
opts = { use_ssl: uri.scheme == 'https' }
|
55
|
+
args = [uri.host, uri.port]
|
56
|
+
|
57
|
+
if proxy
|
58
|
+
proxy = URI.parse(proxy)
|
59
|
+
args += [proxy.host, proxy.port, proxy.user, proxy.password]
|
60
|
+
end
|
61
|
+
|
62
|
+
Bolt::Logger.debug("Making request to #{loc(url, proxy)}")
|
63
|
+
|
64
|
+
Net::HTTP.start(*args, opts) do |client|
|
65
|
+
client.request(Net::HTTP::Get.new(uri, headers))
|
66
|
+
end
|
67
|
+
rescue StandardError => e
|
68
|
+
raise Bolt::Error.new(
|
69
|
+
"Failed to connect to #{loc(uri, proxy)}: #{e.message}",
|
70
|
+
"bolt/http-connect-error"
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a formatted string describing the URL and proxy used when making
|
75
|
+
# a request.
|
76
|
+
#
|
77
|
+
# @param url [String, URI::HTTP] The URL used.
|
78
|
+
# @param proxy [String, URI::HTTP] The proxy used.
|
79
|
+
#
|
80
|
+
private_class_method def self.loc(url, proxy)
|
81
|
+
proxy ? "#{url} with proxy #{proxy}" : url.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# Parses the metadata and validates that it is a Hash.
|
85
|
+
#
|
86
|
+
# @param metadata [String] The JSON data to parse.
|
87
|
+
#
|
88
|
+
private_class_method def self.parse_name_from_metadata(metadata)
|
89
|
+
metadata = JSON.parse(metadata)
|
90
|
+
|
91
|
+
unless metadata.is_a?(Hash)
|
92
|
+
raise Bolt::Error.new(
|
93
|
+
"Invalid metadata. Expected a Hash, got a #{metadata.class}: #{metadata}",
|
94
|
+
"bolt/invalid-module-metadata-error"
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
unless metadata.key?('name')
|
99
|
+
raise Bolt::Error.new(
|
100
|
+
"Invalid metadata. Metadata must include a 'name' key.",
|
101
|
+
"bolt/missing-module-name-error"
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
metadata['name']
|
106
|
+
rescue JSON::ParserError => e
|
107
|
+
raise Bolt::Error.new(
|
108
|
+
"Unable to parse metadata as JSON: #{e.message}",
|
109
|
+
"bolt/metadata-parse-error"
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../../bolt/module_installer/specs/id/base'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class ModuleInstaller
|
7
|
+
class Specs
|
8
|
+
class ID
|
9
|
+
class GitClone < Base
|
10
|
+
# Returns the name and SHA for the module at the given ref.
|
11
|
+
#
|
12
|
+
# @param git [String] The URL to the git repo.
|
13
|
+
# @param ref [String] The ref to checkout.
|
14
|
+
# @param proxy [String] The proxy to use when cloning.
|
15
|
+
#
|
16
|
+
private_class_method def self.name_and_sha(git, ref, proxy)
|
17
|
+
require 'open3'
|
18
|
+
|
19
|
+
unless git?
|
20
|
+
Bolt::Logger.debug("'git' executable not found, unable to use git clone resolution.")
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
|
24
|
+
# Clone the repo into a temp directory that will be automatically cleaned up.
|
25
|
+
Dir.mktmpdir do |dir|
|
26
|
+
return nil unless clone_repo(git, ref, dir, proxy)
|
27
|
+
|
28
|
+
# Extract the name from the metadata file and calculate the SHA.
|
29
|
+
Dir.chdir(dir) do
|
30
|
+
[request_name(git, ref), request_sha(git, ref)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Requests a module's metadata and returns the name from it.
|
36
|
+
#
|
37
|
+
# @param git [String] The URL to the git repo.
|
38
|
+
# @param ref [String] The ref to checkout.
|
39
|
+
#
|
40
|
+
private_class_method def self.request_name(git, ref)
|
41
|
+
command = %W[git show #{ref}:metadata.json]
|
42
|
+
Bolt::Logger.debug("Executing command '#{command.join(' ')}'")
|
43
|
+
|
44
|
+
out, err, status = Open3.capture3(*command)
|
45
|
+
|
46
|
+
unless status.success?
|
47
|
+
raise Bolt::Error.new(
|
48
|
+
"Unable to find metadata file at #{git}: #{err}",
|
49
|
+
"bolt/missing-metadata-file-error"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
Bolt::Logger.debug("Found metadata file at #{git}")
|
54
|
+
parse_name_from_metadata(out)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Requests the SHA for the specified ref.
|
58
|
+
#
|
59
|
+
# @param git [String] The URL to the git repo.
|
60
|
+
# @param ref [String] The ref to checkout.
|
61
|
+
#
|
62
|
+
private_class_method def self.request_sha(git, ref)
|
63
|
+
command = %W[git rev-parse #{ref}^{commit}]
|
64
|
+
Bolt::Logger.debug("Executing command '#{command.join(' ')}'")
|
65
|
+
|
66
|
+
out, err, status = Open3.capture3(*command)
|
67
|
+
|
68
|
+
if status.success?
|
69
|
+
out.strip
|
70
|
+
else
|
71
|
+
raise Bolt::Error.new(
|
72
|
+
"Unable to calculate SHA for ref #{ref} at #{git}: #{err}",
|
73
|
+
"bolt/invalid-ref-error"
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Clones the repository. First attempts to clone a bare repository
|
79
|
+
# and falls back to cloning the full repository if that fails. Cloning
|
80
|
+
# a bare repository is significantly faster for large modules, but
|
81
|
+
# cloning a bare repository using a commit is not supported.
|
82
|
+
#
|
83
|
+
# @param git [String] The URL to the git repo.
|
84
|
+
# @param ref [String] The ref to checkout.
|
85
|
+
# @param dir [String] The directory to clone the repo to.
|
86
|
+
# @param proxy [String] The proxy to use when cloning.
|
87
|
+
#
|
88
|
+
private_class_method def self.clone_repo(git, ref, dir, proxy)
|
89
|
+
clone = %W[git clone #{git} #{dir}]
|
90
|
+
clone += %W[--config "http.proxy=#{proxy}" --config "https.proxy=#{proxy}"] if proxy
|
91
|
+
|
92
|
+
bare_clone = clone + %w[--bare --depth=1]
|
93
|
+
bare_clone.push("--branch=#{ref}") unless ref == 'HEAD'
|
94
|
+
|
95
|
+
# Attempt to clone a bare repository
|
96
|
+
Bolt::Logger.debug("Executing command '#{bare_clone.join(' ')}'")
|
97
|
+
_out, err, status = Open3.capture3(*bare_clone)
|
98
|
+
return true if status.success?
|
99
|
+
Bolt::Logger.debug("Unable to clone bare repository at #{loc(git, proxy)}: #{err}")
|
100
|
+
|
101
|
+
# Fall back to cloning the full repository
|
102
|
+
Bolt::Logger.debug("Executing command '#{clone.join(' ')}'")
|
103
|
+
_out, err, status = Open3.capture3(*clone)
|
104
|
+
Bolt::Logger.debug("Unable to clone repository at #{loc(git, proxy)}: #{err}") unless status.success?
|
105
|
+
status.success?
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns true if the 'git' executable is available.
|
109
|
+
#
|
110
|
+
private_class_method def self.git?
|
111
|
+
Open3.capture3('git', '--version')
|
112
|
+
true
|
113
|
+
rescue Errno::ENOENT
|
114
|
+
false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../../bolt/module_installer/specs/id/base'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class ModuleInstaller
|
7
|
+
class Specs
|
8
|
+
class ID
|
9
|
+
class GitHub < Base
|
10
|
+
# Returns the name and SHA for the module at the given ref.
|
11
|
+
#
|
12
|
+
# @param git [String] The URL to the git repo.
|
13
|
+
# @param ref [String] The ref to use.
|
14
|
+
# @param proxy [String] The proxy to use when making requests.
|
15
|
+
#
|
16
|
+
private_class_method def self.name_and_sha(git, ref, proxy)
|
17
|
+
repo = parse_repo(git)
|
18
|
+
return nil unless repo
|
19
|
+
[request_name(repo, ref, proxy), request_sha(repo, ref, proxy)]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parses the repo path out of the URL.
|
23
|
+
#
|
24
|
+
# @param git [String] The URL to the git repo.
|
25
|
+
#
|
26
|
+
private_class_method def self.parse_repo(git)
|
27
|
+
if git.start_with?('git@github.com:')
|
28
|
+
git.split('git@github.com:').last.split('.git').first
|
29
|
+
elsif git.start_with?('https://github.com')
|
30
|
+
git.split('https://github.com/').last.split('.git').first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Requests a module's metadata and returns the name from it.
|
35
|
+
#
|
36
|
+
# @param repo [String] The repo ID, i.e. 'owner/repo'
|
37
|
+
# @param ref [String] The ref to use.
|
38
|
+
# @param proxy [String] The proxy to use when making requests.
|
39
|
+
#
|
40
|
+
private_class_method def self.request_name(repo, ref, proxy)
|
41
|
+
metadata_url = "https://raw.githubusercontent.com/#{repo}/#{ref}/metadata.json"
|
42
|
+
response = make_request(metadata_url, proxy)
|
43
|
+
|
44
|
+
case response
|
45
|
+
when Net::HTTPOK
|
46
|
+
Bolt::Logger.debug("Found metadata file at #{loc(metadata_url, proxy)}")
|
47
|
+
parse_name_from_metadata(response.body)
|
48
|
+
else
|
49
|
+
Bolt::Logger.debug("Unable to find metadata file at #{loc(metadata_url, proxy)}")
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Requests the SHA for the specified ref.
|
55
|
+
#
|
56
|
+
# @param repo [String] The repo ID, i.e. 'owner/repo'
|
57
|
+
# @param ref [String] The ref to resolve.
|
58
|
+
# @param proxy [String] The proxy to use when making requests.
|
59
|
+
#
|
60
|
+
private_class_method def self.request_sha(repo, ref, proxy)
|
61
|
+
url = "https://api.github.com/repos/#{repo}/commits/#{ref}"
|
62
|
+
headers = ENV['GITHUB_TOKEN'] ? { "Authorization" => "token #{ENV['GITHUB_TOKEN']}" } : {}
|
63
|
+
response = make_request(url, proxy, headers)
|
64
|
+
|
65
|
+
case response
|
66
|
+
when Net::HTTPOK
|
67
|
+
JSON.parse(response.body).fetch('sha', nil)
|
68
|
+
when Net::HTTPUnauthorized
|
69
|
+
Bolt::Logger.debug("Invalid token at GITHUB_TOKEN, unable to calculate SHA.")
|
70
|
+
nil
|
71
|
+
when Net::HTTPForbidden
|
72
|
+
message = "GitHub API rate limit exceeded, unable to calculate SHA."
|
73
|
+
|
74
|
+
unless ENV['GITHUB_TOKEN']
|
75
|
+
message += " To increase your rate limit, set the GITHUB_TOKEN environment "\
|
76
|
+
"variable with a GitHub personal access token."
|
77
|
+
end
|
78
|
+
|
79
|
+
Bolt::Logger.debug(message)
|
80
|
+
nil
|
81
|
+
else
|
82
|
+
Bolt::Logger.debug("Unable to calculate SHA for ref #{ref}")
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../../bolt/module_installer/specs/id/base'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class ModuleInstaller
|
7
|
+
class Specs
|
8
|
+
class ID
|
9
|
+
class GitLab < Base
|
10
|
+
# Returns the name and SHA for the module at the given ref.
|
11
|
+
#
|
12
|
+
# @param git [String] The URL to the git repo.
|
13
|
+
# @param ref [String] The ref to use.
|
14
|
+
# @param proxy [String] The proxy to use when making requests.
|
15
|
+
#
|
16
|
+
private_class_method def self.name_and_sha(git, ref, proxy)
|
17
|
+
repo = parse_repo(git)
|
18
|
+
return nil unless repo
|
19
|
+
[request_name(repo, ref, proxy), request_sha(repo, ref, proxy)]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parses the repo path out of the URL.
|
23
|
+
#
|
24
|
+
# @param git [String] The URL to the git repo.
|
25
|
+
#
|
26
|
+
private_class_method def self.parse_repo(git)
|
27
|
+
if git.start_with?('git@gitlab.com:')
|
28
|
+
git.split('git@gitlab.com:').last.split('.git').first
|
29
|
+
elsif git.start_with?('https://gitlab.com')
|
30
|
+
git.split('https://gitlab.com/').last.split('.git').first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Requests a module's metadata and returns the name from it.
|
35
|
+
#
|
36
|
+
# @param repo [String] The repo ID, i.e. 'owner/repo'
|
37
|
+
# @param ref [String] The ref to use.
|
38
|
+
# @param proxy [String] The proxy to use when making requests.
|
39
|
+
#
|
40
|
+
private_class_method def self.request_name(repo, ref, proxy)
|
41
|
+
metadata_url = "https://gitlab.com/#{repo}/-/raw/#{ref}/metadata.json"
|
42
|
+
response = make_request(metadata_url, proxy)
|
43
|
+
|
44
|
+
case response
|
45
|
+
when Net::HTTPOK
|
46
|
+
Bolt::Logger.debug("Found metadata file at #{loc(metadata_url, proxy)}")
|
47
|
+
parse_name_from_metadata(response.body)
|
48
|
+
else
|
49
|
+
Bolt::Logger.debug("Unable to find metadata file at #{loc(metadata_url, proxy)}")
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Requests the SHA for the specified ref.
|
55
|
+
#
|
56
|
+
# @param repo [String] The repo ID, i.e. 'owner/repo'
|
57
|
+
# @param ref [String] The ref to resolve.
|
58
|
+
# @param proxy [String] The proxy to use when making requests.
|
59
|
+
#
|
60
|
+
private_class_method def self.request_sha(repo, ref, proxy)
|
61
|
+
require 'cgi'
|
62
|
+
|
63
|
+
url = "https://gitlab.com/api/v4/projects/#{CGI.escape(repo)}/repository/commits/#{ref}"
|
64
|
+
headers = ENV['GITLAB_TOKEN'] ? { "PRIVATE-TOKEN" => ENV['GITLAB_TOKEN'] } : {}
|
65
|
+
response = make_request(url, proxy, headers)
|
66
|
+
|
67
|
+
case response
|
68
|
+
when Net::HTTPOK
|
69
|
+
JSON.parse(response.body).fetch('id', nil)
|
70
|
+
when Net::HTTPUnauthorized
|
71
|
+
Bolt::Logger.debug("Invalid token at GITLAB_TOKEN, unable to calculate SHA.")
|
72
|
+
nil
|
73
|
+
when Net::HTTPForbidden
|
74
|
+
message = "GitLab API rate limit exceeded, unable to calculate SHA."
|
75
|
+
|
76
|
+
unless ENV['GITLAB_TOKEN']
|
77
|
+
message += " To increase your rate limit, set the GITLAB_TOKEN environment "\
|
78
|
+
"variable with a GitLab personal access token."
|
79
|
+
end
|
80
|
+
|
81
|
+
Bolt::Logger.debug(message)
|
82
|
+
nil
|
83
|
+
else
|
84
|
+
Bolt::Logger.debug("Unable to calculate SHA for ref #{ref}")
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -7,8 +7,10 @@ require_relative 'specs/git_spec'
|
|
7
7
|
module Bolt
|
8
8
|
class ModuleInstaller
|
9
9
|
class Specs
|
10
|
-
def initialize(specs = [])
|
11
|
-
@specs
|
10
|
+
def initialize(specs = [], config = {})
|
11
|
+
@specs = []
|
12
|
+
@config = config
|
13
|
+
|
12
14
|
add_specs(specs)
|
13
15
|
assert_unique_names
|
14
16
|
end
|
@@ -49,7 +51,7 @@ module Bolt
|
|
49
51
|
#
|
50
52
|
private def spec_from_hash(hash)
|
51
53
|
return ForgeSpec.new(hash) if ForgeSpec.implements?(hash)
|
52
|
-
return GitSpec.new(hash)
|
54
|
+
return GitSpec.new(hash, @config) if GitSpec.implements?(hash)
|
53
55
|
|
54
56
|
raise Bolt::ValidationError, <<~MESSAGE.chomp
|
55
57
|
Invalid module specification:
|
@@ -18,7 +18,7 @@ module Bolt
|
|
18
18
|
# Adds a single module to the project.
|
19
19
|
#
|
20
20
|
def add(name, specs, puppetfile_path, moduledir, project_file, config)
|
21
|
-
project_specs = Specs.new(specs)
|
21
|
+
project_specs = Specs.new(specs, config)
|
22
22
|
|
23
23
|
# Exit early if project config already includes a spec with this name.
|
24
24
|
if project_specs.include?(name)
|
@@ -151,7 +151,7 @@ module Bolt
|
|
151
151
|
@outputter.print_message("Installing project modules\n\n")
|
152
152
|
|
153
153
|
if resolve != false
|
154
|
-
specs = Specs.new(specs)
|
154
|
+
specs = Specs.new(specs, config)
|
155
155
|
|
156
156
|
# If forcibly installing or if there is no Puppetfile, resolve
|
157
157
|
# and write a Puppetfile.
|
data/lib/bolt/result.rb
CHANGED
@@ -200,16 +200,18 @@ module Bolt
|
|
200
200
|
|
201
201
|
def to_data
|
202
202
|
serialized_value = safe_value
|
203
|
+
|
203
204
|
if serialized_value.key?('_sensitive') &&
|
204
205
|
serialized_value['_sensitive'].is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
|
205
206
|
serialized_value['_sensitive'] = serialized_value['_sensitive'].to_s
|
206
207
|
end
|
208
|
+
|
207
209
|
{
|
208
210
|
"target" => @target.name,
|
209
211
|
"action" => action,
|
210
212
|
"object" => object,
|
211
213
|
"status" => status,
|
212
|
-
"value"
|
214
|
+
"value" => serialized_value
|
213
215
|
}
|
214
216
|
end
|
215
217
|
|
data/lib/bolt/version.rb
CHANGED
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: 3.
|
4
|
+
version: 3.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -222,16 +222,22 @@ dependencies:
|
|
222
222
|
name: puppetfile-resolver
|
223
223
|
requirement: !ruby/object:Gem::Requirement
|
224
224
|
requirements:
|
225
|
-
- - "
|
225
|
+
- - ">="
|
226
226
|
- !ruby/object:Gem::Version
|
227
|
-
version:
|
227
|
+
version: 0.6.2
|
228
|
+
- - "<"
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '1.0'
|
228
231
|
type: :runtime
|
229
232
|
prerelease: false
|
230
233
|
version_requirements: !ruby/object:Gem::Requirement
|
231
234
|
requirements:
|
232
|
-
- - "
|
235
|
+
- - ">="
|
233
236
|
- !ruby/object:Gem::Version
|
234
|
-
version:
|
237
|
+
version: 0.6.2
|
238
|
+
- - "<"
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: '1.0'
|
235
241
|
- !ruby/object:Gem::Dependency
|
236
242
|
name: puppet-resource_api
|
237
243
|
requirement: !ruby/object:Gem::Requirement
|
@@ -526,6 +532,10 @@ files:
|
|
526
532
|
- lib/bolt/module_installer/specs.rb
|
527
533
|
- lib/bolt/module_installer/specs/forge_spec.rb
|
528
534
|
- lib/bolt/module_installer/specs/git_spec.rb
|
535
|
+
- lib/bolt/module_installer/specs/id/base.rb
|
536
|
+
- lib/bolt/module_installer/specs/id/gitclone.rb
|
537
|
+
- lib/bolt/module_installer/specs/id/github.rb
|
538
|
+
- lib/bolt/module_installer/specs/id/gitlab.rb
|
529
539
|
- lib/bolt/node/errors.rb
|
530
540
|
- lib/bolt/node/output.rb
|
531
541
|
- lib/bolt/outputter.rb
|