oscar 0.2.0alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +12 -0
- data/Gemfile +1 -0
- data/LICENSE +14 -0
- data/README.markdown +97 -0
- data/Vagrantfile +6 -0
- data/bootstrap/base/pre/set_hostname.sh +3 -0
- data/bootstrap/base/provision/install_puppet_enterprise.sh +6 -0
- data/bootstrap/master/post/relocate_puppet.sh +23 -0
- data/config/.gitignore +1 -0
- data/config/roles.yaml.dist +17 -0
- data/doc/answers/README.markdown +1 -0
- data/doc/answers/agent.txt +10 -0
- data/doc/answers/master-1.1.txt +22 -0
- data/doc/answers/master-2.0.0.txt +42 -0
- data/doc/answers/master-2.5.0.txt +36 -0
- data/doc/answers/master-db.txt +52 -0
- data/lib/oscar.rb +17 -0
- data/lib/oscar/config.rb +97 -0
- data/lib/oscar/environment.rb +28 -0
- data/lib/oscar/networking.rb +47 -0
- data/lib/oscar/node.rb +91 -0
- data/lib/oscar/schema.yaml +73 -0
- data/lib/oscar/version.rb +3 -0
- data/lib/pe_build.rb +13 -0
- data/lib/pe_build/action.rb +20 -0
- data/lib/pe_build/action/download.rb +56 -0
- data/lib/pe_build/action/unpackage.rb +70 -0
- data/lib/pe_build/command.rb +31 -0
- data/lib/pe_build/command/download.rb +8 -0
- data/lib/pe_build/command/list.rb +10 -0
- data/lib/pe_build/config.rb +30 -0
- data/lib/pe_build/provisioners.rb +9 -0
- data/lib/pe_build/provisioners/puppet_enterprise.rb +1 -0
- data/lib/pe_build/provisioners/puppet_enterprise_bootstrap.rb +138 -0
- data/lib/pe_build/version.rb +5 -0
- data/manifests/.gitignore +3 -0
- data/modules/.gitignore +2 -0
- data/oscar.gemspec +27 -0
- data/templates/answers/agent.txt.erb +10 -0
- data/templates/answers/master.txt.erb +52 -0
- 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
|
+
|
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 }
|