vanagon 0.3.18

Sign up to get free protection for your applications and to get access to all the features.
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