orca 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ class Orca::Resolver
2
+ attr_reader :packages, :tree
3
+ def initialize(package)
4
+ @package = package
5
+ @last_seen = package
6
+ @tree = [@package]
7
+ @packages = []
8
+ end
9
+
10
+ def resolve
11
+ dependancies = @package.dependancies.reverse.map { |d| Orca::PackageIndex.default.get(d) }
12
+ begin
13
+ @tree += dependancies.map {|d| Orca::Resolver.new(d).resolve.tree }
14
+ rescue SystemStackError
15
+ raise CircularDependancyError.new
16
+ end
17
+ @packages = @tree.flatten
18
+ @packages.reverse!
19
+ @packages.uniq!
20
+ add_children
21
+ self
22
+ end
23
+
24
+ def add_children
25
+ @packages = @packages.reduce([]) do |arr, package|
26
+ arr << package
27
+ package.children.each do |child_name|
28
+ child = Orca::PackageIndex.default.get(child_name)
29
+ arr << child unless arr.include?(child)
30
+ end
31
+ arr
32
+ end
33
+ end
34
+
35
+ class CircularDependancyError < StandardError
36
+ end
37
+ end
@@ -0,0 +1,81 @@
1
+ class Orca::Runner
2
+ def initialize(node, package)
3
+ @node = node
4
+ @package = package
5
+ @perform = true
6
+ end
7
+
8
+ def packages
9
+ resolver = Orca::Resolver.new(@package)
10
+ resolver.resolve
11
+ resolver.packages
12
+ end
13
+
14
+ def execute(command_name)
15
+ @node.log command_name, packages.map(&:name).join(', ').yellow
16
+ packages.each do |pkg|
17
+ send(:"execute_#{command_name}", pkg)
18
+ end
19
+ end
20
+
21
+ def execute_apply(pkg)
22
+ return unless should_run?(pkg, :apply)
23
+ exec(pkg, :apply)
24
+ validate!(pkg)
25
+ end
26
+
27
+ def execute_remove(pkg)
28
+ return unless should_run?(pkg, :remove)
29
+ exec(pkg, :remove)
30
+ end
31
+
32
+ def execute_validate(pkg)
33
+ validate!(pkg)
34
+ end
35
+
36
+ def should_run?(pkg, command_name)
37
+ return false unless pkg.provides_command?(command_name)
38
+ return true unless @perform
39
+ return true unless command_name == :apply || command_name == :remove
40
+ return true unless pkg.provides_command?(:validate)
41
+ is_present = is_valid?(pkg)
42
+ return !is_present if command_name == :apply
43
+ return is_present
44
+ end
45
+
46
+ def validate!(pkg)
47
+ return true unless @perform
48
+ return unless pkg.provides_command?(:validate)
49
+ return if is_valid?(pkg)
50
+ raise ValidationFailureError.new(@node, pkg)
51
+ end
52
+
53
+ def is_valid?(pkg)
54
+ results = exec(pkg, :validate)
55
+ results.all?
56
+ end
57
+
58
+ def demonstrate(command_name)
59
+ @perform = false
60
+ execute(command_name)
61
+ @perform = true
62
+ end
63
+
64
+ def exec(pkg, command_name)
65
+ @node.log pkg.name, command_name.to_s.yellow
66
+ context = @perform ? Orca::ExecutionContext.new(@node) : Orca::MockExecutionContext.new(@node)
67
+ cmds = pkg.command(command_name)
68
+ cmds.map {|cmd| context.apply(cmd) }
69
+ end
70
+
71
+ class ValidationFailureError < StandardError
72
+ def initialize(node, package)
73
+ @node = node
74
+ @package = package
75
+ end
76
+
77
+ def message
78
+ "Package #{@package.name} failed validation on #{@node.to_s}"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,18 @@
1
+ class Orca::Suite
2
+
3
+ def load_file(file)
4
+ Orca::DSL.module_eval(File.read(file))
5
+ end
6
+
7
+ def execute(node_name, pkg_name, command)
8
+ node = Orca::Node.find(node_name)
9
+ pkg = Orca::PackageIndex.default.get(pkg_name)
10
+ Orca::Runner.new(node, pkg).execute(command)
11
+ end
12
+
13
+ def demonstrate(node_name, pkg_name, command)
14
+ node = Orca::Node.find(node_name)
15
+ pkg = Orca::PackageIndex.default.get(pkg_name)
16
+ Orca::Runner.new(node, pkg).demonstrate(command)
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.authors = ["Andy Kent"]
3
+ gem.email = ["andy.kent@me.com"]
4
+ gem.description = %q{Orca is a super simple way to build and configure servers}
5
+ gem.summary = %q{Simplified Machine Building}
6
+ gem.homepage = ""
7
+
8
+ gem.files = `git ls-files`.split($\)
9
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
10
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
11
+ gem.name = "orca"
12
+ gem.require_paths = ["lib"]
13
+ gem.version = '0.1.0'
14
+ gem.add_dependency('colored')
15
+ gem.add_dependency('net-ssh')
16
+ gem.add_dependency('net-sftp')
17
+ gem.add_dependency('thor')
18
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Orca::DSL do
4
+ describe "'package' command" do
5
+ after :each do
6
+ reset_package_index!
7
+ end
8
+
9
+ it "adds a package to the index" do
10
+ Orca::DSL.package('my-package') { nil }
11
+ Orca::PackageIndex.default.get('my-package').must_be_instance_of Orca::Package
12
+ end
13
+
14
+ it "creates a new package based on the supplied name" do
15
+ Orca::DSL.package('my-package') { nil }
16
+ package = Orca::PackageIndex.default.get('my-package')
17
+ package.name.must_equal 'my-package'
18
+ end
19
+
20
+ it "executes the given definition block in the package context" do
21
+ Orca::DSL.package('my-package') { depends_on 'other-package' }
22
+ package = Orca::PackageIndex.default.get('my-package')
23
+ package.dependancies.must_equal ['other-package']
24
+ end
25
+ end
26
+
27
+ describe "'node' command" do
28
+ it "creates a node and adds it to the index" do
29
+ Orca::DSL.node('node-name', 'node-host')
30
+ node = Orca::Node.find('node-name')
31
+ node.name.must_equal 'node-name'
32
+ node.host.must_equal 'node-host'
33
+ end
34
+ end
35
+ end
@@ -0,0 +1 @@
1
+ example
@@ -0,0 +1,59 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Orca::LocalFile do
4
+ before :each do
5
+ @local_file_path = File.join(File.dirname(__FILE__), 'fixtures', 'example.txt')
6
+ @local_file = Orca::LocalFile.new(@local_file_path)
7
+ end
8
+
9
+ describe 'path' do
10
+ it "returns the absolute path of a local file" do
11
+ @local_file.path.must_equal @local_file_path
12
+ end
13
+ end
14
+
15
+ describe 'hash' do
16
+ it "returns the sha1 of a local file" do
17
+ @local_file.hash.must_equal 'c3499c2729730a7f807efb8676a92dcb6f8a3f8f'
18
+ end
19
+ end
20
+
21
+ describe 'matches' do
22
+ it "returns true if the passed file has a matching hash" do
23
+ @local_file.matches?(@local_file).must_equal true
24
+ end
25
+ end
26
+
27
+ describe 'exists?' do
28
+ it "checks if a file exists" do
29
+ @local_file.exists?.must_equal true
30
+ end
31
+ end
32
+
33
+ describe 'copy_to' do
34
+ before(:each) { @destination = Orca::LocalFile.new("/tmp/example-#{Time.now.to_i}.txt") }
35
+ after(:each) { @destination.delete! }
36
+
37
+ it "copies a file to another local location" do
38
+ @destination.exists?.must_equal false
39
+ @local_file.copy_to(@destination).must_equal @destination
40
+ @destination.exists?.must_equal true
41
+ end
42
+
43
+ it "copies a file to a remote location by uploading it" do
44
+ @remote_destination_context = mock()
45
+ @remote_destination = Orca::RemoteFile.new(@remote_destination_context, "/tmp/example-dest.txt")
46
+ @remote_destination_context.expects(:upload).with(@local_file.path, @remote_destination.path)
47
+ @local_file.copy_to(@remote_destination).must_equal @remote_destination
48
+ end
49
+ end
50
+
51
+ describe 'set_permissions' do
52
+ it "sets permissions on the file based on a mask" do
53
+ @local_file.set_permissions(0664).must_equal @local_file
54
+ @local_file.permissions.must_equal 0664
55
+ @local_file.set_permissions(0644).must_equal @local_file
56
+ @local_file.permissions.must_equal 0644
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Orca::PackageIndex do
4
+ before :each do
5
+ @package = Orca::Package.new('my-package')
6
+ @default = Orca::PackageIndex.default
7
+ end
8
+
9
+ after :each do
10
+ reset_package_index!
11
+ end
12
+
13
+ describe "default" do
14
+ it "returns a package index singleton named default" do
15
+ Orca::PackageIndex.default.index_name.must_equal 'default'
16
+ end
17
+
18
+ it "allways returns the same package index" do
19
+ Orca::PackageIndex.default.must_equal Orca::PackageIndex.default
20
+ end
21
+ end
22
+
23
+ describe "add" do
24
+ it "adds a package to the index" do
25
+ @default.add(@package)
26
+ @default.get(@package.name).must_equal @package
27
+ end
28
+ end
29
+
30
+ describe 'get' do
31
+ it "fetches a package by name" do
32
+ @default.add(@package)
33
+ @default.get(@package.name).must_equal @package
34
+ end
35
+
36
+ it "throws an execption if the package doesn't exist" do
37
+ assert_raises(Orca::PackageIndex::MissingPackageError) { @default.get(@package.name) }
38
+ end
39
+ end
40
+
41
+ describe "clear!" do
42
+ it "wipes the index clean of packages" do
43
+ @default.add(@package)
44
+ @default.clear!
45
+ assert_raises(Orca::PackageIndex::MissingPackageError) { @default.get(@package.name) }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Orca::Package do
4
+ before :each do
5
+ @package = Orca::Package.new('my-package')
6
+ end
7
+
8
+ describe "depends_on" do
9
+ it "adds a dependancy" do
10
+ @package.dependancies.must_equal []
11
+ @package.depends_on('other-package')
12
+ @package.dependancies.must_equal ['other-package']
13
+ end
14
+
15
+ it "adds multiple dependancies at once" do
16
+ @package.dependancies.must_equal []
17
+ @package.depends_on('other-package', 'third-package')
18
+ @package.dependancies.must_equal ['other-package', 'third-package']
19
+ end
20
+ end
21
+
22
+ describe "action" do
23
+ it "allows defining actions with a name and a block" do
24
+ @package.action('my-action') { 'foo' }
25
+ @package.actions['my-action'].call.must_equal 'foo'
26
+ end
27
+ end
28
+
29
+ describe "commands" do
30
+ it "can add an 'apply' command" do
31
+ @package.apply { 'my-apply' }
32
+ @package.command(:apply).first.call.must_equal 'my-apply'
33
+ end
34
+
35
+ it "can add an 'remove' command" do
36
+ @package.remove { 'my-remove' }
37
+ @package.command(:remove).first.call.must_equal 'my-remove'
38
+ end
39
+
40
+ it "can add an 'validate' command" do
41
+ @package.validate { 'my-validate' }
42
+ @package.command(:validate).first.call.must_equal 'my-validate'
43
+ end
44
+
45
+ it 'knows if a command is provided' do
46
+ @package.provides_command?(:apply).must_equal false
47
+ @package.apply { 'my-apply' }
48
+ @package.provides_command?(:apply).must_equal true
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,91 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Orca::RemoteFile do
4
+ before :each do
5
+ @local_file_path = File.join(File.dirname(__FILE__), 'fixtures', 'example.txt')
6
+ @local_file = Orca::LocalFile.new(@local_file_path)
7
+ @remote_file_path = '/tmp/example.txt'
8
+ @context = mock()
9
+ @remote_file = Orca::RemoteFile.new(@context, @remote_file_path)
10
+ end
11
+
12
+ describe 'path' do
13
+ it "returns the absolute path of a local file" do
14
+ @remote_file.path.must_equal @remote_file_path
15
+ end
16
+ end
17
+
18
+ describe 'hash' do
19
+ it "returns the sha1 of a remote file" do
20
+ @context.expects(:run)
21
+ .with(%[if [ -f #{@remote_file_path} ]; then echo "true"; else echo "false"; fi])
22
+ .returns("true\n")
23
+ @context.expects(:run)
24
+ .with("sha1sum #{@remote_file_path}")
25
+ .returns("c3499c2729730a7f807efb8676a92dcb6f8a3f8f #{@remote_file_path}")
26
+ @remote_file.hash.must_equal 'c3499c2729730a7f807efb8676a92dcb6f8a3f8f'
27
+ end
28
+ end
29
+
30
+ describe 'matches' do
31
+ it "returns true if the passed file has a matching hash" do
32
+ @context.expects(:run)
33
+ .with(%[if [ -f #{@remote_file_path} ]; then echo "true"; else echo "false"; fi])
34
+ .returns("true\n")
35
+ @context.expects(:run)
36
+ .with("sha1sum #{@remote_file_path}")
37
+ .returns("c3499c2729730a7f807efb8676a92dcb6f8a3f8f #{@remote_file_path}")
38
+ @remote_file.matches?(@remote_file).must_equal true
39
+ end
40
+ end
41
+
42
+ describe 'exists?' do
43
+ it "checks if a file exists" do
44
+ @context.expects(:run)
45
+ .with(%[if [ -f #{@remote_file_path} ]; then echo "true"; else echo "false"; fi])
46
+ .returns("true\n")
47
+ @remote_file.exists?.must_equal true
48
+ end
49
+
50
+ it "checks if a missing file exists" do
51
+ @context.expects(:run)
52
+ .with(%[if [ -f #{@remote_file_path} ]; then echo "true"; else echo "false"; fi])
53
+ .returns("false\n")
54
+ @remote_file.exists?.must_equal false
55
+ end
56
+ end
57
+
58
+ describe 'copy_to' do
59
+ it "copies a file to another remote location" do
60
+ @remote_destination_context = mock()
61
+ @remote_destination = Orca::RemoteFile.new(@remote_destination_context, "/tmp/example-dest.txt")
62
+ @context.expects(:sudo)
63
+ .with(%[cp #{@remote_file.path} #{@remote_destination.path}])
64
+ .returns("true\n")
65
+ @remote_file.copy_to(@remote_destination).must_equal @remote_destination
66
+ end
67
+
68
+ it "copies a file to local location by downlaoding it" do
69
+ @local_destination = Orca::LocalFile.new("/tmp/example-#{Time.now.to_i}.txt")
70
+ @context.expects(:download)
71
+ .with(@remote_file_path, @local_destination.path)
72
+ @remote_file.copy_to(@local_destination).must_equal @local_destination
73
+ end
74
+ end
75
+
76
+ describe 'delete!' do
77
+ it "removes the file from the remote server" do
78
+ @context.expects(:remove).with(@remote_file.path)
79
+ @remote_file.delete!
80
+ end
81
+ end
82
+
83
+ describe 'set_permissions' do
84
+ it "sets permissions on the file based on a mask" do
85
+ @context.expects(:sudo).with('chmod -R 644 /tmp/example.txt')
86
+ @context.expects(:run).with("stat --format=%a /tmp/example.txt").returns("644\n")
87
+ @remote_file.set_permissions(0644).must_equal @remote_file
88
+ @remote_file.permissions.must_equal 0644
89
+ end
90
+ end
91
+ end