orca 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +29 -0
- data/LICENSE +7 -0
- data/README.md +109 -0
- data/Rakefile +9 -0
- data/bin/orca +6 -0
- data/config/template/files/.empty_directory +0 -0
- data/config/template/orca.rb +28 -0
- data/lib/orca.rb +48 -0
- data/lib/orca/cli.rb +53 -0
- data/lib/orca/dsl.rb +18 -0
- data/lib/orca/execution_context.rb +89 -0
- data/lib/orca/extensions/apt.rb +54 -0
- data/lib/orca/extensions/file_sync.rb +100 -0
- data/lib/orca/local_file.rb +77 -0
- data/lib/orca/node.rb +83 -0
- data/lib/orca/package.rb +52 -0
- data/lib/orca/package_index.rb +37 -0
- data/lib/orca/remote_file.rb +104 -0
- data/lib/orca/resolver.rb +37 -0
- data/lib/orca/runner.rb +81 -0
- data/lib/orca/suite.rb +18 -0
- data/orca.gemspec +18 -0
- data/test/dsl_test.rb +35 -0
- data/test/fixtures/example.txt +1 -0
- data/test/local_file_test.rb +59 -0
- data/test/package_index_test.rb +48 -0
- data/test/package_test.rb +51 -0
- data/test/remote_file_test.rb +91 -0
- data/test/resolver_test.rb +61 -0
- data/test/test_helper.rb +8 -0
- metadata +150 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
orca (0.1.0)
|
5
|
+
colored
|
6
|
+
net-sftp
|
7
|
+
net-ssh
|
8
|
+
thor
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
colored (1.2)
|
14
|
+
metaclass (0.0.1)
|
15
|
+
mocha (0.13.1)
|
16
|
+
metaclass (~> 0.0.1)
|
17
|
+
net-sftp (2.1.2)
|
18
|
+
net-ssh (>= 2.6.5)
|
19
|
+
net-ssh (2.6.7)
|
20
|
+
rake (10.0.4)
|
21
|
+
thor (0.18.1)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
mocha
|
28
|
+
orca!
|
29
|
+
rake
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Andrew Kent
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
Orca
|
2
|
+
====
|
3
|
+
|
4
|
+
**Because building servers shouldn't be a PITA.**
|
5
|
+
|
6
|
+
Orca is a super simple way to build and configure servers.
|
7
|
+
|
8
|
+
If you've found yourself stuck in the gap between deployment tools like Capistrano and full blown infrastructure tools like Puppet and Chef then Orca is probably for you.
|
9
|
+
|
10
|
+
|
11
|
+
What problem does Orca try to solve?
|
12
|
+
------------------------------------
|
13
|
+
|
14
|
+
All too often you need to get a new server up and running to a known state so that you can get an app deployed. Before Orca there were boardly 4 options...
|
15
|
+
|
16
|
+
1. Start from scratch and hand install all the packages, files, permissions, etc. Yourself via trial and error over SSH.
|
17
|
+
2. Use a deployment tool like Capistrano to codeify your shell scripts into semi-reusable steps.
|
18
|
+
3. Use Puppet or Chef in single machine mode.
|
19
|
+
4. Use Full blown Puppet or Chef, this requires a server.
|
20
|
+
|
21
|
+
Orca fills the rather large gap between (2) and (3). It's a bigger gap then you think as both Puppet and Chef require...
|
22
|
+
|
23
|
+
- bootstrapping a machine to a point where you are able to run them
|
24
|
+
- Creating a seperate repository describing the configuration you require
|
25
|
+
- learning their complex syntaxes and structures
|
26
|
+
- hiding the differences of different host OSes
|
27
|
+
|
28
|
+
Orca fixes these problems by...
|
29
|
+
|
30
|
+
- working directly over SSH, all you need is a box tht you can connect to
|
31
|
+
- package definitions can all go in a single file and most servers can be configured in ~50 lines
|
32
|
+
- packages are defined in a ruby based DSL that consists of only 5 very basic commands to learn
|
33
|
+
- Orca makes no assumptions about the underlying OS accept to assume it supports SSH
|
34
|
+
- Orca is extensible and adding platform specific features like package manger support can be achieved in a dozen or so lines.
|
35
|
+
|
36
|
+
|
37
|
+
What problems is Orca avoiding?
|
38
|
+
-------------------------------
|
39
|
+
|
40
|
+
Orca intentionally skirts around some important thengs that may or may not matter to you. If they do then you are probably better using more robust tools such as Puppet or Chef.
|
41
|
+
|
42
|
+
Orca doesn't...
|
43
|
+
|
44
|
+
- try to scale beyond a smallish number of nodes
|
45
|
+
- have any algorithyms that attempt to run periodically and converge divergent configurations
|
46
|
+
- abstract the differences of different host OSes
|
47
|
+
- provide a server to supervise infrastructure configuration
|
48
|
+
|
49
|
+
|
50
|
+
Installation
|
51
|
+
------------
|
52
|
+
|
53
|
+
To install orca you will need to be running Ruby 1.9 or 2.0 and then install the orca gem from this repository...
|
54
|
+
|
55
|
+
gem 'orca', :git => 'git@github.com:andykent/orca.git'
|
56
|
+
|
57
|
+
|
58
|
+
Command Line Usage
|
59
|
+
------------------
|
60
|
+
|
61
|
+
To get started from within your projct you can run...
|
62
|
+
|
63
|
+
bundle exec orca init .
|
64
|
+
|
65
|
+
This will create a config/orca.rb file for you to get started with.
|
66
|
+
|
67
|
+
To ship run a command the syntax is as follows...
|
68
|
+
|
69
|
+
orca [command] [package] [node]
|
70
|
+
|
71
|
+
So here are some examples (assuming you have a package called "app" and a node called "server" defined in your orca.rb)...
|
72
|
+
|
73
|
+
orca apply app server
|
74
|
+
orca remove app server
|
75
|
+
orca demonstrate app server
|
76
|
+
|
77
|
+
|
78
|
+
The Orca DSL
|
79
|
+
------------
|
80
|
+
|
81
|
+
Orca packages are written in a Ruby based DSL. It's really simple to learn in less than 5 mins. Here's an example orca.rb file with all you'll need to know to get started...
|
82
|
+
|
83
|
+
# define a new pacage called 'gem' that provides some actions for managing rubygems
|
84
|
+
package 'gem' do
|
85
|
+
depends_on 'ruby-1.9.3' # this package depends on another package called ruby-1.9.3
|
86
|
+
action 'exists' do |gem_name| # define an action that other packages can trigger called 'exists'
|
87
|
+
run("gem list -i #{gem_name}") =~ /true/ # execute the command, get the output and check it contains 'true'
|
88
|
+
end
|
89
|
+
action 'install' do |gem_name|
|
90
|
+
run "gem install #{gem_name} --no-ri --no-rdoc"
|
91
|
+
end
|
92
|
+
action 'uninstall' do |gem_name|
|
93
|
+
run "gem uninstall #{gem_name} -x -a"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# define a package called 'bundler' that can be used to manage the gem by the same name
|
98
|
+
package 'bundler' do
|
99
|
+
depends_on 'gem'
|
100
|
+
apply do # apply gets called whenever this package or a package that depends on it is applied
|
101
|
+
trigger('gem:install', 'bundler') # trigger triggers defined actions, in this case the action 'instal' on 'gem'
|
102
|
+
end
|
103
|
+
remove do # remove gets called whenever this package or a package that depends on it is removed
|
104
|
+
trigger('gem:remove', 'bundler')
|
105
|
+
end
|
106
|
+
validate do # validate is used internally to check if the package is applied correctly or not
|
107
|
+
trigger('gem:exists', 'bundler')
|
108
|
+
end
|
109
|
+
end
|
data/Rakefile
ADDED
data/bin/orca
ADDED
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# This is an example Orca configuration file
|
2
|
+
# You will need to edit or define for youself at
|
3
|
+
# least one node and one package for orca to work.
|
4
|
+
|
5
|
+
|
6
|
+
# Define at least one or more nodes where you want
|
7
|
+
# package to be applied, nodes are usually
|
8
|
+
# physical or virtual machines.
|
9
|
+
|
10
|
+
node 'server', 'my.server.address'
|
11
|
+
|
12
|
+
|
13
|
+
# packages get applied to servers from the command line
|
14
|
+
# Most simple projects will have an 'app' package which
|
15
|
+
# defines all the projects dependancies.
|
16
|
+
|
17
|
+
package 'app' do
|
18
|
+
depends_on 'my-package'
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Define the other packages that you will need...
|
23
|
+
|
24
|
+
package 'my-package' do
|
25
|
+
apply do
|
26
|
+
# your logic here
|
27
|
+
end
|
28
|
+
end
|
data/lib/orca.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'colored'
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Orca
|
6
|
+
def root
|
7
|
+
File.dirname(ENV['ORCA_FILE'])
|
8
|
+
end
|
9
|
+
module_function :root
|
10
|
+
|
11
|
+
def add_package(name)
|
12
|
+
package = Orca::Package.new(name)
|
13
|
+
yield(package) if block_given?
|
14
|
+
Orca::PackageIndex.default.add(package)
|
15
|
+
package
|
16
|
+
end
|
17
|
+
module_function :add_package
|
18
|
+
|
19
|
+
def extension(name, &blk)
|
20
|
+
Orca::DSL.class_eval(&blk)
|
21
|
+
end
|
22
|
+
module_function :extension
|
23
|
+
|
24
|
+
class MissingExtensionError < StandardError
|
25
|
+
def initialize(extension_name)
|
26
|
+
@extension_name = extension_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
"The extension '#{@extension_name}' is not available."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require_relative "./orca/package"
|
36
|
+
require_relative "./orca/package_index"
|
37
|
+
require_relative "./orca/node"
|
38
|
+
require_relative "./orca/runner"
|
39
|
+
require_relative "./orca/resolver"
|
40
|
+
require_relative "./orca/execution_context"
|
41
|
+
require_relative "./orca/local_file"
|
42
|
+
require_relative "./orca/remote_file"
|
43
|
+
require_relative "./orca/dsl"
|
44
|
+
require_relative "./orca/suite"
|
45
|
+
require_relative "./orca/cli"
|
46
|
+
|
47
|
+
require_relative "./orca/extensions/apt"
|
48
|
+
require_relative "./orca/extensions/file_sync"
|
data/lib/orca/cli.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
class Orca::Cli < Thor
|
2
|
+
include Thor::Actions
|
3
|
+
|
4
|
+
source_root File.join(File.dirname(__FILE__), *%w[.. .. config])
|
5
|
+
|
6
|
+
class_option :demonstrate, :type => :boolean, :desc => "Don't actually run any commands on the node, just pretend."
|
7
|
+
class_option :file, :banner => 'ORCA_FILE', :desc => "path to the orca.rb file to load, defaults to ./orca/orca.rb"
|
8
|
+
class_option :throw, :type => :boolean, :desc => "Don't pretty print errors, raise with a stack trace."
|
9
|
+
|
10
|
+
desc "apply PACKAGE_NAME NODE_NAME", "apply the given package onto the given named node"
|
11
|
+
def apply(package, node)
|
12
|
+
run_command(package, node, :apply)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "remove PACKAGE_NAME NODE_NAME", "remove the given package onto the given named node"
|
16
|
+
def remove(package, node)
|
17
|
+
run_command(package, node, :remove)
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "validate PACKAGE_NAME NODE_NAME", "run validation steps on the given named node"
|
21
|
+
def validate(package, node)
|
22
|
+
run_command(package, node, :validate)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "init", "initialize the current directory with a orca/orca.rb"
|
26
|
+
def init
|
27
|
+
directory('template', 'orca')
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def run_command(package, node, cmd)
|
33
|
+
begin
|
34
|
+
suite = Orca::Suite.new
|
35
|
+
suite.load_file(orca_file)
|
36
|
+
if options[:demonstrate]
|
37
|
+
suite.demonstrate(node, package, cmd)
|
38
|
+
else
|
39
|
+
suite.execute(node, package, cmd)
|
40
|
+
end
|
41
|
+
rescue => e
|
42
|
+
if options[:throw]
|
43
|
+
raise e
|
44
|
+
else
|
45
|
+
puts "!!! ERROR !!! [#{e.class.name}] #{e.message}".red.bold
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def orca_file
|
51
|
+
ENV['ORCA_FILE'] ||= (options[:file] || File.join(Dir.pwd, 'orca', 'orca.rb'))
|
52
|
+
end
|
53
|
+
end
|
data/lib/orca/dsl.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Orca
|
2
|
+
module DSL
|
3
|
+
module_function
|
4
|
+
def package(name, &definition)
|
5
|
+
Orca.add_package(name) do |pkg|
|
6
|
+
pkg.instance_eval(&definition)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_extension(name)
|
11
|
+
Orca.load_extension(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def node(name, host, options={})
|
15
|
+
Orca::Node.new(name, host, options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class Orca::ExecutionContext
|
2
|
+
def initialize(node)
|
3
|
+
@node = node
|
4
|
+
end
|
5
|
+
|
6
|
+
def apply(blk)
|
7
|
+
instance_eval(&blk)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(cmd)
|
11
|
+
@node.execute(cmd)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sudo(cmd)
|
15
|
+
@node.sudo(cmd)
|
16
|
+
end
|
17
|
+
|
18
|
+
def upload(from, to)
|
19
|
+
@node.upload(from, to)
|
20
|
+
end
|
21
|
+
|
22
|
+
def download(from, to)
|
23
|
+
@node.download(from, to)
|
24
|
+
end
|
25
|
+
|
26
|
+
def local(path)
|
27
|
+
Orca::LocalFile.new(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove(path)
|
31
|
+
@node.remove(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def stat(path)
|
35
|
+
@node.stat(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def setstat(path, opts)
|
39
|
+
@node.setstat(path, opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
def remote(path)
|
43
|
+
Orca::RemoteFile.new(self, path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def trigger(action_ref, *args)
|
47
|
+
pkg_name, action_name = *action_ref.split(':', 2)
|
48
|
+
pkg = Orca::PackageIndex.default.get(pkg_name)
|
49
|
+
action = pkg.actions[action_name]
|
50
|
+
raise "Action #{action_ref} could not be found." unless action
|
51
|
+
instance_exec(*args, &action)
|
52
|
+
end
|
53
|
+
|
54
|
+
def binary_exists?(binary)
|
55
|
+
run("which #{binary}") =~ /\/#{binary}/
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Orca::MockExecutionContext < Orca::ExecutionContext
|
60
|
+
def run(cmd)
|
61
|
+
@node.log 'mock-execute', cmd
|
62
|
+
''
|
63
|
+
end
|
64
|
+
|
65
|
+
def sudo(cmd)
|
66
|
+
@node.log 'mock-execute', "sudo #{cmd}"
|
67
|
+
''
|
68
|
+
end
|
69
|
+
|
70
|
+
def upload(from, to)
|
71
|
+
@node.log('mock-sftp', "UPLOAD: #{from} => #{to}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def download(from, to)
|
75
|
+
@node.log('mock-sftp', "DOWLOAD: #{from} => #{to}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove(path)
|
79
|
+
@node.log('mock-sftp', "REMOVE: #{path}")
|
80
|
+
end
|
81
|
+
|
82
|
+
def stat(path)
|
83
|
+
@node.log('mock-sftp', "STAT: #{path}")
|
84
|
+
end
|
85
|
+
|
86
|
+
def setstat(path, opts)
|
87
|
+
@node.log('mock-sftp', "SET: #{path} - #{opts.inspect}")
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
Orca.extension :apt do
|
2
|
+
module_function
|
3
|
+
def apt_package(pkg_name, apt_name=pkg_name, &blk)
|
4
|
+
package pkg_name do
|
5
|
+
depends_on 'apt'
|
6
|
+
validate { trigger 'apt:exists', apt_name }
|
7
|
+
apply do
|
8
|
+
trigger 'apt:update'
|
9
|
+
trigger 'apt:install', apt_name
|
10
|
+
end
|
11
|
+
remove { trigger 'apt:remove', apt_name }
|
12
|
+
instance_eval(&blk) if blk
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
package 'apt' do
|
17
|
+
action 'install' do |package_name|
|
18
|
+
sudo "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq #{package_name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
action 'remove' do |package_name|
|
22
|
+
sudo "DEBIAN_FRONTEND=noninteractive apt-get remove -y -qq #{package_name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
action 'ppa' do |repo|
|
26
|
+
sudo "DEBIAN_FRONTEND=noninteractive add-apt-repository #{repo} -y"
|
27
|
+
end
|
28
|
+
|
29
|
+
action 'update' do
|
30
|
+
sudo "DEBIAN_FRONTEND=noninteractive apt-get update -y -qq"
|
31
|
+
end
|
32
|
+
|
33
|
+
action 'exists' do |package_name|
|
34
|
+
run("dpkg -s #{package_name} 2>&1 | grep Status") =~ /Status: install ok installed/
|
35
|
+
end
|
36
|
+
|
37
|
+
validate do
|
38
|
+
trigger('apt:exists', 'python-software-properties') &&
|
39
|
+
trigger('apt:exists', 'software-properties-common')
|
40
|
+
end
|
41
|
+
|
42
|
+
apply do
|
43
|
+
trigger 'apt:update'
|
44
|
+
trigger 'apt:install', 'python-software-properties'
|
45
|
+
trigger 'apt:install', 'software-properties-common'
|
46
|
+
end
|
47
|
+
|
48
|
+
remove do
|
49
|
+
trigger 'apt:remove', 'python-software-properties'
|
50
|
+
trigger 'apt:remove', 'software-properties-common'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|