arnold 0.0.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.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2013 Puppet Labs, info@puppetlabs.com
2
+ Copyright (c) 2013 FBL Financial, puppet@fblfinancial.com
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,92 @@
1
+ Introduction
2
+ ============
3
+
4
+ ### Arnold: *the provisionator*
5
+
6
+ This is the start of a Razor provisioning system.
7
+
8
+ Currently, all it does is provide a web service that will read & write YAML files
9
+ and a few functions to read them and apply classes to a node.
10
+
11
+ Very soon, I plan to provide Razor functionality to spin up new instances after
12
+ classifying them, and then it will be a simplistic self service provisioner.
13
+
14
+ Configuration
15
+ =============
16
+
17
+ * Installing
18
+ * It's a Puppet module, yo! Copy the `arnold` subdirectory to your modulepath.
19
+ * Alternately, you can install by hand by running the little installer script.
20
+ * Setup the server
21
+ 1. Classify your server with `arnold::provisionator` and apply.
22
+ 2. Configure by editing `/etc/arnold/config.yaml`
23
+ * You will probably want to point the `datadir` to wherever you've configured Hiera to use.
24
+ * You may configure Arnold to reuse Puppet certs if you wish.
25
+ * If you choose to generate your own SSL certs, drop them in /etc/arnold/certs
26
+ * `openssl genrsa -out server.key 1024`
27
+ * `openssl req -new -key server.key -out server.csr`
28
+ * `openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt`
29
+ 3. Point a web browser at the configured port
30
+ * Clicky clicky
31
+ * List nodes, create nodes, modify nodes, etc
32
+ 4. Enable arnold for clients:
33
+ * In `site.pp` on your master, outside of any node definitions, simply call the `arnold()` function.
34
+ * Alternately, you can classify your nodes with `arnold`
35
+ * If you place this class in your default group, it will automatically be applied to all nodes.
36
+
37
+ Provisioner backend configuration
38
+ =============
39
+
40
+ Currently only the CloudProvisioner provisioning backend exits. You can enable it by
41
+ adding a stanza like this to your `config.yaml`
42
+
43
+ backend: CloudProvisioner
44
+ keyfile: ~/.ssh/private_key.pem
45
+ enc_password: <password>
46
+
47
+ If a backend is not configured, Arnold will not perform any provisioning actions.
48
+ It is entirely useful like this, as it will instead only manage the Hiera datafiles.
49
+
50
+ Limitations
51
+ ============
52
+
53
+ * It does not currently manage parameterized classes.
54
+ * Razor support is not yet included.
55
+
56
+ Contact
57
+ =======
58
+
59
+ * Author: Ben Ford
60
+ * Email: ben.ford@puppetlabs.com
61
+ * Twitter: @binford2k
62
+ * IRC (Freenode): binford2k
63
+
64
+ Credit
65
+ =======
66
+
67
+ The development of this code was sponsored by FBL Financial.
68
+
69
+ License
70
+ =======
71
+
72
+ Copyright (c) 2013 Puppet Labs, info@puppetlabs.com
73
+ Copyright (c) 2013 FBL Financial, puppet@fblfinancial.com
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ "Software"), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
89
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
90
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
91
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
92
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'optparse'
6
+ require 'pathname'
7
+
8
+ require 'arnold/node'
9
+ require 'arnold/node_manager'
10
+ require 'arnold/monkeypatch'
11
+ require 'arnold/controller'
12
+
13
+ cmdlineopts = {}
14
+ optparse = OptionParser.new { |opts|
15
+ opts.banner = "Usage : arnold [-c <confdir>] [-d]
16
+
17
+ Runs the arnold daemon.
18
+
19
+ "
20
+
21
+ opts.on("-b", "--backend BACKEND", "Choose an alternate provisioning backend.") do |opt|
22
+ cmdlineopts[:backend] = opt
23
+ end
24
+
25
+ opts.on("-c CONFIG", "--config CONFIG", "Choose an alternate config file. Defaults to /etc/arnold/config.yaml") do |opt|
26
+ configfile = opt
27
+ end
28
+
29
+ opts.on("-d", "--debug", "Run in the foreground and display debugging messages") do
30
+ # Separate options so daemonize = false doesn't force debug
31
+ cmdlineopts[:debug] = true
32
+ cmdlineopts[:daemonize] = false
33
+ end
34
+
35
+ opts.separator('')
36
+
37
+ opts.on("-h", "--help", "Displays this help") do
38
+ puts opts
39
+ exit
40
+ end
41
+ }
42
+ optparse.parse!
43
+
44
+ # Load default configuration data, deferring to command line overrides
45
+ configfile ||= '/etc/arnold/config.yaml'
46
+
47
+ begin
48
+ $CONFIG = YAML.load_file(configfile)
49
+ $CONFIG.merge!(cmdlineopts)
50
+ rescue Errno::ENOENT => e
51
+ puts "Config file doesn't exist; loading defaults! (#{e})" if cmdlineopts[:debug]
52
+ $CONFIG={}
53
+ end
54
+
55
+ $CONFIG[:docroot] ||= File.dirname(__FILE__) + '/../' # well isn't this ugly
56
+ $CONFIG[:backend] ||= 'Null'
57
+ $CONFIG[:datadir] ||= '/etc/arnold/data'
58
+ $CONFIG[:sslcert] ||= '/etc/arnold/certs/server.crt'
59
+ $CONFIG[:sslkey] ||= '/etc/arnold/certs/server.key'
60
+ $CONFIG[:port] ||= 9090
61
+ $CONFIG[:enc_server] ||= 'localhost'
62
+ $CONFIG[:enc_port] ||= 443
63
+ $CONFIG[:enc_user] ||= 'console'
64
+
65
+ begin
66
+ # load up our provisioning backend
67
+ require "arnold/provisioner/#{$CONFIG[:backend].to_underscore}"
68
+ $CONFIG[:provisioner] = Arnold::Provisioner::const_get($CONFIG[:backend]).new
69
+ rescue LoadError, NameError => e
70
+ puts "No such provisioning backend, loading null backend! (#{e})"
71
+ require 'arnold/provisioner/null'
72
+ $CONFIG[:provisioner] = Arnold::Provisioner::Null.new
73
+ end
74
+
75
+ case ARGV[0]
76
+ when 'serve'
77
+ Arnold::Controller::Web.new
78
+
79
+ else
80
+ Arnold::Controller::Cli.new(ARGV)
81
+
82
+ end
@@ -0,0 +1 @@
1
+ # Place certificates in this directory
@@ -0,0 +1,15 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICRzCCAbACCQDZGL+AiHPpyjANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJV
3
+ UzEPMA0GA1UECBMGT3JlZ29uMREwDwYDVQQHEwhQb3J0bGFuZDEUMBIGA1UEChML
4
+ UHVwcGV0IExhYnMxCzAJBgNVBAsTAlBTMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcN
5
+ MTIwOTE5MTcxMTQyWhcNMTMwOTE5MTcxMTQyWjBoMQswCQYDVQQGEwJVUzEPMA0G
6
+ A1UECBMGT3JlZ29uMREwDwYDVQQHEwhQb3J0bGFuZDEUMBIGA1UEChMLUHVwcGV0
7
+ IExhYnMxCzAJBgNVBAsTAlBTMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZI
8
+ hvcNAQEBBQADgY0AMIGJAoGBAKjECcBJqQx5G479gtFVSF7M9noXEcTG6tV/Z3SW
9
+ h/N5PjZzovcc9tKzCRfeCzVGupkUblkxwhjP+gWSjcPoioIvG1twfQIDuiG6wtH5
10
+ FfS2q8creJZwd9OxGMTb4mkmfaY+88N9vt0a+OyLE6L9Cw6+CoB2XNAwgcNWKdzs
11
+ QzfPAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAbItcPsOqCqktv+TuXrCgiYdmQjEl
12
+ R/xKqDUGSNkkUJCEjmff6VJslPzdteIkv42trJZMbbQY2SR1eylt7/AmLXmSqEJh
13
+ ivnAF3MCAg59RF3P5RoqFmjUJZcsoGZp7v6d5y2/HB712JKmiohIWj82H6iayW+k
14
+ 43jfo4DvdfNUL7I=
15
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXQIBAAKBgQCoxAnASakMeRuO/YLRVUhezPZ6FxHExurVf2d0lofzeT42c6L3
3
+ HPbSswkX3gs1RrqZFG5ZMcIYz/oFko3D6IqCLxtbcH0CA7ohusLR+RX0tqvHK3iW
4
+ cHfTsRjE2+JpJn2mPvPDfb7dGvjsixOi/QsOvgqAdlzQMIHDVinc7EM3zwIDAQAB
5
+ AoGAWsFJaSFziiSagFOuBLpy96ALL+61/HboFDW2QckthO3/WbLnwTHPPdFPo4kh
6
+ x92oPOfyy35pnYRCNLryB5dG3Al9b6x9G5wx5PqXASShFhgjrUYTA0bPnAeMM7po
7
+ b8x5rMssVhCPYYxmrKVyTEsmzL8EhGcEArEyFfL4JSaEA0ECQQDaMies6EeTeQ3v
8
+ v2hJUiknC4T++RfAubo1/mk4y4U7WUbmLGUXOE6txCbqRMs7HUZrL/kDmxORWudo
9
+ XSevQSPvAkEAxgF2WFMR0C1PpMbKaE8CSQfAOawt9u3eJ+TSRBHa3k+IRb8dg2OQ
10
+ yfbEc1YvUBE3Ycz8eOu5WBzoFLf7uq3KIQJAToNJn4AdcUVX7HL1dZyozjHo805y
11
+ a5jpFlCrUBJ7qHVhe6Vx4r8SIJi6YAXNE0JfemZStidxDRamufj7NKa95QJBALIG
12
+ po0LQzzVQIJ6aYoXX4qh+WbhNAKMI+3iglrJYuv2viNXjgWQA6JSyJaaqrdmg1Df
13
+ qTBfYKmkc9YNBbv2fYECQQDMSMx9gkDu/Rhv8fF8y4gUrVqSh8/aSWqrbaEQwEkp
14
+ MPbCuYg/dh0Epb+btmed5+b3zcQImlPccP0JZGs0nFa4
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,16 @@
1
+ ---
2
+ :sslcert: /etc/arnold/certs/server.crt
3
+ :sslkey: /etc/arnold/certs/server.key
4
+ :datadir: /etc/arnold/data
5
+
6
+ :user: admin
7
+ :password: admin
8
+
9
+ :daemonize: false
10
+ :port: 9090
11
+
12
+ :classes:
13
+ apache: Manage the Apache webserver
14
+ mysql: Manage the MySQL database
15
+ ntp::server: Install the NTP server
16
+ ntp::client: Install the NTP client and configure the node to query the internal NTP server
@@ -0,0 +1,42 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # A quick 'n dirty example of a simple script to exercise Arnold's REST API
4
+
5
+ require 'net/https'
6
+ require 'rubygems'
7
+ require 'json'
8
+ require 'uri'
9
+
10
+ user = 'admin'
11
+ pass = 'admin'
12
+ server = 'localhost'
13
+
14
+ def provision()
15
+ # Call VMware APIs to create a new machine, configure, and boot it.
16
+ # Return its MAC address.
17
+ return '00:0C:29:D1:03:A4'
18
+ end
19
+
20
+ macaddr = provision()
21
+
22
+ payload = {
23
+ 'macaddr' => macaddr,
24
+ 'name' => 'this.is.another.brand.new.system',
25
+ 'parameters' => {
26
+ 'booga' => 'wooga',
27
+ 'fiddle' => 'faddle',
28
+ },
29
+ 'classes' => [ 'test', 'mysql', 'ntp' ],
30
+ }.to_json
31
+
32
+ uri = URI.parse("https://#{server}:9090/api/v1/create")
33
+ http = Net::HTTP.new(uri.host, uri.port)
34
+ http.use_ssl = true
35
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
36
+ request = Net::HTTP::Post.new(uri.request_uri)
37
+ request.add_field('Content-Type', 'application/json')
38
+ request.basic_auth(user, pass)
39
+ request.body = payload
40
+ response = http.request(request)
41
+ puts "Response #{response.code} #{response.message}: #{response.body}"
42
+
@@ -0,0 +1,6 @@
1
+ module Arnold
2
+ module Controller
3
+ autoload :Web, 'arnold/controller/web'
4
+ autoload :Cli, 'arnold/controller/cli'
5
+ end
6
+ end
@@ -0,0 +1,65 @@
1
+ require 'arnold/node'
2
+ require 'arnold/node_manager'
3
+
4
+ module Arnold
5
+ module Controller
6
+ class Cli
7
+
8
+ def initialize(args)
9
+ @manager = Arnold::NodeManager.new
10
+
11
+ case args[0]
12
+ when "help"
13
+ usage
14
+ when "list"
15
+ listnodes
16
+ exit 0
17
+ when "new"
18
+ args.shift
19
+ @data = {}
20
+ args.each do |arg|
21
+ name, value = arg.split("=")
22
+ @data[name] = value
23
+ end
24
+
25
+ begin
26
+ node = Arnold::Node.new(nil,
27
+ @data['name'],
28
+ @data['macaddr'],
29
+ Arnold::Node.munge(@data, :params),
30
+ @data['classes'].split(','))
31
+ @manager.write(node)
32
+
33
+ $CONFIG[:provisioner].provision(node)
34
+ rescue RuntimeError => e
35
+ puts "Whoops: #{e}"
36
+ end
37
+ else
38
+ puts "WAT"
39
+ usage
40
+ end
41
+ end
42
+
43
+ def listnodes
44
+ nodes = @manager.loadall
45
+ puts
46
+ puts "________GUID______________________Name____________________MAC Address___"
47
+ nodes.each do |node|
48
+ printf "%18s │ %30s │ %18s\n", node.guid, node.name, node.macaddr
49
+ end
50
+ puts
51
+ end
52
+
53
+ def usage
54
+ puts
55
+ puts "Usage:"
56
+ puts " * arnold help"
57
+ puts " * arnold list"
58
+ puts " * arnold new [name=<name>] [macaddr=<macaddr>] [template=<template>] [group=<group>] [classes=<class1,class2,...>] [param1=value1]..."
59
+ puts
60
+ exit 1
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ require 'sinatra/base'
2
+ require 'webrick'
3
+ require 'webrick/https'
4
+ require 'openssl'
5
+
6
+ require 'arnold/server'
7
+
8
+ module Arnold
9
+ module Controller
10
+ class Web
11
+ def initialize
12
+ opts = {
13
+ :Port => $CONFIG[:port] || 8080,
14
+ :Logger => WEBrick::Log::new($stderr, WEBrick::Log::DEBUG),
15
+ :ServerType => $CONFIG[:daemonize] ? WEBrick::Daemon : WEBrick::SimpleServer,
16
+ :DocumentRoot => $CONFIG[:docroot],
17
+ :SSLEnable => true,
18
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
19
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open($CONFIG[:sslcert]).read),
20
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open($CONFIG[:sslkey]).read),
21
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ]
22
+ }
23
+
24
+ # now it's off to the races!
25
+ Rack::Handler::WEBrick.run(Arnold::Server, opts) do |server|
26
+ [:INT, :TERM].each { |sig| trap(sig) { server.stop } }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ if not String.method_defined? :to_underscore
2
+ class String
3
+ # ruby mutation methods have the expectation to return self if a mutation
4
+ # occurred, nil otherwise.
5
+ # (see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-gsub-21)
6
+ def to_underscore!
7
+ gsub!(/(.)([A-Z])/,'\1_\2') && downcase!
8
+ end
9
+
10
+ def to_underscore
11
+ dup.tap { |s| s.to_underscore! }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ module Arnold
2
+ class Node
3
+ attr_reader :guid, :name, :macaddr, :parameters, :classes
4
+ @@reserved_params = ['guid', 'name', 'macaddr', 'classes']
5
+
6
+ def initialize(guid=nil, name=nil, macaddr=nil, parameters={}, classes = [])
7
+ # if no guid, this is intended to be a new node
8
+ self.guid = guid
9
+ self.name = name
10
+ self.macaddr = macaddr
11
+ self.parameters = parameters
12
+ self.classes = classes
13
+ end
14
+
15
+ def guid=(g)
16
+ @guid = validate(g, :filename)
17
+ end
18
+
19
+ def name=(n)
20
+ @name = validate(n, :filename)
21
+ end
22
+
23
+ def macaddr=(m)
24
+ @macaddr = validate(m, :macaddr)
25
+ end
26
+
27
+ def parameters=(p)
28
+ @parameters = validate(p, :params)
29
+ end
30
+
31
+ def classes=(c)
32
+ @classes = validate(c, :classes)
33
+ end
34
+
35
+ # returns classes and descriptions for classes enabled or disabled
36
+ def enabled
37
+ return {} if @classes.nil?
38
+ $CONFIG[:classes].select { |name, desc| @classes.include? name }
39
+ end
40
+
41
+ def disabled
42
+ return $CONFIG[:classes] if @classes.nil?
43
+ $CONFIG[:classes].reject { |name, desc| @classes.include? name }
44
+ end
45
+
46
+ # Raise exceptions if the given condition fails
47
+ #
48
+ def validate(value, type=:exists)
49
+ case type
50
+ when :classes
51
+ return if value.nil?
52
+ raise "Invalid type: #{value.class}" if not value.kind_of?(Array)
53
+ value.each { |n| raise "Invalid class: #{n}" if not $CONFIG[:classes].has_key?(n) }
54
+
55
+ when :params
56
+ return if value.nil?
57
+ raise "Invalid type: #{value.class}" if not value.kind_of?(Hash)
58
+ @@reserved_params.each { |n| raise "Invalid parameter: #{n}" if value.has_key?(n) }
59
+
60
+ when :macaddr
61
+ return if value.nil?
62
+ value.upcase!
63
+ raise "Invalid MAC address: #{value}" if not value =~ /^(([0-9A-F]{2}[:-]){5}([0-9A-F]{2}))?$/
64
+
65
+ when :filename
66
+ return if value.nil?
67
+ raise "Invalid name: #{value}" if not value =~ /^([^\/])*$/
68
+
69
+ when :exists
70
+ raise "Value does not exist." if (value.nil? || value.empty?)
71
+
72
+ end
73
+
74
+ return value
75
+ end
76
+
77
+ def self.munge(value, type=:upcase)
78
+ case type
79
+ when :upcase
80
+ return value.nil? ? nil : value.upcase
81
+ when :params
82
+ return value.reject { |name, val| @@reserved_params.include? name }
83
+ end
84
+ end
85
+ end
86
+ end