beaker-module_install_helper 0.1.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +17 -0
- data/.github/workflows/release.yml +31 -0
- data/.github/workflows/test.yml +57 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -43
- data/.rubocop_todo.yml +25 -0
- data/CHANGELOG.md +124 -29
- data/Gemfile +16 -7
- data/LICENSE +197 -11
- data/README.md +27 -0
- data/Rakefile +28 -6
- data/beaker-module_install_helper.gemspec +15 -10
- data/lib/beaker/module_install_helper.rb +163 -152
- data/spec/spec_helper.rb +26 -0
- data/spec/unit/beaker/module_install_helper_spec.rb +82 -31
- metadata +56 -17
- data/.travis.yml +0 -8
- data/MAINTAINERS +0 -11
data/Rakefile
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
|
2
|
-
task default: %i[lint spec]
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
require 'bundler/gem_tasks'
|
4
4
|
require 'rubocop/rake_task'
|
5
|
-
|
6
|
-
RuboCop::RakeTask.new
|
7
|
-
t.requires << 'rubocop-rspec'
|
8
|
-
end
|
5
|
+
|
6
|
+
RuboCop::RakeTask.new
|
9
7
|
|
10
8
|
require 'rspec/core/rake_task'
|
11
9
|
desc 'Run spec tests using rspec'
|
@@ -13,3 +11,27 @@ RSpec::Core::RakeTask.new(:spec) do |t|
|
|
13
11
|
t.rspec_opts = ['--color']
|
14
12
|
t.pattern = 'spec'
|
15
13
|
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'rubygems'
|
17
|
+
require 'github_changelog_generator/task'
|
18
|
+
rescue LoadError
|
19
|
+
# github_changelog_generator isn't available, so we won't define a rake task with it
|
20
|
+
else
|
21
|
+
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
22
|
+
config.header = <<-HEADER
|
23
|
+
# Changelog
|
24
|
+
|
25
|
+
# All notable changes to this project will be documented in this file.
|
26
|
+
HEADER
|
27
|
+
config.exclude_labels = %w[duplicate question invalid wontfix wont-fix skip-changelog]
|
28
|
+
config.user = 'voxpupuli'
|
29
|
+
config.project = 'beaker-module_install_helper'
|
30
|
+
config.future_release = Gem::Specification.load("#{config.project}.gemspec").version
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
task default: %w[
|
35
|
+
rubocop
|
36
|
+
spec
|
37
|
+
]
|
@@ -1,11 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
|
4
6
|
Gem::Specification.new do |spec|
|
5
7
|
spec.name = 'beaker-module_install_helper'
|
6
|
-
spec.version = '0.
|
7
|
-
spec.authors = ['
|
8
|
-
spec.email = ['
|
8
|
+
spec.version = '2.0.0'
|
9
|
+
spec.authors = ['Vox Pupuli']
|
10
|
+
spec.email = ['voxpupuli@groups.io']
|
9
11
|
|
10
12
|
spec.summary = 'A helper gem for use in a Puppet Modules ' \
|
11
13
|
'spec_helper_acceptance.rb file'
|
@@ -13,19 +15,22 @@ Gem::Specification.new do |spec|
|
|
13
15
|
'spec_helper_acceptance.rb file to help install the ' \
|
14
16
|
'module under test and its dependencies on the system ' \
|
15
17
|
'under test'
|
16
|
-
spec.homepage = 'https://github.com/
|
18
|
+
spec.homepage = 'https://github.com/voxpupuli/beaker-module_install_helper'
|
17
19
|
spec.license = 'Apache-2.0'
|
18
20
|
|
19
21
|
spec.files = `git ls-files`.split("\n")
|
20
|
-
spec.
|
21
|
-
|
22
|
-
.split("\n") \
|
22
|
+
spec.executables = `git ls-files -- bin/*`
|
23
|
+
.split("\n")
|
23
24
|
.map { |f| File.basename(f) }
|
24
25
|
spec.require_paths = ['lib']
|
25
26
|
|
26
27
|
## Testing dependencies
|
27
|
-
spec.add_development_dependency 'rspec'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
29
|
+
spec.add_development_dependency 'voxpupuli-rubocop', '~> 1.2'
|
28
30
|
|
29
31
|
# Run time dependencies
|
30
|
-
spec.add_runtime_dependency 'beaker', '>= 2.0'
|
32
|
+
spec.add_runtime_dependency 'beaker', '>= 2.0', '< 6'
|
33
|
+
spec.add_runtime_dependency 'beaker-puppet', '>= 1', '< 3'
|
34
|
+
|
35
|
+
spec.required_ruby_version = '>= 2.7.0'
|
31
36
|
end
|
@@ -1,205 +1,216 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'beaker'
|
4
|
+
require 'beaker-puppet'
|
2
5
|
|
3
6
|
# Provides method for use in module test setup to install the module under
|
4
7
|
# test and it's dependencies on the specified hosts
|
5
|
-
module Beaker
|
6
|
-
|
8
|
+
module Beaker
|
9
|
+
module ModuleInstallHelper
|
10
|
+
include Beaker::DSL
|
11
|
+
|
12
|
+
# This method calls the install_module_on method for each host which is a
|
13
|
+
# master, or if no master is present, on all agent nodes.
|
14
|
+
def install_module(opts = {})
|
15
|
+
install_module_on(hosts_to_install_module_on, opts)
|
16
|
+
end
|
7
17
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
18
|
+
# This method will install the module under test on the specified host(s) from
|
19
|
+
# the source on the local machine
|
20
|
+
def install_module_on(host, opts = {})
|
21
|
+
opts = {
|
22
|
+
source: $module_source_dir,
|
23
|
+
module_name: module_name_from_metadata,
|
24
|
+
}.merge(opts)
|
25
|
+
copy_module_to(host, opts)
|
26
|
+
end
|
13
27
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
module_name: module_name_from_metadata)
|
20
|
-
end
|
28
|
+
# This method calls the install_module_dependencies_on method for each
|
29
|
+
# host which is a master, or if no master is present, on all agent nodes.
|
30
|
+
def install_module_dependencies(deps = nil)
|
31
|
+
install_module_dependencies_on(hosts_to_install_module_on, deps)
|
32
|
+
end
|
21
33
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
34
|
+
# This method will install the module under tests module dependencies on the
|
35
|
+
# specified host(s) from the dependencies list in metadata.json
|
36
|
+
def install_module_dependencies_on(hsts, deps = nil)
|
37
|
+
hsts = [hsts] if hsts.is_a?(Hash)
|
38
|
+
hsts = [hsts] unless hsts.respond_to?(:each)
|
39
|
+
deps = module_dependencies_from_metadata if deps.nil?
|
27
40
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
deps = deps.nil? ? module_dependencies_from_metadata : deps
|
34
|
-
|
35
|
-
fh = ENV['BEAKER_FORGE_HOST']
|
36
|
-
|
37
|
-
hsts.each do |host|
|
38
|
-
deps.each do |dep|
|
39
|
-
if fh.nil?
|
40
|
-
install_puppet_module_via_pmt_on(host, dep)
|
41
|
-
else
|
42
|
-
with_forge_stubbed_on(host) do
|
41
|
+
fh = ENV.fetch('BEAKER_FORGE_HOST', nil)
|
42
|
+
|
43
|
+
hsts.each do |host|
|
44
|
+
deps.each do |dep|
|
45
|
+
if fh.nil?
|
43
46
|
install_puppet_module_via_pmt_on(host, dep)
|
47
|
+
else
|
48
|
+
with_forge_stubbed_on(host) do
|
49
|
+
install_puppet_module_via_pmt_on(host, dep)
|
50
|
+
end
|
44
51
|
end
|
45
52
|
end
|
46
53
|
end
|
47
54
|
end
|
48
|
-
end
|
49
55
|
|
50
|
-
|
51
|
-
|
52
|
-
|
56
|
+
def install_module_from_forge(mod_name, ver_req)
|
57
|
+
install_module_from_forge_on(hosts_to_install_module_on, mod_name, ver_req)
|
58
|
+
end
|
53
59
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
def install_module_from_forge_on(hsts, mod_name, ver_req)
|
61
|
+
sub_mod_name = mod_name.sub('/', '-')
|
62
|
+
dependency = {
|
63
|
+
module_name: sub_mod_name,
|
64
|
+
version: module_version_from_requirement(sub_mod_name, ver_req),
|
65
|
+
}
|
60
66
|
|
61
|
-
|
62
|
-
|
67
|
+
install_module_dependencies_on(hsts, [dependency])
|
68
|
+
end
|
63
69
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
# This method returns an array of dependencies from the metadata.json file
|
71
|
+
# in the format of an array of hashes, containing :module_name and optionally
|
72
|
+
# :version elements. If no dependencies are specified, empty array is returned
|
73
|
+
def module_dependencies_from_metadata
|
74
|
+
metadata = module_metadata
|
75
|
+
return [] unless metadata.key?('dependencies')
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
dependencies = []
|
78
|
+
metadata['dependencies'].each do |d|
|
79
|
+
tmp = { module_name: d['name'].sub('/', '-') }
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
|
81
|
+
if d.key?('version_requirement')
|
82
|
+
tmp[:version] = module_version_from_requirement(tmp[:module_name],
|
83
|
+
d['version_requirement'])
|
84
|
+
end
|
85
|
+
dependencies.push(tmp)
|
78
86
|
end
|
79
|
-
|
87
|
+
|
88
|
+
dependencies
|
80
89
|
end
|
81
90
|
|
82
|
-
|
83
|
-
|
91
|
+
# This method takes a module name and the version requirement string from the
|
92
|
+
# metadata.json file, containing either lower bounds of version or both lower
|
93
|
+
# and upper bounds. The function then uses the forge rest endpoint to find
|
94
|
+
# the most recent release of the given module matching the version requirement
|
95
|
+
def module_version_from_requirement(mod_name, vr_str)
|
96
|
+
require 'net/http'
|
97
|
+
uri = URI("#{forge_api}v3/modules/#{mod_name}")
|
98
|
+
response = Net::HTTP.get_response(uri)
|
99
|
+
raise "Puppetforge API error '#{uri}': '#{response.body}'" if response.code.to_i >= 400
|
100
|
+
|
101
|
+
forge_data = JSON.parse(response.body)
|
102
|
+
|
103
|
+
vrs = version_requirements_from_string(vr_str)
|
84
104
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
response = Net::HTTP.get(uri)
|
93
|
-
forge_data = JSON.parse(response)
|
94
|
-
|
95
|
-
vrs = version_requirements_from_string(vr_str)
|
96
|
-
|
97
|
-
# Here we iterate the releases of the given module and pick the most recent
|
98
|
-
# that matches to version requirement
|
99
|
-
forge_data['releases'].each do |rel|
|
100
|
-
return rel['version'] if vrs.all? { |vr| vr.match?('', rel['version']) }
|
105
|
+
# Here we iterate the releases of the given module and pick the most recent
|
106
|
+
# that matches to version requirement
|
107
|
+
forge_data['releases'].each do |rel|
|
108
|
+
return rel['version'] if vrs.all? { |vr| vr.match?('', rel['version']) }
|
109
|
+
end
|
110
|
+
|
111
|
+
raise "No release version found matching '#{mod_name}' '#{vr_str}'"
|
101
112
|
end
|
102
113
|
|
103
|
-
|
104
|
-
|
114
|
+
# This method takes a version requirement string as specified in the link
|
115
|
+
# below, with either simply a lower bound, or both lower and upper bounds and
|
116
|
+
# returns an array of Gem::Dependency objects
|
117
|
+
# https://docs.puppet.com/puppet/latest/modules_metadata.html
|
118
|
+
def version_requirements_from_string(vr_str)
|
119
|
+
ops = vr_str.scan(/[(<|>=)]{1,2}/i)
|
120
|
+
vers = vr_str.scan(/[(0-9|.)]+/i)
|
105
121
|
|
106
|
-
|
107
|
-
|
108
|
-
# returns an array of Gem::Dependency objects
|
109
|
-
# https://docs.puppet.com/puppet/latest/modules_metadata.html
|
110
|
-
def version_requirements_from_string(vr_str)
|
111
|
-
ops = vr_str.scan(/[(<|>|=)]{1,2}/i)
|
112
|
-
vers = vr_str.scan(/[(0-9|\.)]+/i)
|
122
|
+
raise 'Invalid version requirements' if ops.count != 0 &&
|
123
|
+
ops.count != vers.count
|
113
124
|
|
114
|
-
|
115
|
-
|
125
|
+
vrs = []
|
126
|
+
ops.each_with_index do |op, index|
|
127
|
+
vrs.push(Gem::Dependency.new('', "#{op} #{vers[index]}"))
|
128
|
+
end
|
116
129
|
|
117
|
-
|
118
|
-
ops.each_with_index do |op, index|
|
119
|
-
vrs.push(Gem::Dependency.new('', "#{op} #{vers[index]}"))
|
130
|
+
vrs
|
120
131
|
end
|
121
132
|
|
122
|
-
|
123
|
-
|
133
|
+
# This method will return array of all masters. If no masters exist, it will
|
134
|
+
# return all agent nodes. If no nodes tagged master or agent exist, all nodes
|
135
|
+
# will be returned
|
136
|
+
def hosts_to_install_module_on
|
137
|
+
masters = hosts_with_role(hosts, :master)
|
138
|
+
return masters unless masters.empty?
|
124
139
|
|
125
|
-
|
126
|
-
|
127
|
-
# will be returned
|
128
|
-
def hosts_to_install_module_on
|
129
|
-
masters = hosts_with_role(hosts, :master)
|
130
|
-
return masters unless masters.empty?
|
140
|
+
agents = hosts_with_role(hosts, :agent)
|
141
|
+
return agents unless agents.empty?
|
131
142
|
|
132
|
-
|
133
|
-
|
143
|
+
hosts
|
144
|
+
end
|
134
145
|
|
135
|
-
|
136
|
-
|
146
|
+
# This method will read the 'name' attribute from metadata.json file and
|
147
|
+
# remove the first segment. E.g. puppetlabs-vcsrepo -> vcsrepo
|
148
|
+
def module_name_from_metadata
|
149
|
+
res = get_module_name module_metadata['name']
|
150
|
+
raise 'Error getting module name' unless res
|
137
151
|
|
138
|
-
|
139
|
-
|
140
|
-
def module_name_from_metadata
|
141
|
-
res = get_module_name module_metadata['name']
|
142
|
-
raise 'Error getting module name' unless res
|
143
|
-
res[1]
|
144
|
-
end
|
152
|
+
res[1]
|
153
|
+
end
|
145
154
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
155
|
+
# This method uses the module_source_directory path to read the metadata.json
|
156
|
+
# file into a json array
|
157
|
+
def module_metadata
|
158
|
+
metadata_path = "#{$module_source_dir}/metadata.json"
|
159
|
+
raise "Error loading metadata.json file from #{$module_source_dir}" unless File.exist?(metadata_path)
|
160
|
+
|
161
|
+
JSON.parse(File.read(metadata_path))
|
152
162
|
end
|
153
|
-
JSON.parse(File.read(metadata_path))
|
154
|
-
end
|
155
163
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
164
|
+
# Use this property to store the module_source_dir, so we don't traverse
|
165
|
+
# the tree every time
|
166
|
+
def get_module_source_directory(call_stack)
|
167
|
+
matching_caller = call_stack.grep(/(spec_helper_acceptance|_spec)/i)
|
160
168
|
|
161
|
-
|
169
|
+
raise 'Error finding module source directory' if matching_caller.empty?
|
162
170
|
|
163
|
-
|
164
|
-
|
171
|
+
matching_caller = matching_caller[0] if matching_caller.is_a?(Array)
|
172
|
+
search_in = matching_caller[/[^:]+/]
|
165
173
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
174
|
+
module_source_dir = nil
|
175
|
+
# here we go up the file tree and search the directories for a
|
176
|
+
# valid metadata.json
|
177
|
+
while module_source_dir.nil? && search_in != File.dirname(search_in)
|
178
|
+
# remove last segment (file or folder, doesn't matter)
|
179
|
+
search_in = File.dirname(search_in)
|
172
180
|
|
173
|
-
|
174
|
-
|
175
|
-
|
181
|
+
# Append metadata.json, check it exists in the directory we're searching
|
182
|
+
metadata_path = File.join(search_in, 'metadata.json')
|
183
|
+
module_source_dir = search_in if File.exist?(metadata_path)
|
184
|
+
end
|
185
|
+
module_source_dir
|
176
186
|
end
|
177
|
-
module_source_dir
|
178
|
-
end
|
179
187
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
188
|
+
def forge_host
|
189
|
+
fh = ENV['BEAKER_FORGE_HOST'] # rubocop:disable Style/FetchEnvVar
|
190
|
+
unless fh.nil?
|
191
|
+
fh = "https://#{fh}" unless %r{^(https://|http://)}i.match?(fh)
|
192
|
+
fh += '/' unless fh != %r{/$}
|
193
|
+
return fh
|
194
|
+
end
|
195
|
+
|
196
|
+
'https://forge.puppet.com/'
|
186
197
|
end
|
187
198
|
|
188
|
-
|
189
|
-
|
199
|
+
def forge_api
|
200
|
+
fa = ENV['BEAKER_FORGE_API'] # rubocop:disable Style/FetchEnvVar
|
201
|
+
unless fa.nil?
|
202
|
+
fa = "https://#{fa}" unless %r{^(https://|http://)}i.match?(fa)
|
203
|
+
fa += '/' unless fa != %r{/$}
|
204
|
+
return fa
|
205
|
+
end
|
190
206
|
|
191
|
-
|
192
|
-
fa = ENV['BEAKER_FORGE_API']
|
193
|
-
unless fa.nil?
|
194
|
-
fa = 'https://' + fa if fa !~ /^(https:\/\/|http:\/\/)/i
|
195
|
-
fa += '/' unless fa != /\/$/
|
196
|
-
return fa
|
207
|
+
'https://forgeapi.puppetlabs.com/'
|
197
208
|
end
|
198
|
-
|
199
|
-
'https://forgeapi.puppetlabs.com/'
|
200
209
|
end
|
201
210
|
end
|
202
211
|
|
212
|
+
# rubocop:disable Style/MixinUsage
|
203
213
|
include Beaker::ModuleInstallHelper
|
214
|
+
# rubocop:enable Style/MixinUsage
|
204
215
|
# Use the caller (requirer) of this file to begin search for module source dir
|
205
216
|
$module_source_dir = get_module_source_directory caller
|
data/spec/spec_helper.rb
CHANGED
@@ -1 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-console'
|
6
|
+
require 'codecov'
|
7
|
+
rescue LoadError
|
8
|
+
else
|
9
|
+
SimpleCov.start do
|
10
|
+
track_files 'lib/**/*.rb'
|
11
|
+
|
12
|
+
add_filter '/spec'
|
13
|
+
|
14
|
+
enable_coverage :branch
|
15
|
+
|
16
|
+
# do not track vendored files
|
17
|
+
add_filter '/vendor'
|
18
|
+
add_filter '/.vendor'
|
19
|
+
end
|
20
|
+
|
21
|
+
SimpleCov.formatters = [
|
22
|
+
SimpleCov::Formatter::Console,
|
23
|
+
SimpleCov::Formatter::Codecov,
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
1
27
|
require 'beaker/module_install_helper'
|