oscar 0.2.0alpha1

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 (42) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGELOG +12 -0
  3. data/Gemfile +1 -0
  4. data/LICENSE +14 -0
  5. data/README.markdown +97 -0
  6. data/Vagrantfile +6 -0
  7. data/bootstrap/base/pre/set_hostname.sh +3 -0
  8. data/bootstrap/base/provision/install_puppet_enterprise.sh +6 -0
  9. data/bootstrap/master/post/relocate_puppet.sh +23 -0
  10. data/config/.gitignore +1 -0
  11. data/config/roles.yaml.dist +17 -0
  12. data/doc/answers/README.markdown +1 -0
  13. data/doc/answers/agent.txt +10 -0
  14. data/doc/answers/master-1.1.txt +22 -0
  15. data/doc/answers/master-2.0.0.txt +42 -0
  16. data/doc/answers/master-2.5.0.txt +36 -0
  17. data/doc/answers/master-db.txt +52 -0
  18. data/lib/oscar.rb +17 -0
  19. data/lib/oscar/config.rb +97 -0
  20. data/lib/oscar/environment.rb +28 -0
  21. data/lib/oscar/networking.rb +47 -0
  22. data/lib/oscar/node.rb +91 -0
  23. data/lib/oscar/schema.yaml +73 -0
  24. data/lib/oscar/version.rb +3 -0
  25. data/lib/pe_build.rb +13 -0
  26. data/lib/pe_build/action.rb +20 -0
  27. data/lib/pe_build/action/download.rb +56 -0
  28. data/lib/pe_build/action/unpackage.rb +70 -0
  29. data/lib/pe_build/command.rb +31 -0
  30. data/lib/pe_build/command/download.rb +8 -0
  31. data/lib/pe_build/command/list.rb +10 -0
  32. data/lib/pe_build/config.rb +30 -0
  33. data/lib/pe_build/provisioners.rb +9 -0
  34. data/lib/pe_build/provisioners/puppet_enterprise.rb +1 -0
  35. data/lib/pe_build/provisioners/puppet_enterprise_bootstrap.rb +138 -0
  36. data/lib/pe_build/version.rb +5 -0
  37. data/manifests/.gitignore +3 -0
  38. data/modules/.gitignore +2 -0
  39. data/oscar.gemspec +27 -0
  40. data/templates/answers/agent.txt.erb +10 -0
  41. data/templates/answers/master.txt.erb +52 -0
  42. metadata +175 -0
@@ -0,0 +1,28 @@
1
+ require 'oscar'
2
+
3
+ class Oscar::Environment
4
+ attr_reader :networking
5
+ attr_reader :config
6
+
7
+ def initialize
8
+ @config = Oscar::Config.new
9
+ @nodes = []
10
+ end
11
+
12
+ def run!
13
+ @networking = Oscar::Networking.new(@config.data["networking"])
14
+ nodes = @config.all_node_configs
15
+
16
+ # TODO make sure that the master is provisioned before any agents.
17
+ nodes.each do |node_attrs|
18
+ node = Oscar::Node.new(node_attrs)
19
+ @networking.register(node)
20
+
21
+ @nodes << node
22
+ end
23
+
24
+ Vagrant::Config.run do |config|
25
+ @nodes.each { |node| node.define(config) }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ require 'oscar'
2
+ require 'ipaddress'
3
+
4
+ class Oscar::Networking
5
+
6
+ def initialize(yaml_config)
7
+
8
+ range = yaml_config["pool"]
9
+ @domainname = yaml_config["domainname"]
10
+ @network = IPAddress.parse(range)
11
+
12
+ @pool = []
13
+ @nodes = {}
14
+
15
+ @network.each_host { |h| @pool << h }
16
+ @iterator = @pool.each
17
+
18
+ end
19
+
20
+ def register(node)
21
+ next_addr = @iterator.next.to_s
22
+
23
+ node.networking = self
24
+ node.address = next_addr
25
+
26
+ @nodes[next_addr] = node
27
+ end
28
+
29
+ def hosts_for(node)
30
+ arr = []
31
+
32
+ arr << ['127.0.0.1', ['localhost']]
33
+ arr << ['127.0.1.1', [node.name, "#{node.name}.#{@domainname}"]]
34
+
35
+ arr << ['::1', %w{ip6-localhost ip6-loopback}]
36
+
37
+ arr << ['fe00::0', ['ip6-localnet']]
38
+ arr << ['ff00::0', ['ip6-mcastprefix']]
39
+ arr << ['ff02::1', ['ip6-allnodes']]
40
+ arr << ['ff02::2', ['ip6-allrouters']]
41
+
42
+ @nodes.each_pair { |address, n| arr << [address, [n.name, "#{n.name}.#{@domainname}"]] }
43
+
44
+
45
+ arr
46
+ end
47
+ end
data/lib/oscar/node.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'oscar'
2
+ require 'vagrant'
3
+
4
+ require 'vagrant-hosts'
5
+
6
+ class Oscar::Node
7
+
8
+ class << self
9
+ def addrole(name, &block)
10
+ @roles ||= {}
11
+ @roles[name] = block
12
+ end
13
+
14
+ def getrole(name)
15
+ @roles[name] if @roles
16
+ end
17
+ end
18
+
19
+ addrole(:base) do |node|
20
+ node.vm.box = @boxname
21
+
22
+ if @forwards
23
+ @forwards.each { |h| node.vm.forward_port h["source"], h["dest"] }
24
+ end
25
+
26
+ node.vm.network :hostonly, @address
27
+ # Add in optional per-node configuration
28
+ node.vm.box_url = @boxurl if @boxurl
29
+ node.vm.boot_mode = @gui if @gui
30
+
31
+ host_entries = @networking.hosts_for(self)
32
+ #node.vm.provision :shell do |shell|
33
+ # shell.inline = %{grep "#{entry}" /etc/hosts || echo "#{entry}" >> /etc/hosts}
34
+ #end
35
+
36
+ node.vm.provision :hosts do |h|
37
+ host_entries.each { |(address, aliases)| h.add_host address, aliases }
38
+ end
39
+
40
+ node.vm.provision :puppet_enterprise_bootstrap do |pe|
41
+ pe.role = @role if @role
42
+ end
43
+ end
44
+
45
+ addrole(:master) do |node|
46
+ # Manifests and modules need to be mounted on the master via shared folders,
47
+ # but the default /vagrant mount has permissions and ownership that conflicts
48
+ # with the master and pe-puppet. We mount these folders separately with
49
+ # loosened permissions.
50
+ node.vm.share_folder 'manifests', '/manifests', './manifests', :extra => 'fmode=644,dmode=755,fmask=022,dmask=022'
51
+ node.vm.share_folder 'modules', '/modules', './modules', :extra => 'fmode=644,dmode=755,fmask=022,dmask=022'
52
+
53
+ # Boost RAM for the master so that activemq doesn't asplode
54
+ node.vm.customize([ "modifyvm", :id, "--memory", "1024" ])
55
+ end
56
+
57
+ attr_accessor :address
58
+ attr_writer :networking # Callback attribute for retrieving hosts
59
+
60
+ attr_reader :name, :boxname, :boxurl, :role
61
+ attr_reader :forwards # really?
62
+
63
+ def initialize(yaml_attrs)
64
+ @attrs = yaml_attrs
65
+
66
+ @name = @attrs["name"]
67
+ @address = @attrs["address"]
68
+ @role = @attrs["role"].intern if @attrs["role"]
69
+
70
+ @boxurl = @attrs["boxurl"]
71
+ @boxname = @attrs["boxname"]
72
+ @forwards = @attrs["forwards"]
73
+ @gui = @attrs["gui"]
74
+ end
75
+
76
+ def define(config)
77
+
78
+ blk = lambda do |config|
79
+ config.vm.define @name do |node|
80
+
81
+ instance_exec(node, &(self.class.getrole(:base)))
82
+
83
+ if (blk = self.class.getrole(@role))
84
+ instance_exec(node, &blk)
85
+ end
86
+ end
87
+ end
88
+
89
+ blk.call(config)
90
+ end
91
+ end
@@ -0,0 +1,73 @@
1
+ ---
2
+ # Oscar kwalify schema
3
+
4
+ type: map
5
+ mapping:
6
+
7
+ "nodes":
8
+ type: seq
9
+ sequence:
10
+ -
11
+ type: map
12
+ mapping:
13
+ "name": { required: true }
14
+ "address": { required: false }
15
+
16
+ "boxname": &boxname
17
+ type: str
18
+ "boxurl": &boxurl
19
+ type: str
20
+ "role":
21
+ desc: "A node can have a role, that adds specific configuration for how it is going to be used."
22
+ type: str
23
+ enum: ["master", "console", "agent"]
24
+ default: "agent"
25
+
26
+ "profile":
27
+ desc: "A node can have a profile, which describes where the box came from."
28
+ type: str
29
+ "forwards": &forwards
30
+ type: seq
31
+ sequence:
32
+ -
33
+ type: map
34
+ mapping:
35
+ "source":
36
+ type: int
37
+ required: true
38
+ "dest":
39
+ type: int
40
+ required: true
41
+
42
+ "profiles":
43
+ desc: "A profile describes a box type, so what name it is and where to retrieve it from."
44
+ type: seq
45
+ sequence:
46
+ -
47
+ type: map
48
+ mapping:
49
+ "name":
50
+ required: true
51
+ "boxname": *boxname
52
+ "boxurl": *boxurl
53
+
54
+
55
+ "roles":
56
+ desc: "A role is the configuration needed for a node to do a specific role."
57
+ type: seq
58
+ sequence:
59
+ -
60
+ type: map
61
+ mapping:
62
+ name: { required: true }
63
+ "forwards": *forwards
64
+
65
+ # Network configuration for oscar, not applicable to any node.
66
+ "networking":
67
+ type: map
68
+ mapping:
69
+ "pool":
70
+ required: true
71
+ "domainname":
72
+ required: true
73
+
@@ -0,0 +1,3 @@
1
+ module Oscar
2
+ VERSION = '0.2.0alpha1'
3
+ end
data/lib/pe_build.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'vagrant'
2
+
3
+ module PEBuild
4
+ def self.archive_directory
5
+ File.expand_path(File.join(ENV['HOME'], '.vagrant.d', 'pe_builds'))
6
+ end
7
+ end
8
+
9
+ require 'pe_build/version'
10
+ require 'pe_build/action'
11
+ require 'pe_build/config'
12
+ require 'pe_build/command'
13
+ require 'pe_build/provisioners'
@@ -0,0 +1,20 @@
1
+ require 'vagrant'
2
+ require 'vagrant/action/builder'
3
+ require 'pe_build'
4
+
5
+ module PEBuild::Action
6
+ end
7
+
8
+ require 'pe_build/action/download'
9
+ require 'pe_build/action/unpackage'
10
+
11
+ builder = Vagrant::Action::Builder.new do
12
+ use PEBuild::Action::Download
13
+ end
14
+
15
+ Vagrant.actions.register :download_pe_build, builder
16
+
17
+ Vagrant.actions.register(:prep_build, Vagrant::Action::Builder.new do
18
+ use PEBuild::Action::Download
19
+ use PEBuild::Action::Unpackage
20
+ end)
@@ -0,0 +1,56 @@
1
+ require 'pe_build'
2
+ require 'pe_build/action'
3
+ require 'vagrant'
4
+ require 'fileutils'
5
+
6
+ class PEBuild::Action::Download
7
+ # Downloads a PE build to a temp directory
8
+
9
+ def initialize(app, env)
10
+ @app, @env = app, env
11
+ load_variables
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+ perform_download
17
+ @app.call(@env)
18
+ end
19
+
20
+ private
21
+
22
+ # Determine system state and download a PE build accordingly.
23
+ #
24
+ # If we are applying actions within the context of a single box, then we
25
+ # should try to prefer and box level configuration options first. If
26
+ # anything is unset then we should fall back to the global settings.
27
+ def load_variables
28
+ if @env[:box_name]
29
+ @root = @env[:vm].pe_build.download_root
30
+ @version = @env[:vm].pe_build.version
31
+ @filename = @env[:vm].pe_build.version
32
+ end
33
+
34
+ @root ||= @env[:global_config].pe_build.download_root
35
+ @version ||= @env[:global_config].pe_build.version
36
+ @filename ||= @env[:global_config].pe_build.filename
37
+
38
+ @archive_path = File.join(PEBuild.archive_directory, @filename)
39
+ end
40
+
41
+ # @return [String] The full URL to download, based on the config
42
+ def url
43
+ [@root, @version, @filename].join('/')
44
+ end
45
+
46
+ def perform_download
47
+ if File.exist? @archive_path
48
+ @env[:ui].info "#{@filename} cached, skipping download."
49
+ else
50
+ FileUtils.mkdir_p PEBuild.archive_directory unless File.directory? PEBuild.archive_directory
51
+ cmd = %{curl -L -A "Vagrant/PEBuild (v#{PEBuild::VERSION})" -O #{url}}
52
+ @env[:ui].info "Executing '#{cmd}'"
53
+ Dir.chdir(PEBuild.archive_directory) { %x{#{cmd}} }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,70 @@
1
+ require 'vagrant'
2
+ require 'pe_build/action'
3
+ require 'fileutils'
4
+
5
+ class PEBuild::Action::Unpackage
6
+ def initialize(app, env)
7
+ @app, @env = app, env
8
+ load_variables
9
+ end
10
+
11
+ def call(env)
12
+ @env = env
13
+ @extracted_dir = File.join(@env[:unpack_directory], destination_directory)
14
+ extract_build
15
+ @app.call(@env)
16
+ end
17
+
18
+ private
19
+
20
+ def load_variables
21
+ if @env[:box_name]
22
+ @root = @env[:vm].pe_build.download_root
23
+ @version = @env[:vm].pe_build.version
24
+ @filename = @env[:vm].pe_build.version
25
+ end
26
+
27
+ @root ||= @env[:global_config].pe_build.download_root
28
+ @version ||= @env[:global_config].pe_build.version
29
+ @filename ||= @env[:global_config].pe_build.filename
30
+
31
+ @archive_path = File.join(PEBuild.archive_directory, @filename)
32
+ end
33
+
34
+ # Sadly, shelling out is more sane than trying to use the facilities
35
+ # provided.
36
+ def extract_build
37
+ if File.directory? @extracted_dir
38
+ @env[:ui].info "#{destination_directory} already present, skipping extraction."
39
+ else
40
+ cmd = %{tar xf #{@archive_path} -C #{@env[:unpack_directory]}}
41
+ @env[:ui].info "Extracting #{@archive_path} to #{@env[:unpack_directory]}"
42
+ %x{#{cmd}}
43
+ end
44
+ rescue => e
45
+ # If anything goes wrong while extracting, nuke the extracted directory
46
+ # as it could be incomplete. If we do this, then we can ensure that if
47
+ # the extracted directory already exists then it will be in a good state.
48
+ @env[:ui].info "Removing possibly damaged installer directory '#{@extracted_dir}'"
49
+ FileUtils.rm_r @extracted_dir
50
+ end
51
+
52
+ # Determine the name of the top level directory by peeking into the tarball
53
+ def destination_directory
54
+ raise Errno::ENOENT, "No such file \"#{@archive_path}\"" unless File.file? @archive_path
55
+
56
+ firstline = nil
57
+ dir = nil
58
+ dir_regex = %r[^(.*?)/]
59
+ IO.popen(%{tar -tf #{@archive_path}}) { |out| firstline = out.gets }
60
+
61
+ if firstline.nil? or firstline.empty?
62
+ raise "Could not get a directory listing from the installer tarfile #{File.basename @archive_path}"
63
+ elsif (match = firstline.match dir_regex)
64
+ dir = match[1]
65
+ else
66
+ raise "Could not determine the base directory name from #{firstline} - doesn't match #{dir_regex.to_s}"
67
+ end
68
+ dir
69
+ end
70
+ end
@@ -0,0 +1,31 @@
1
+ require 'pe_build'
2
+ require 'vagrant'
3
+
4
+ class PEBuild::Command < Vagrant::Command::Base
5
+ def initialize(argv, env)
6
+ @argv, @env = argv, env
7
+
8
+ @main_args, @subcommand, @sub_args = split_main_and_subcommand(argv)
9
+
10
+ # Is this even remotely sane? Are we verging on cargo cult programming?
11
+ @subcommands = Vagrant::Registry.new
12
+
13
+ @subcommands.register('download') { PEBuild::Command::Download }
14
+ @subcommands.register('list') { PEBuild::Command::List }
15
+ end
16
+
17
+ def execute
18
+ if @subcommand and (klass = @subcommands.get(@subcommand))
19
+ klass.new(@argv, @env).execute
20
+ elsif @subcommand
21
+ raise "Unrecognized subcommand #{@subcommand}"
22
+ else
23
+ PEBuild::Command::List.new(@argv, @env).execute
24
+ end
25
+ end
26
+ end
27
+
28
+ require 'pe_build/command/list'
29
+ require 'pe_build/command/download'
30
+
31
+ Vagrant.commands.register(:pe_build) { PEBuild::Command }