orca 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+
2
+ pkg/*.gem
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'mocha', :require => false
@@ -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.
@@ -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
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+
4
+ require_relative "../lib/orca"
5
+
6
+ Orca::Cli.start(ARGV)
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
@@ -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"
@@ -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
@@ -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