vanagon 0.15.38 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- load_engine(engine, @platform, target)
44
- rescue LoadError => e
45
- raise Vanagon::Error.wrap(e, "Could not load the desired engine '#{engine}'")
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
- fail "No such engine '#{camelize(engine_type)}'"
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 # rubocop:disable Metrics/AbcSize
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 -p #{@platform.ssh_port}:22 #{extra_args.join(' ')} #{@platform.docker_image}")
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
- # Wait for ssh to come up in the container. Retry 5 times with a 1
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 docker. Ssh was not up in the container after 5 seconds.")
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
@@ -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 ||= ['<', '>', '<=', '>=', '=']