orca 0.1.0

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.
@@ -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