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.
@@ -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
@@ -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)
@@ -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 # 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