vanagon 0.15.35 → 0.16.1
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/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/docker.rb +101 -14
- data/lib/vanagon/platform.rb +9 -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/resources/rpm/project.spec.erb +9 -0
- data/spec/lib/vanagon/cli_spec.rb +80 -0
- data/spec/lib/vanagon/engine/docker_spec.rb +74 -16
- metadata +55 -32
- data/lib/vanagon/optparse.rb +0 -86
- data/spec/lib/vanagon/optparse_spec.rb +0 -64
@@ -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)
|
@@ -10,6 +10,7 @@ class Vanagon
|
|
10
10
|
|
11
11
|
@docker_cmd = Vanagon::Utilities.find_program_on_path('docker')
|
12
12
|
@required_attributes << "docker_image"
|
13
|
+
@required_attributes.delete('ssh_port') if @platform.use_docker_exec
|
13
14
|
end
|
14
15
|
|
15
16
|
# Get the engine name
|
@@ -33,25 +34,16 @@ class Vanagon
|
|
33
34
|
# This method is used to obtain a vm to build upon using
|
34
35
|
# a docker container.
|
35
36
|
# @raise [Vanagon::Error] if a target cannot be obtained
|
36
|
-
def select_target
|
37
|
+
def select_target
|
38
|
+
ssh_args = @platform.use_docker_exec ? '' : "-p #{@platform.ssh_port}:22"
|
37
39
|
extra_args = @platform.docker_run_args.nil? ? [] : @platform.docker_run_args
|
38
40
|
|
39
|
-
Vanagon::Utilities.ex("#{@docker_cmd} run -d --name #{build_host_name}-builder
|
41
|
+
Vanagon::Utilities.ex("#{@docker_cmd} run -d --name #{build_host_name}-builder #{ssh_args} #{extra_args.join(' ')} #{@platform.docker_image}")
|
40
42
|
@target = 'localhost'
|
41
43
|
|
42
|
-
|
43
|
-
# second sleep between errors to account for network resets while SSHD
|
44
|
-
# is starting. Allow a maximum of 5 seconds for SSHD to start.
|
45
|
-
Vanagon::Utilities.retry_with_timeout(5, 5) do
|
46
|
-
begin
|
47
|
-
Vanagon::Utilities.remote_ssh_command("#{@target_user}@#{@target}", 'exit', @platform.ssh_port)
|
48
|
-
rescue StandardError => e
|
49
|
-
sleep(1) # Give SSHD some time to start.
|
50
|
-
raise e
|
51
|
-
end
|
52
|
-
end
|
44
|
+
wait_for_ssh unless @platform.use_docker_exec
|
53
45
|
rescue StandardError => e
|
54
|
-
raise Vanagon::Error.wrap(e, "Something went wrong getting a target vm to build on using
|
46
|
+
raise Vanagon::Error.wrap(e, "Something went wrong getting a target vm to build on using Docker.")
|
55
47
|
end
|
56
48
|
|
57
49
|
# This method is used to tell the vmpooler to delete the instance of the
|
@@ -62,6 +54,101 @@ class Vanagon
|
|
62
54
|
rescue Vanagon::Error => e
|
63
55
|
warn "There was a problem tearing down the docker container #{build_host_name}-builder (#{e.message})."
|
64
56
|
end
|
57
|
+
|
58
|
+
def dispatch(command, return_output = false)
|
59
|
+
if @platform.use_docker_exec
|
60
|
+
docker_exec(command, return_output)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def ship_workdir(workdir)
|
67
|
+
if @platform.use_docker_exec
|
68
|
+
docker_cp_globs_to("#{workdir}/*", @remote_workdir)
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def retrieve_built_artifact(artifacts_to_fetch, no_packaging)
|
75
|
+
if @platform.use_docker_exec
|
76
|
+
output_path = 'output/'
|
77
|
+
FileUtils.mkdir_p(output_path)
|
78
|
+
unless no_packaging
|
79
|
+
artifacts_to_fetch << "#{@remote_workdir}/output/*"
|
80
|
+
end
|
81
|
+
|
82
|
+
docker_cp_globs_from(artifacts_to_fetch, 'output/')
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Execute a command on a container via docker exec
|
89
|
+
def docker_exec(command, return_output = false)
|
90
|
+
command = command.gsub("'", "'\\\\''")
|
91
|
+
Vanagon::Utilities.local_command("#{@docker_cmd} exec #{build_host_name}-builder /bin/sh -c '#{command}'",
|
92
|
+
return_command_output: return_output)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Copy files between a container and the host
|
96
|
+
def docker_cp(source, target)
|
97
|
+
Vanagon::Utilities.ex("#{@docker_cmd} cp '#{source}' '#{target}'")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Copy files matching a glob pattern from the host to the container
|
101
|
+
def docker_cp_globs_to(globs, container_path)
|
102
|
+
Array(globs).each do |glob|
|
103
|
+
Dir.glob(glob).each do |path|
|
104
|
+
docker_cp(path, "#{build_host_name}-builder:#{container_path}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Copy files matching a glob pattern from the container to the host
|
110
|
+
#
|
111
|
+
# @note Globs are expanded by running `/bin/sh` in the container, which
|
112
|
+
# may not support the same variety of expressions as Ruby's `Dir.glob`.
|
113
|
+
# For example, `**` may not work.
|
114
|
+
def docker_cp_globs_from(globs, host_path)
|
115
|
+
Array(globs).each do |glob|
|
116
|
+
# Match the behavior of `rsync -r` when both paths are directories
|
117
|
+
# by copying the contents of the directory instead of the directory.
|
118
|
+
glob += '*' if glob.end_with?('/') && host_path.end_with?('/')
|
119
|
+
|
120
|
+
# TODO: This doesn't handle "interesting" paths. E.g. paths with
|
121
|
+
# spaces or other special non-glob characters. This could be
|
122
|
+
# fixed with a variant of `Shellwords.shellescape` that allows
|
123
|
+
# glob characters to pass through.
|
124
|
+
paths = docker_exec(%(for file in #{glob};do [ -e "$file" ] && printf '%s\\0' "${file}";done), true).split("\0")
|
125
|
+
|
126
|
+
paths.each do |path|
|
127
|
+
docker_cp("#{build_host_name}-builder:#{path}", host_path)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Wait for ssh to come up in the container
|
133
|
+
#
|
134
|
+
# Retry 5 times with a 1 second sleep between errors to account for
|
135
|
+
# network resets while SSHD is starting. Allow a maximum of 5 seconds for
|
136
|
+
# SSHD to start.
|
137
|
+
#
|
138
|
+
# @raise [Vanagon::Error] if a SSH connection cannot be established.
|
139
|
+
# @return [void]
|
140
|
+
def wait_for_ssh
|
141
|
+
Vanagon::Utilities.retry_with_timeout(5, 5) do
|
142
|
+
begin
|
143
|
+
Vanagon::Utilities.remote_ssh_command("#{@target_user}@#{@target}", 'exit', @platform.ssh_port)
|
144
|
+
rescue StandardError => e
|
145
|
+
sleep(1) # Give SSHD some time to start.
|
146
|
+
raise e
|
147
|
+
end
|
148
|
+
end
|
149
|
+
rescue StandardError => e
|
150
|
+
raise Vanagon::Error.wrap(e, "SSH was not up in the container after 5 seconds.")
|
151
|
+
end
|
65
152
|
end
|
66
153
|
end
|
67
154
|
end
|