vanagon 0.15.36 → 0.17.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 +4 -4
- data/README.md +48 -23
- data/bin/build +4 -25
- data/bin/build_host_info +4 -17
- data/bin/build_requirements +4 -31
- data/bin/inspect +4 -21
- data/bin/render +4 -22
- data/bin/ship +4 -28
- data/bin/sign +4 -11
- data/bin/vanagon +7 -0
- data/lib/vanagon.rb +1 -1
- data/lib/vanagon/cli.rb +94 -0
- data/lib/vanagon/cli/build.rb +75 -0
- data/lib/vanagon/cli/build_host_info.rb +49 -0
- data/lib/vanagon/cli/build_requirements.rb +60 -0
- data/lib/vanagon/cli/inspect.rb +65 -0
- data/lib/vanagon/cli/render.rb +51 -0
- data/lib/vanagon/cli/ship.rb +52 -0
- data/lib/vanagon/cli/sign.rb +34 -0
- data/lib/vanagon/driver.rb +11 -7
- data/lib/vanagon/engine/always_be_scheduling.rb +271 -1
- data/lib/vanagon/engine/docker.rb +101 -14
- data/lib/vanagon/engine/pooler.rb +7 -3
- data/lib/vanagon/platform.rb +5 -3
- data/lib/vanagon/platform/deb.rb +1 -1
- data/lib/vanagon/platform/dsl.rb +11 -0
- data/lib/vanagon/platform/rpm.rb +1 -1
- data/lib/vanagon/platform/windows.rb +29 -2
- data/lib/vanagon/project.rb +23 -4
- data/lib/vanagon/project/dsl.rb +33 -0
- data/lib/vanagon/utilities.rb +30 -8
- data/resources/rpm/project.spec.erb +3 -0
- data/spec/lib/vanagon/cli_spec.rb +80 -0
- data/spec/lib/vanagon/engine/always_be_scheduling_spec.rb +113 -1
- data/spec/lib/vanagon/engine/docker_spec.rb +74 -16
- data/spec/lib/vanagon/engine/ec2_spec.rb +2 -0
- data/spec/lib/vanagon/engine/pooler_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- metadata +56 -33
- data/lib/vanagon/optparse.rb +0 -86
- data/spec/lib/vanagon/optparse_spec.rb +0 -64
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
|
3
|
+
class Vanagon
|
4
|
+
class CLI
|
5
|
+
class Build < Vanagon::CLI
|
6
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
7
|
+
Usage:
|
8
|
+
build [options] <project-name> <platforms> [<targets>]
|
9
|
+
|
10
|
+
Options:
|
11
|
+
-h, --help Display help
|
12
|
+
-c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
|
13
|
+
-e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
|
14
|
+
-o, --only-build COMPONENT,COMPONENT,...
|
15
|
+
Only build listed COMPONENTs
|
16
|
+
-p, --preserve [RULE] Rule for VM preservation: never, on-failure, always
|
17
|
+
[Default: always]
|
18
|
+
-r, --remote-workdir DIRECTORY Working directory on the remote host
|
19
|
+
-s, --skipcheck Skip the "check" stage when building components
|
20
|
+
-w, --workdir DIRECTORY Working directory on the local host
|
21
|
+
-v, --verbose Only here for backwards compatibility. Does nothing.
|
22
|
+
DOCOPT
|
23
|
+
|
24
|
+
def parse(argv)
|
25
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
26
|
+
rescue Docopt::Exit => e
|
27
|
+
puts e.message
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(options) # rubocop:disable Metrics/AbcSize
|
32
|
+
project = options[:project_name]
|
33
|
+
platform_list = options[:platforms].split(',')
|
34
|
+
target_list = []
|
35
|
+
unless options[:targets].nil? || options[:targets].empty?
|
36
|
+
target_list = options[:targets].split(',')
|
37
|
+
end
|
38
|
+
|
39
|
+
platform_list.zip(target_list).each do |pair|
|
40
|
+
platform, target = pair
|
41
|
+
artifact = Vanagon::Driver.new(platform, project, options.merge({ 'target' => target }))
|
42
|
+
artifact.run
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def options_translate(docopt_options)
|
47
|
+
translations = {
|
48
|
+
'--verbose' => :verbose,
|
49
|
+
'--workdir' => :workdir,
|
50
|
+
'--remote-workdir' => :"remote-workdir",
|
51
|
+
'--configdir' => :configdir,
|
52
|
+
'--engine' => :engine,
|
53
|
+
'--skipcheck' => :skipcheck,
|
54
|
+
'--preserve' => :preserve,
|
55
|
+
'--only-build' => :only_build,
|
56
|
+
'<project-name>' => :project_name,
|
57
|
+
'<platforms>' => :platforms,
|
58
|
+
'<targets>' => :targets
|
59
|
+
}
|
60
|
+
return docopt_options.map { |k, v| [translations[k], v] }.to_h
|
61
|
+
end
|
62
|
+
|
63
|
+
def options_validate(options)
|
64
|
+
# Handle --preserve option checking
|
65
|
+
valid_preserves = %w[always never on-failure]
|
66
|
+
unless valid_preserves.include? options[:preserve]
|
67
|
+
raise InvalidArgument, "--preserve option can only be one of: " +
|
68
|
+
valid_preserves.join(', ')
|
69
|
+
end
|
70
|
+
options[:preserve] = options[:preserve].to_sym
|
71
|
+
return options
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
|
3
|
+
class Vanagon
|
4
|
+
class CLI
|
5
|
+
class BuildHostInfo < Vanagon::CLI
|
6
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
7
|
+
Usage:
|
8
|
+
build_host_info [options] <project-name> <platforms>
|
9
|
+
|
10
|
+
Options:
|
11
|
+
-h, --help Display help
|
12
|
+
-c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
|
13
|
+
-e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
|
14
|
+
-w, --workdir DIRECTORY Working directory on the local host
|
15
|
+
-v, --verbose Only here for backwards compatibility. Does nothing.
|
16
|
+
DOCOPT
|
17
|
+
|
18
|
+
def parse(argv)
|
19
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
20
|
+
rescue Docopt::Exit => e
|
21
|
+
puts e.message
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(options)
|
26
|
+
platforms = options[:platforms].split(',')
|
27
|
+
project = options[:project_name]
|
28
|
+
|
29
|
+
platforms.each do |platform|
|
30
|
+
driver = Vanagon::Driver.new(platform, project, options)
|
31
|
+
$stdout.puts JSON.generate(driver.build_host_info)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def options_translate(docopt_options)
|
36
|
+
translations = {
|
37
|
+
'--verbose' => :verbose,
|
38
|
+
'--workdir' => :workdir,
|
39
|
+
'--configdir' => :configdir,
|
40
|
+
'--engine' => :engine,
|
41
|
+
'<project-name>' => :project_name,
|
42
|
+
'<platforms>' => :platforms,
|
43
|
+
'<targets>' => :targets
|
44
|
+
}
|
45
|
+
return docopt_options.map { |k, v| [translations[k], v] }.to_h
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Vanagon
|
5
|
+
class CLI
|
6
|
+
class BuildRequirements < Vanagon::CLI
|
7
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
8
|
+
Usage:
|
9
|
+
build_requirements [options] <project-name> <platform>
|
10
|
+
|
11
|
+
Options:
|
12
|
+
-h, --help Display help
|
13
|
+
-c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
|
14
|
+
-e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
|
15
|
+
-w, --workdir DIRECTORY Working directory on the local host
|
16
|
+
-v, --verbose Only here for backwards compatibility. Does nothing.
|
17
|
+
DOCOPT
|
18
|
+
|
19
|
+
def parse(argv)
|
20
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
21
|
+
rescue Docopt::Exit => e
|
22
|
+
puts e.message
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(options) # rubocop:disable Metrics/AbcSize
|
27
|
+
platform = options[:platform]
|
28
|
+
project = options[:project_name]
|
29
|
+
driver = Vanagon::Driver.new(platform, project)
|
30
|
+
|
31
|
+
components = driver.project.components
|
32
|
+
component_names = components.map(&:name)
|
33
|
+
build_requirements = []
|
34
|
+
components.each do |component|
|
35
|
+
build_requirements << component.build_requires.reject do |requirement|
|
36
|
+
# only include external requirements: i.e. those that do not match
|
37
|
+
# other components in the project
|
38
|
+
component_names.include?(requirement)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
$stdout.puts
|
43
|
+
$stdout.puts "**** External packages required to build #{project} on #{platform}: ***"
|
44
|
+
$stdout.puts JSON.pretty_generate(build_requirements.flatten.uniq.sort)
|
45
|
+
end
|
46
|
+
|
47
|
+
def options_translate(docopt_options)
|
48
|
+
translations = {
|
49
|
+
'--verbose' => :verbose,
|
50
|
+
'--workdir' => :workdir,
|
51
|
+
'--configdir' => :configdir,
|
52
|
+
'--engine' => :engine,
|
53
|
+
'<project-name>' => :project_name,
|
54
|
+
'<platform>' => :platform,
|
55
|
+
}
|
56
|
+
return docopt_options.map { |k, v| [translations[k], v] }.to_h
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Vanagon
|
5
|
+
class CLI
|
6
|
+
class Inspect < Vanagon::CLI
|
7
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
8
|
+
Usage:
|
9
|
+
inspect [options] <project-name> <platforms>
|
10
|
+
|
11
|
+
Options:
|
12
|
+
-h, --help Display help
|
13
|
+
-c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
|
14
|
+
-e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
|
15
|
+
|
16
|
+
-p, --preserve [RULE] Rule for VM preservation: never, on-failure, always
|
17
|
+
[Default: on-failure]
|
18
|
+
-w, --workdir DIRECTORY Working directory on the local host
|
19
|
+
-v, --verbose Only here for backwards compatibility. Does nothing.
|
20
|
+
DOCOPT
|
21
|
+
|
22
|
+
def parse(argv)
|
23
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
24
|
+
rescue Docopt::Exit => e
|
25
|
+
puts e.message
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def run(options)
|
30
|
+
platforms = options[:platforms].split(',')
|
31
|
+
project = options[:project_name]
|
32
|
+
|
33
|
+
platforms.each do |platform|
|
34
|
+
driver = Vanagon::Driver.new(platform, project, options)
|
35
|
+
components = driver.project.components.map(&:to_hash)
|
36
|
+
$stdout.puts JSON.pretty_generate(components)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def options_translate(docopt_options)
|
41
|
+
translations = {
|
42
|
+
'--verbose' => :verbose,
|
43
|
+
'--workdir' => :workdir,
|
44
|
+
'--configdir' => :configdir,
|
45
|
+
'--engine' => :engine,
|
46
|
+
'--preserve' => :preserve,
|
47
|
+
'<project-name>' => :project_name,
|
48
|
+
'<platforms>' => :platforms
|
49
|
+
}
|
50
|
+
return docopt_options.map { |k, v| [translations[k], v] }.to_h
|
51
|
+
end
|
52
|
+
|
53
|
+
def options_validate(options)
|
54
|
+
# Handle --preserve option checking
|
55
|
+
valid_preserves = %w[always never on-failure]
|
56
|
+
unless valid_preserves.include? options[:preserve]
|
57
|
+
raise InvalidArgument, "--preserve option can only be one of: " +
|
58
|
+
valid_preserves.join(', ')
|
59
|
+
end
|
60
|
+
options[:preserve] = options[:preserve].to_sym
|
61
|
+
return options
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Vanagon
|
5
|
+
class CLI
|
6
|
+
class Render < Vanagon::CLI
|
7
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
8
|
+
Usage:
|
9
|
+
render [options] <project-name> <platforms>
|
10
|
+
|
11
|
+
Options:
|
12
|
+
-h, --help Display help
|
13
|
+
-c, --configdir DIRECTORY Configuration directory [default: #{Dir.pwd}/configs]
|
14
|
+
-e, --engine ENGINE Custom engine to use [base, local, docker, pooler] [default: pooler]
|
15
|
+
-w, --workdir DIRECTORY Working directory on the local host
|
16
|
+
-v, --verbose Only here for backwards compatibility. Does nothing.
|
17
|
+
DOCOPT
|
18
|
+
|
19
|
+
def parse(argv)
|
20
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
21
|
+
rescue Docopt::Exit => e
|
22
|
+
puts e.message
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(options)
|
27
|
+
platforms = options[:platforms].split(',')
|
28
|
+
project = options[:project_name]
|
29
|
+
target_list = []
|
30
|
+
|
31
|
+
platforms.zip(target_list).each do |pair|
|
32
|
+
platform, target = pair
|
33
|
+
artifact = Vanagon::Driver.new(platform, project, options.merge({ :target => target }))
|
34
|
+
artifact.render
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def options_translate(docopt_options)
|
39
|
+
translations = {
|
40
|
+
'--verbose' => :verbose,
|
41
|
+
'--workdir' => :workdir,
|
42
|
+
'--configdir' => :configdir,
|
43
|
+
'--engine' => :engine,
|
44
|
+
'<project-name>' => :project_name,
|
45
|
+
'<platforms>' => :platforms,
|
46
|
+
}
|
47
|
+
return docopt_options.map { |k, v| [translations[k], v] }.to_h
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
|
3
|
+
class Vanagon
|
4
|
+
class CLI
|
5
|
+
class Ship < Vanagon::CLI
|
6
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
7
|
+
Usage:
|
8
|
+
ship [--help]
|
9
|
+
|
10
|
+
Options:
|
11
|
+
-h, --help Display help
|
12
|
+
DOCOPT
|
13
|
+
|
14
|
+
def parse(argv)
|
15
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
16
|
+
rescue Docopt::Exit => e
|
17
|
+
puts e.message
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(_)
|
22
|
+
ENV['PROJECT_ROOT'] = Dir.pwd
|
23
|
+
|
24
|
+
artifactory_warning = <<-DOC
|
25
|
+
Unable to ship packages to artifactory. Please make sure you are pointing to a
|
26
|
+
recent version of packaging in your Gemfile. Please also make sure you include
|
27
|
+
the artifactory gem in your Gemfile.
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
gem 'packaging', :github => 'puppetlabs/packaging', branch: '1.0.x'
|
31
|
+
gem 'artifactory'
|
32
|
+
DOC
|
33
|
+
|
34
|
+
if Dir['output/**/*'].select { |entry| File.file?(entry) }.empty?
|
35
|
+
warn 'vanagon: Error: No packages to ship in the "output" directory. Maybe build some first?'
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'packaging'
|
40
|
+
Pkg::Util::RakeUtils.load_packaging_tasks
|
41
|
+
Pkg::Util::RakeUtils.invoke_task('pl:jenkins:ship', 'artifacts', 'output')
|
42
|
+
begin
|
43
|
+
Pkg::Util::RakeUtils.invoke_task('pl:jenkins:ship_to_artifactory', 'output')
|
44
|
+
rescue LoadError
|
45
|
+
warn artifactory_warning
|
46
|
+
rescue StandardError
|
47
|
+
warn artifactory_warning
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'docopt'
|
2
|
+
|
3
|
+
class Vanagon
|
4
|
+
class CLI
|
5
|
+
class Sign < Vanagon::CLI
|
6
|
+
DOCUMENTATION = <<~DOCOPT.freeze
|
7
|
+
Usage:
|
8
|
+
sign [--help]
|
9
|
+
|
10
|
+
Options:
|
11
|
+
-h, --help Display help
|
12
|
+
DOCOPT
|
13
|
+
|
14
|
+
def parse(argv)
|
15
|
+
Docopt.docopt(DOCUMENTATION, { argv: argv })
|
16
|
+
rescue Docopt::Exit => e
|
17
|
+
puts e.message
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(_)
|
22
|
+
ENV['PROJECT_ROOT'] = Dir.pwd
|
23
|
+
if Dir['output/**/*'].select { |entry| File.file?(entry) }.empty?
|
24
|
+
warn 'sign: Error: No packages to sign in the "output" directory. Maybe build some first?'
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'packaging'
|
29
|
+
Pkg::Util::RakeUtils.load_packaging_tasks
|
30
|
+
Pkg::Util::RakeUtils.invoke_task('pl:jenkins:sign_all', 'output')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/vanagon/driver.rb
CHANGED
@@ -29,7 +29,6 @@ class Vanagon
|
|
29
29
|
components = options[:components] || []
|
30
30
|
only_build = options[:only_build]
|
31
31
|
target = options[:target]
|
32
|
-
engine = options[:engine] || 'pooler'
|
33
32
|
|
34
33
|
@platform = Vanagon::Platform.load_platform(platform, File.join(@@configdir, "platforms"))
|
35
34
|
@project = Vanagon::Project.load_project(project, File.join(@@configdir, "projects"), @platform, components)
|
@@ -40,9 +39,14 @@ class Vanagon
|
|
40
39
|
|
41
40
|
@remote_workdir = options[:"remote-workdir"]
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
if options[:engine]
|
43
|
+
# Use the explicitly configured engine.
|
44
|
+
load_engine_object(options[:engine], @platform, target)
|
45
|
+
else
|
46
|
+
# Use 'pooler' as a default, but also apply selection logic that may
|
47
|
+
# choose something different based on platform configuration.
|
48
|
+
load_engine('pooler', @platform, target)
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def filter_out_components(only_build)
|
@@ -73,8 +77,8 @@ class Vanagon
|
|
73
77
|
def load_engine_object(engine_type, platform, target)
|
74
78
|
require "vanagon/engine/#{engine_type}"
|
75
79
|
@engine = Object::const_get("Vanagon::Engine::#{camelize(engine_type)}").new(platform, target, remote_workdir: remote_workdir)
|
76
|
-
rescue StandardError
|
77
|
-
|
80
|
+
rescue StandardError, ScriptError => e
|
81
|
+
raise Vanagon::Error.wrap(e, "Could not load the desired engine '#{engine_type}'")
|
78
82
|
end
|
79
83
|
|
80
84
|
def camelize(string)
|
@@ -140,7 +144,7 @@ class Vanagon
|
|
140
144
|
@project.make_bill_of_materials(workdir)
|
141
145
|
# Don't generate packaging artifacts if no_packaging is set
|
142
146
|
@project.generate_packaging_artifacts(workdir) unless @project.no_packaging
|
143
|
-
@project.save_manifest_json(@platform)
|
147
|
+
@project.save_manifest_json(@platform, workdir)
|
144
148
|
@engine.ship_workdir(workdir)
|
145
149
|
@engine.dispatch("(cd #{@engine.remote_workdir}; #{@platform.make} #{make_target})")
|
146
150
|
@engine.retrieve_built_artifact(@project.artifacts_to_fetch, @project.no_packaging)
|
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'vanagon/engine/base'
|
2
1
|
require 'json'
|
2
|
+
require 'vanagon/engine/base'
|
3
|
+
require 'yaml'
|
3
4
|
|
4
5
|
class Vanagon
|
5
6
|
class Engine
|
@@ -82,9 +83,15 @@ class Vanagon
|
|
82
83
|
# {"name":"aix-53-ppc","engine":"always_be_scheduling"}
|
83
84
|
# ```
|
84
85
|
class AlwaysBeScheduling < Base
|
86
|
+
attr_reader :token
|
87
|
+
attr_reader :token_vmpooler
|
88
|
+
|
85
89
|
def initialize(platform, target, **opts)
|
86
90
|
super
|
87
91
|
|
92
|
+
@available_abs_endpoint = "https://abs-prod.k8s.infracore.puppet.net/api/v2"
|
93
|
+
@token_vmpooler = ENV['VMPOOLER_TOKEN']
|
94
|
+
@token = load_token
|
88
95
|
Vanagon::Driver.logger.debug "AlwaysBeScheduling engine invoked."
|
89
96
|
end
|
90
97
|
|
@@ -94,6 +101,7 @@ class Vanagon
|
|
94
101
|
end
|
95
102
|
|
96
103
|
# return the platform name as the "host" name
|
104
|
+
# order of preference: abs_resource_name, vmpooler_template or name
|
97
105
|
def build_host_name
|
98
106
|
if @platform.abs_resource_name
|
99
107
|
@platform.abs_resource_name
|
@@ -103,6 +111,268 @@ class Vanagon
|
|
103
111
|
@platform.name
|
104
112
|
end
|
105
113
|
end
|
114
|
+
|
115
|
+
# Retrieve the ABS token from an environment variable
|
116
|
+
# ("ABS_TOKEN") or from a number of potential configuration
|
117
|
+
# files (~/.vanagon-token or ~/.vmfloaty.yml).
|
118
|
+
# @return [String, nil] token for use with the vmpooler
|
119
|
+
def load_token
|
120
|
+
ENV['ABS_TOKEN'] || token_from_file
|
121
|
+
end
|
122
|
+
|
123
|
+
# a wrapper method around retrieving a token,
|
124
|
+
# with an explicitly ordered preference for a Vanagon-specific
|
125
|
+
# token file or a preexisting vmfoaty yaml file.
|
126
|
+
#
|
127
|
+
# @return [String, nil] token for use with the vmpooler
|
128
|
+
def token_from_file
|
129
|
+
read_vanagon_token || read_vmfloaty_token
|
130
|
+
end
|
131
|
+
private :token_from_file
|
132
|
+
|
133
|
+
# Read an ABS/vmpooler token from the plaintext vanagon-token file,
|
134
|
+
# as outlined in the project README.
|
135
|
+
# The first line should be the ABS token
|
136
|
+
# and the second (optional) line should be the vmpooler token
|
137
|
+
# Saves the second line into the @token_vmpooler variable
|
138
|
+
#
|
139
|
+
# @return [String, nil] the vanagon vmpooler token value
|
140
|
+
def read_vanagon_token(path = "~/.vanagon-token")
|
141
|
+
absolute_path = File.expand_path(path)
|
142
|
+
return nil unless File.exist?(absolute_path)
|
143
|
+
|
144
|
+
warn "Reading ABS token from: #{path}"
|
145
|
+
contents = File.read(absolute_path).chomp
|
146
|
+
lines = contents.each_line.map(&:chomp)
|
147
|
+
|
148
|
+
abs = lines.shift
|
149
|
+
@token_vmpooler = lines.shift
|
150
|
+
|
151
|
+
warn "Please add a second line with the vmpooler token to be able to modify or see the VM in floaty/bit-bar" if @token_vmpooler.nil?
|
152
|
+
return abs
|
153
|
+
end
|
154
|
+
private :read_vanagon_token
|
155
|
+
|
156
|
+
# Read a vmpooler token from the yaml formatted vmfloaty config,
|
157
|
+
# as outlined by the vmfloaty project:
|
158
|
+
# https://github.com/puppetlabs/vmfloaty
|
159
|
+
#
|
160
|
+
# It returns the top-level token value or the first 'abs' service
|
161
|
+
# token value it finds in the services list
|
162
|
+
# it sets @token_vmpooler with the optional vmpooler token
|
163
|
+
# if the abs services has a vmpooler_fallback, and that service
|
164
|
+
# has a token eg
|
165
|
+
#
|
166
|
+
# TOP-LEVEL DEFINED => would get only the abs token
|
167
|
+
# user: 'jdoe'
|
168
|
+
# url: 'https://abs.example.net'
|
169
|
+
# token: '456def789'
|
170
|
+
#
|
171
|
+
# MULTIPLE SERVICES DEFINED => would get the abs token in 'abs-prod' and the vmpooler token in 'vmpooler-prod'
|
172
|
+
# user: 'jdoe'
|
173
|
+
# services:
|
174
|
+
# abs-prod:
|
175
|
+
# type: 'abs'
|
176
|
+
# url: 'https://abs.example.net/api/v2'
|
177
|
+
# token: '123abc456'
|
178
|
+
# vmpooler_fallback: 'vmpooler-prod'
|
179
|
+
# vmpooler-dev:
|
180
|
+
# type: 'vmpooler'
|
181
|
+
# url: 'https://vmpooler-dev.example.net'
|
182
|
+
# token: '987dsa654'
|
183
|
+
# vmpooler-prod:
|
184
|
+
# type: 'vmpooler'
|
185
|
+
# url: 'https://vmpooler.example.net'
|
186
|
+
# token: '456def789'
|
187
|
+
#
|
188
|
+
# @return [String, nil] the vmfloaty vmpooler token value
|
189
|
+
def read_vmfloaty_token(path = "~/.vmfloaty.yml") # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
190
|
+
absolute_path = File.expand_path(path)
|
191
|
+
return nil unless File.exist?(absolute_path)
|
192
|
+
|
193
|
+
yaml_config = YAML.load_file(absolute_path)
|
194
|
+
abs_token = nil
|
195
|
+
abs_service_name = nil
|
196
|
+
if yaml_config['token'] # top level
|
197
|
+
abs_token = yaml_config['token']
|
198
|
+
elsif yaml_config['services']
|
199
|
+
yaml_config['services'].each do |name, value|
|
200
|
+
if value['type'] == "abs" && value['token']
|
201
|
+
abs_token = value['token']
|
202
|
+
abs_service_name = name
|
203
|
+
vmpooler_fallback = value['vmpooler_fallback']
|
204
|
+
unless vmpooler_fallback.nil? || yaml_config['services'][vmpooler_fallback].nil? || yaml_config['services'][vmpooler_fallback]['token'].nil?
|
205
|
+
@token_vmpooler = yaml_config['services'][vmpooler_fallback]['token']
|
206
|
+
end
|
207
|
+
break
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
message = "Reading ABS token from: #{path}"
|
212
|
+
if abs_service_name
|
213
|
+
message.concat(" for service named #{abs_service_name}")
|
214
|
+
if @token_vmpooler.nil?
|
215
|
+
message.concat(" but there was no vmpooler_fallback value, please add one if you want to be able to modify the VM via bit-bar/floaty")
|
216
|
+
else
|
217
|
+
message.concat(" with a vmpooler_fallback token")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
warn message
|
221
|
+
return abs_token
|
222
|
+
end
|
223
|
+
private :read_vmfloaty_token
|
224
|
+
|
225
|
+
# This method is used to obtain a vm to build upon using Puppet's internal
|
226
|
+
# ABS (https://github.com/puppetlabs/always-be-scheduling) which is a level of abstraction for other
|
227
|
+
# engines using similar APIs
|
228
|
+
# @raise [Vanagon::Error] if a target cannot be obtained
|
229
|
+
def select_target
|
230
|
+
@pooler = select_target_from(@available_abs_endpoint)
|
231
|
+
raise Vanagon::Error, "Something went wrong getting a target vm to build on" if @pooler.empty?
|
232
|
+
end
|
233
|
+
|
234
|
+
# Attempt to provision a host from a specific pooler.
|
235
|
+
def select_target_from(pooler) # rubocop:disable Metrics/AbcSize
|
236
|
+
request_object = build_request_object
|
237
|
+
|
238
|
+
warn "Requesting VMs with job_id: #{@saved_job_id}. Will poll for up to an hour."
|
239
|
+
#the initial request is always replied with "come back again"
|
240
|
+
response = Vanagon::Utilities.http_request_generic(
|
241
|
+
"#{pooler}/request",
|
242
|
+
'POST',
|
243
|
+
request_object.to_json,
|
244
|
+
{ 'X-AUTH-TOKEN' => @token }
|
245
|
+
)
|
246
|
+
|
247
|
+
unless response.code == "202"
|
248
|
+
warn "failed to request ABS with code #{response.code}"
|
249
|
+
if valid_json?(response.body)
|
250
|
+
response_json = JSON.parse(response.body)
|
251
|
+
warn "reason: #{response_json['reason']}"
|
252
|
+
end
|
253
|
+
return ''
|
254
|
+
end
|
255
|
+
response_body = check_queue(pooler, request_object)
|
256
|
+
|
257
|
+
return '' unless response_body["ok"]
|
258
|
+
@target = response_body[build_host_name]['hostname']
|
259
|
+
Vanagon::Driver.logger.info "Reserving #{@target} (#{build_host_name}) [#{@token ? 'token used' : 'no token used'}]"
|
260
|
+
return pooler
|
261
|
+
end
|
262
|
+
|
263
|
+
# main loop where the status of the request is checked, to see if the request
|
264
|
+
# has been allocated
|
265
|
+
def check_queue(pooler, request_object)
|
266
|
+
retries = 360 # ~ one hour
|
267
|
+
response_body = nil
|
268
|
+
begin
|
269
|
+
(1..retries).each do |i|
|
270
|
+
response = Vanagon::Utilities.http_request_generic(
|
271
|
+
"#{pooler}/request",
|
272
|
+
'POST',
|
273
|
+
request_object.to_json,
|
274
|
+
{ 'X-AUTH-TOKEN' => @token }
|
275
|
+
)
|
276
|
+
response_body = validate_queue_status_response(response.code, response.body)
|
277
|
+
break if response_body
|
278
|
+
|
279
|
+
sleep_seconds = 10 if i >= 10
|
280
|
+
sleep_seconds = i if i < 10
|
281
|
+
warn "Waiting #{sleep_seconds} seconds to check if ABS request has been filled. (x#{i})"
|
282
|
+
|
283
|
+
sleep(sleep_seconds)
|
284
|
+
end
|
285
|
+
rescue SystemExit, Interrupt
|
286
|
+
warn "\nVanagon interrupted during mains ABS polling. Make sure you delete the requested job_id #{@saved_job_id}"
|
287
|
+
raise
|
288
|
+
end
|
289
|
+
response_body = translated(response_body, @saved_job_id)
|
290
|
+
response_body
|
291
|
+
end
|
292
|
+
|
293
|
+
def validate_queue_status_response(status_code, body)
|
294
|
+
case status_code
|
295
|
+
when "200"
|
296
|
+
return JSON.parse(body) unless body.empty? || !valid_json?(body)
|
297
|
+
when "202"
|
298
|
+
return nil
|
299
|
+
when "401"
|
300
|
+
raise Vanagon::Error, "HTTP #{status_code}: The token provided could not authenticate.\n#{body}"
|
301
|
+
when "503"
|
302
|
+
return nil
|
303
|
+
else
|
304
|
+
raise Vanagon::Error, "HTTP #{status_code}: request to ABS failed!\n#{body}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# This method is used to tell the ABS to delete the job_id requested
|
309
|
+
# otherwise the resources will eventually get allocated asynchronously
|
310
|
+
# and will keep running until the end of their lifetime.
|
311
|
+
def teardown # rubocop:disable Metrics/AbcSize
|
312
|
+
request_object = {
|
313
|
+
'job_id' => @saved_job_id,
|
314
|
+
}
|
315
|
+
|
316
|
+
response = Vanagon::Utilities.http_request_generic(
|
317
|
+
"#{@available_abs_endpoint}/return",
|
318
|
+
"POST",
|
319
|
+
request_object.to_json,
|
320
|
+
{ 'X-AUTH-TOKEN' => @token }
|
321
|
+
)
|
322
|
+
if response && response.body == 'OK'
|
323
|
+
Vanagon::Driver.logger.info "#{@saved_job_id} has been scheduled for removal"
|
324
|
+
warn "#{@saved_job_id} has been scheduled for removal"
|
325
|
+
else
|
326
|
+
Vanagon::Driver.logger.info "#{@saved_job_id} could not be scheduled for removal: #{response.body}"
|
327
|
+
warn "#{@saved_job_id} could not be scheduled for removal"
|
328
|
+
end
|
329
|
+
rescue Vanagon::Error => e
|
330
|
+
Vanagon::Driver.logger.info "#{@saved_job_id} could not be scheduled for removal (#{e.message})"
|
331
|
+
warn "#{@saved_job_id} could not be scheduled for removal (#{e.message})"
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
|
336
|
+
def translated(response_body, job_id)
|
337
|
+
vmpooler_formatted_body = { 'job_id' => job_id }
|
338
|
+
|
339
|
+
response_body.each do |host| # in this context there should be only one host
|
340
|
+
vmpooler_formatted_body[host['type']] = { 'hostname' => host['hostname'] }
|
341
|
+
end
|
342
|
+
vmpooler_formatted_body['ok'] = true
|
343
|
+
|
344
|
+
vmpooler_formatted_body
|
345
|
+
end
|
346
|
+
|
347
|
+
def build_request_object
|
348
|
+
user = ENV['USER'] || ENV['USERNAME'] || 'unknown'
|
349
|
+
|
350
|
+
@saved_job_id = user + "-" + DateTime.now.strftime('%Q')
|
351
|
+
request_object = {
|
352
|
+
:resources => { build_host_name => 1 },
|
353
|
+
:job => {
|
354
|
+
:id => @saved_job_id,
|
355
|
+
:tags => {
|
356
|
+
:jenkins_build_url => ENV['BUILD_URL'],
|
357
|
+
:project => ENV['JOB_NAME'] || 'vanagon',
|
358
|
+
:created_by => user,
|
359
|
+
:user => user
|
360
|
+
},
|
361
|
+
},
|
362
|
+
:priority => 1, # DO NOT use priority 1 in automated CI runs
|
363
|
+
}
|
364
|
+
unless @token_vmpooler.nil?
|
365
|
+
request_object[:vm_token] = @token_vmpooler
|
366
|
+
end
|
367
|
+
request_object
|
368
|
+
end
|
369
|
+
|
370
|
+
def valid_json?(json)
|
371
|
+
JSON.parse(json)
|
372
|
+
return true
|
373
|
+
rescue TypeError, JSON::ParserError
|
374
|
+
return false
|
375
|
+
end
|
106
376
|
end
|
107
377
|
end
|
108
378
|
end
|