vanagon 0.15.38 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +10 -6
- data/lib/vanagon/engine/docker.rb +101 -14
- data/lib/vanagon/platform.rb +3 -0
- data/lib/vanagon/platform/dsl.rb +11 -0
- data/spec/lib/vanagon/cli_spec.rb +80 -0
- data/spec/lib/vanagon/engine/docker_spec.rb +74 -16
- metadata +54 -31
- 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 self.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)
|
@@ -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
|
data/lib/vanagon/platform.rb
CHANGED
@@ -115,6 +115,7 @@ class Vanagon
|
|
115
115
|
# Docker engine specific
|
116
116
|
attr_accessor :docker_image
|
117
117
|
attr_accessor :docker_run_args
|
118
|
+
attr_accessor :use_docker_exec
|
118
119
|
|
119
120
|
# AWS engine specific
|
120
121
|
attr_accessor :aws_ami
|
@@ -238,6 +239,8 @@ class Vanagon
|
|
238
239
|
@copy ||= "cp"
|
239
240
|
@shasum ||= "sha1sum"
|
240
241
|
|
242
|
+
@use_docker_exec = false
|
243
|
+
|
241
244
|
# Our first attempt at defining metadata about a platform
|
242
245
|
@cross_compiled ||= false
|
243
246
|
@valid_operators ||= ['<', '>', '<=', '>=', '=']
|