vanagon 0.3.18

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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +175 -0
  4. data/bin/build +33 -0
  5. data/bin/devkit +22 -0
  6. data/bin/repo +26 -0
  7. data/bin/ship +15 -0
  8. data/lib/vanagon.rb +8 -0
  9. data/lib/vanagon/common.rb +2 -0
  10. data/lib/vanagon/common/pathname.rb +87 -0
  11. data/lib/vanagon/common/user.rb +25 -0
  12. data/lib/vanagon/component.rb +157 -0
  13. data/lib/vanagon/component/dsl.rb +307 -0
  14. data/lib/vanagon/component/source.rb +66 -0
  15. data/lib/vanagon/component/source/git.rb +60 -0
  16. data/lib/vanagon/component/source/http.rb +158 -0
  17. data/lib/vanagon/driver.rb +112 -0
  18. data/lib/vanagon/engine/base.rb +82 -0
  19. data/lib/vanagon/engine/docker.rb +40 -0
  20. data/lib/vanagon/engine/local.rb +40 -0
  21. data/lib/vanagon/engine/pooler.rb +85 -0
  22. data/lib/vanagon/errors.rb +28 -0
  23. data/lib/vanagon/extensions/string.rb +11 -0
  24. data/lib/vanagon/optparse.rb +62 -0
  25. data/lib/vanagon/platform.rb +245 -0
  26. data/lib/vanagon/platform/deb.rb +71 -0
  27. data/lib/vanagon/platform/dsl.rb +293 -0
  28. data/lib/vanagon/platform/osx.rb +100 -0
  29. data/lib/vanagon/platform/rpm.rb +76 -0
  30. data/lib/vanagon/platform/rpm/wrl.rb +39 -0
  31. data/lib/vanagon/platform/solaris_10.rb +182 -0
  32. data/lib/vanagon/platform/solaris_11.rb +138 -0
  33. data/lib/vanagon/platform/swix.rb +35 -0
  34. data/lib/vanagon/project.rb +251 -0
  35. data/lib/vanagon/project/dsl.rb +218 -0
  36. data/lib/vanagon/utilities.rb +299 -0
  37. data/spec/fixures/component/invalid-test-fixture.json +3 -0
  38. data/spec/fixures/component/mcollective.service +1 -0
  39. data/spec/fixures/component/test-fixture.json +4 -0
  40. data/spec/lib/vanagon/common/pathname_spec.rb +103 -0
  41. data/spec/lib/vanagon/common/user_spec.rb +36 -0
  42. data/spec/lib/vanagon/component/dsl_spec.rb +443 -0
  43. data/spec/lib/vanagon/component/source/git_spec.rb +19 -0
  44. data/spec/lib/vanagon/component/source/http_spec.rb +43 -0
  45. data/spec/lib/vanagon/component/source_spec.rb +99 -0
  46. data/spec/lib/vanagon/component_spec.rb +22 -0
  47. data/spec/lib/vanagon/engine/base_spec.rb +40 -0
  48. data/spec/lib/vanagon/engine/docker_spec.rb +40 -0
  49. data/spec/lib/vanagon/engine/pooler_spec.rb +54 -0
  50. data/spec/lib/vanagon/platform/deb_spec.rb +60 -0
  51. data/spec/lib/vanagon/platform/dsl_spec.rb +128 -0
  52. data/spec/lib/vanagon/platform/rpm_spec.rb +41 -0
  53. data/spec/lib/vanagon/platform/solaris_11_spec.rb +44 -0
  54. data/spec/lib/vanagon/platform_spec.rb +53 -0
  55. data/spec/lib/vanagon/project/dsl_spec.rb +203 -0
  56. data/spec/lib/vanagon/project_spec.rb +44 -0
  57. data/spec/lib/vanagon/utilities_spec.rb +140 -0
  58. data/templates/Makefile.erb +116 -0
  59. data/templates/deb/changelog.erb +5 -0
  60. data/templates/deb/conffiles.erb +3 -0
  61. data/templates/deb/control.erb +21 -0
  62. data/templates/deb/dirs.erb +3 -0
  63. data/templates/deb/docs.erb +1 -0
  64. data/templates/deb/install.erb +3 -0
  65. data/templates/deb/postinst.erb +46 -0
  66. data/templates/deb/postrm.erb +15 -0
  67. data/templates/deb/prerm.erb +17 -0
  68. data/templates/deb/rules.erb +25 -0
  69. data/templates/osx/postinstall.erb +24 -0
  70. data/templates/osx/preinstall.erb +19 -0
  71. data/templates/osx/project-installer.xml.erb +19 -0
  72. data/templates/rpm/project.spec.erb +217 -0
  73. data/templates/solaris/10/depend.erb +3 -0
  74. data/templates/solaris/10/pkginfo.erb +13 -0
  75. data/templates/solaris/10/postinstall.erb +37 -0
  76. data/templates/solaris/10/preinstall.erb +7 -0
  77. data/templates/solaris/10/preremove.erb +6 -0
  78. data/templates/solaris/10/proto.erb +5 -0
  79. data/templates/solaris/11/p5m.erb +73 -0
  80. metadata +172 -0
@@ -0,0 +1,112 @@
1
+ require 'vanagon/project'
2
+ require 'vanagon/platform'
3
+ require 'vanagon/component'
4
+ require 'vanagon/utilities'
5
+ require 'vanagon/common'
6
+ require 'vanagon/errors'
7
+ require 'tmpdir'
8
+ require 'logger'
9
+
10
+ class Vanagon
11
+ class Driver
12
+ include Vanagon::Utilities
13
+ attr_accessor :platform, :project, :target, :workdir, :verbose, :preserve
14
+
15
+ def initialize(platform, project, options = { :configdir => nil, :target => nil, :engine => nil, :components => nil })
16
+ @verbose = false
17
+ @preserve = false
18
+
19
+ @@configdir = options[:configdir] || File.join(Dir.pwd, "configs")
20
+ components = options[:components] || []
21
+ target = options[:target]
22
+ engine = options[:engine] || 'pooler'
23
+
24
+ @platform = Vanagon::Platform.load_platform(platform, File.join(@@configdir, "platforms"))
25
+ @project = Vanagon::Project.load_project(project, File.join(@@configdir, "projects"), @platform, components)
26
+
27
+ # If a target has been given, we don't want to make any assumptions about how to tear it down.
28
+ engine = 'base' if target
29
+ require "vanagon/engine/#{engine}"
30
+ @engine = Object.const_get("Vanagon::Engine::#{engine.capitalize}").new(@platform, target)
31
+
32
+ @@logger = Logger.new('vanagon_hosts.log')
33
+ @@logger.progname = 'vanagon'
34
+ rescue LoadError => e
35
+ raise Vanagon::Error.wrap(e, "Could not load the desired engine '#{@engine_name}'.")
36
+ end
37
+
38
+ def cleanup_workdir
39
+ FileUtils.rm_rf(@workdir)
40
+ end
41
+
42
+ def self.configdir
43
+ @@configdir
44
+ end
45
+
46
+ def self.logger
47
+ @@logger
48
+ end
49
+
50
+ # Returns the set difference between the build_requires and the components to get a list of external dependencies that need to be installed.
51
+ def list_build_dependencies
52
+ @project.components.map(&:build_requires).flatten.uniq - @project.components.map(&:name)
53
+ end
54
+
55
+ def install_build_dependencies
56
+ unless list_build_dependencies.empty?
57
+ if @platform.build_dependencies && @platform.build_dependencies.command && !@platform.build_dependencies.command.empty?
58
+ @engine.dispatch("#{@platform.build_dependencies.command} #{list_build_dependencies.join(' ')} #{@platform.build_dependencies.suffix}")
59
+ elsif @platform.respond_to?(:install_build_dependencies)
60
+ @engine.dispatch(@platform.install_build_dependencies(list_build_dependencies))
61
+ else
62
+ raise Vanagon::Error, "No method defined to install build dependencies for #{@platform.name}"
63
+ end
64
+ end
65
+ end
66
+
67
+ def run
68
+ # Simple sanity check for the project
69
+ if @project.version.nil? or @project.version.empty?
70
+ raise Vanagon::Error, "Project requires a version set, all is lost."
71
+ end
72
+ @workdir = Dir.mktmpdir
73
+ @engine.startup(@workdir)
74
+
75
+ puts "Target is #{@engine.target}"
76
+
77
+ install_build_dependencies
78
+ @project.fetch_sources(@workdir)
79
+ @project.make_makefile(@workdir)
80
+ @project.make_bill_of_materials(@workdir)
81
+ @project.generate_packaging_artifacts(@workdir)
82
+ @engine.ship_workdir(@workdir)
83
+ @engine.dispatch("(cd #{@engine.remote_workdir}; #{@platform.make})")
84
+ @engine.retrieve_built_artifact
85
+ @engine.teardown unless @preserve
86
+ cleanup_workdir unless @preserve
87
+ rescue => e
88
+ puts e
89
+ puts e.backtrace.join("\n")
90
+ raise e
91
+ end
92
+
93
+ def prepare(workdir = nil)
94
+ @workdir = workdir ? FileUtils.mkdir_p(workdir).first : Dir.mktmpdir
95
+ @engine.startup(@workdir)
96
+
97
+ puts "Devkit on #{@engine.target}"
98
+
99
+ install_build_dependencies
100
+ @project.fetch_sources(@workdir)
101
+ @project.make_makefile(@workdir)
102
+ @project.make_bill_of_materials(@workdir)
103
+ # Builds only the project, skipping packaging into an artifact.
104
+ @engine.ship_workdir(@workdir)
105
+ @engine.dispatch("(cd #{@engine.remote_workdir}; #{@platform.make} #{@project.name}-project)")
106
+ rescue => e
107
+ puts e
108
+ puts e.backtrace.join("\n")
109
+ raise e
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,82 @@
1
+ require 'vanagon/utilities'
2
+ require 'vanagon/errors'
3
+
4
+ class Vanagon
5
+ class Engine
6
+ class Base
7
+ attr_accessor :target, :remote_workdir
8
+
9
+ def initialize(platform, target = nil)
10
+ @platform = platform
11
+ @required_attributes = ["ssh_port"]
12
+ @target = target if target
13
+ @target_user = "root"
14
+ end
15
+
16
+ # This method is used to obtain a vm to build upon
17
+ # For the base class we just return the target that was passed in
18
+ def select_target
19
+ @target or raise Vanagon::Error, '#select_target has not been implemented for your engine.'
20
+ end
21
+
22
+ # Dispatches the command for execution
23
+ def dispatch(command, return_output = false)
24
+ Vanagon::Utilities.remote_ssh_command("#{@target_user}@#{@target}", command, @platform.ssh_port, return_command_output: return_output)
25
+ end
26
+
27
+ # Steps needed to tear down or clean up the system after the build is
28
+ # complete
29
+ def teardown
30
+ end
31
+
32
+ # Applies the steps needed to extend the system to build packages against
33
+ # the target system
34
+ def setup
35
+ unless @platform.provisioning.empty?
36
+ script = @platform.provisioning.join(' && ')
37
+ dispatch(script)
38
+ end
39
+ end
40
+
41
+ # This method will take care of validation and target selection all at
42
+ # once as an easy shorthand to call from the driver
43
+ def startup(workdir)
44
+ validate_platform
45
+ select_target
46
+ setup
47
+ get_remote_workdir
48
+ end
49
+
50
+ def get_remote_workdir
51
+ @remote_workdir ||= dispatch("mktemp -d -p /var/tmp 2>/dev/null || mktemp -d -t 'tmp'", true)
52
+ end
53
+
54
+ def ship_workdir(workdir)
55
+ Vanagon::Utilities.rsync_to("#{workdir}/*", "#{@target_user}@#{@target}", @remote_workdir, @platform.ssh_port)
56
+ end
57
+
58
+ def retrieve_built_artifact
59
+ FileUtils.mkdir_p("output")
60
+ Vanagon::Utilities.rsync_from("#{@remote_workdir}/output/*", "#{@target_user}@#{@target}", "output", @platform.ssh_port)
61
+ end
62
+
63
+ # Ensures that the platform defines the attributes that the engine needs to function.
64
+ #
65
+ # @raise [Vanagon::Error] an error is raised if a needed attribute is not defined
66
+ def validate_platform
67
+ missing_attrs = []
68
+ @required_attributes.each do |attr|
69
+ if (!@platform.instance_variables.include?("@#{attr}".to_sym)) or @platform.instance_variable_get("@#{attr}".to_sym).nil?
70
+ missing_attrs << attr
71
+ end
72
+ end
73
+
74
+ if missing_attrs.empty?
75
+ return true
76
+ else
77
+ raise Vanagon::Error, "The following required attributes were not set in '#{@platform.name}': #{missing_attrs.join(', ')}."
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,40 @@
1
+ require 'vanagon/engine/base'
2
+
3
+ class Vanagon
4
+ class Engine
5
+ class Docker < Base
6
+ # Both the docker_image and the docker command itself are required for
7
+ # the docker engine to work
8
+ def initialize(platform, target = nil)
9
+ @docker_cmd = Vanagon::Utilities.find_program_on_path('docker')
10
+ super
11
+ @required_attributes << "docker_image"
12
+ end
13
+
14
+ # This method is used to obtain a vm to build upon using
15
+ # a docker container.
16
+ # @raise [Vanagon::Error] if a target cannot be obtained
17
+ def select_target
18
+ Vanagon::Utilities.ex("#{@docker_cmd} run -d --name #{@platform.docker_image}-builder -p #{@platform.ssh_port}:22 #{@platform.docker_image}")
19
+ @target = 'localhost'
20
+
21
+ # Wait for ssh to come up in the container
22
+ Vanagon::Utilities.retry_with_timeout do
23
+ Vanagon::Utilities.remote_ssh_command("#{@target_user}@#{@target}", 'exit', @platform.ssh_port)
24
+ end
25
+ rescue => e
26
+ 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.")
27
+ end
28
+
29
+ # This method is used to tell the vmpooler to delete the instance of the
30
+ # vm that was being used so the pool can be replenished.
31
+ def teardown
32
+ Vanagon::Utilities.ex("#{@docker_cmd} stop #{@platform.docker_image}-builder")
33
+ Vanagon::Utilities.ex("#{@docker_cmd} rm #{@platform.docker_image}-builder")
34
+ rescue Vanagon::Error => e
35
+ warn "There was a problem tearing down the docker container #{@platform.docker_image}-builder (#{e.message})."
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,40 @@
1
+ require 'vanagon/utilities'
2
+ require 'vanagon/errors'
3
+ require 'benchmark'
4
+
5
+ class Vanagon
6
+ class Engine
7
+ class Local
8
+ attr_accessor :target
9
+
10
+ def initialize(platform, target = nil)
11
+ @platform = platform
12
+ @target = "local machine"
13
+ end
14
+
15
+ # Dispatches the command for execution
16
+ def dispatch(command)
17
+ puts Benchmark.measure { local_command(command, @workdir) }
18
+ end
19
+
20
+ # Steps needed to tear down or clean up the system after the build is
21
+ # complete
22
+ def teardown
23
+ end
24
+
25
+ # This method will take care of validation and target selection all at
26
+ # once as an easy shorthand to call from the driver
27
+ def startup(workdir)
28
+ @workdir = workdir
29
+ script = @platform.provisioning.join(' && ')
30
+ dispatch(script)
31
+ end
32
+
33
+ def ship_workdir(workdir)
34
+ end
35
+
36
+ def retrieve_built_artifact
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,85 @@
1
+ require 'vanagon/engine/base'
2
+
3
+ class Vanagon
4
+ class Engine
5
+ class Pooler < Base
6
+ attr_reader :token
7
+
8
+ # The vmpooler_template is required to use the pooler engine
9
+ def initialize(platform, target = nil)
10
+ @pooler = "http://vmpooler.delivery.puppetlabs.net"
11
+ @token = load_token
12
+ super
13
+ @required_attributes << "vmpooler_template"
14
+ end
15
+
16
+ # This method loads the pooler token from one of two locations
17
+ # @return [String, nil] token for use with the vmpooler
18
+ def load_token
19
+ if ENV['VMPOOLER_TOKEN']
20
+ token = ENV['VMPOOLER_TOKEN']
21
+ else
22
+ token_file = File.expand_path("~/.vanagon-token")
23
+ if File.exist?(token_file)
24
+ token = File.open(token_file).read.chomp
25
+ end
26
+ end
27
+ token
28
+ end
29
+
30
+ # This method is used to obtain a vm to build upon using the Puppet Labs'
31
+ # vmpooler (https://github.com/puppetlabs/vmpooler)
32
+ # @raise [Vanagon::Error] if a target cannot be obtained
33
+ def select_target
34
+ response = Vanagon::Utilities.http_request(
35
+ "#{@pooler}/vm",
36
+ 'POST',
37
+ '{"' + @platform.vmpooler_template + '":"1"}',
38
+ { 'X-AUTH-TOKEN' => @token }
39
+ )
40
+ if response and response["ok"]
41
+ @target = response[@platform.vmpooler_template]['hostname'] + '.' + response['domain']
42
+ Vanagon::Driver.logger.info "Reserving #{@target} (#{@platform.vmpooler_template}) [#{@token ? 'token used' : 'no token used'}]"
43
+
44
+ tags = {
45
+ 'tags' => {
46
+ 'jenkins_build_url' => ENV['BUILD_URL'],
47
+ 'project' => ENV['JOB_NAME'] || 'vanagon',
48
+ 'created_by' => ENV['USER'] || ENV['USERNAME'] || 'unknown'
49
+ }
50
+ }
51
+
52
+ response_tag = Vanagon::Utilities.http_request(
53
+ "#{@pooler}/vm/#{response[@platform.vmpooler_template]['hostname']}",
54
+ 'PUT',
55
+ tags.to_json,
56
+ { 'X-AUTH-TOKEN' => @token }
57
+ )
58
+ else
59
+ raise Vanagon::Error, "Something went wrong getting a target vm to build on, maybe the pool for #{@platform.vmpooler_template} is empty?"
60
+ end
61
+ end
62
+
63
+ # This method is used to tell the vmpooler to delete the instance of the
64
+ # vm that was being used so the pool can be replenished.
65
+ def teardown
66
+ response = Vanagon::Utilities.http_request(
67
+ "#{@pooler}/vm/#{@target}",
68
+ "DELETE",
69
+ nil,
70
+ { 'X-AUTH-TOKEN' => @token }
71
+ )
72
+ if response and response["ok"]
73
+ Vanagon::Driver.logger.info "#{@target} has been destroyed"
74
+ puts "#{@target} has been destroyed"
75
+ else
76
+ Vanagon::Driver.logger.info "#{@target} could not be destroyed"
77
+ warn "#{@target} could not be destroyed"
78
+ end
79
+ rescue Vanagon::Error => e
80
+ Vanagon::Driver.logger.info "#{@target} could not be destroyed (#{e.message})"
81
+ warn "#{@target} could not be destroyed (#{e.message})"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,28 @@
1
+ class Vanagon
2
+
3
+ # An error class that accepts a wrapped error message
4
+ #
5
+ class Error < StandardError
6
+ attr_accessor :original
7
+
8
+ # Generate a wrapped exception
9
+ #
10
+ # @param original [Exception] The exception to wrap
11
+ # @param mesg [String]
12
+ #
13
+ # @return [Vanagon::Error]
14
+ def self.wrap(original, mesg)
15
+ new(mesg).tap do |e|
16
+ e.set_backtrace(caller(4))
17
+ e.original = original
18
+ end
19
+ end
20
+
21
+ # @overload initialize(mesg)
22
+ # @param mesg [String] The exception mesg
23
+ #
24
+ def initialize(mesg)
25
+ super(mesg)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ # String is generally pretty functional, but sometimes you need
2
+ # a couple of small convienence methods to make working with really
3
+ # long or really funky strings easier. This will indent all lines
4
+ # to the margin of the first line, preserving sunsequent indentation
5
+ # while still helping reign in HEREDOCS.
6
+ class String
7
+ # @return [String]
8
+ def undent
9
+ gsub(/^.{#{slice(/^ +/).length}}/, '')
10
+ end
11
+ end
@@ -0,0 +1,62 @@
1
+ require 'optparse'
2
+
3
+ class Vanagon
4
+ class OptParse
5
+ def initialize(banner, options = [])
6
+ @options = Hash[options.map { |v| [v, nil] }]
7
+ @optparse = OptionParser.new do |opts|
8
+ opts.banner = banner
9
+
10
+ if @options.include?(:workdir)
11
+ opts.on('-w DIR', '--workdir DIR', "Working directory where build source should be put (defaults to a tmpdir)") do |dir|
12
+ @options[:workdir] = dir
13
+ end
14
+ end
15
+
16
+ if @options.include?(:configdir)
17
+ opts.on('-c', '--configdir DIR', 'Configs dir (defaults to $pwd/configs)') do |dir|
18
+ @options[:configdir] = dir
19
+ end
20
+ end
21
+
22
+ if @options.include?(:target)
23
+ opts.on('-t HOST', '--target HOST', 'Configure a target machine for build and packaging (defaults to grabbing one from the pooler)') do |name|
24
+ @options[:target] = name
25
+ end
26
+ end
27
+
28
+ if @options.include?(:engine)
29
+ opts.on('-e ENGINE', '--engine ENGINE', "A custom engine to use (defaults to the pooler) [base, local, docker, pooler currently supported]") do |engine|
30
+ @options[:engine] = engine
31
+ end
32
+ end
33
+
34
+ if @options.include?(:preserve)
35
+ opts.on('-p', '--preserve', 'Whether to tear down the VM on success or not (defaults to false)') do |flag|
36
+ @options[:preserve] = flag
37
+ end
38
+ end
39
+
40
+ if @options.include?(:verbose)
41
+ opts.on('-v', '--verbose', 'Verbose output (does nothing)') do |flag|
42
+ @options[:verbose] = flag
43
+ end
44
+ end
45
+
46
+ opts.on('-h', '--help', 'Display help') do
47
+ puts opts
48
+ exit 1
49
+ end
50
+ end
51
+ end
52
+
53
+ def parse!(args)
54
+ @optparse.parse!(args)
55
+ @options
56
+ end
57
+
58
+ def to_s
59
+ @optparse.to_s
60
+ end
61
+ end
62
+ end