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.
- 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
@@ -0,0 +1,100 @@
|
|
1
|
+
Orca.extension :file_sync do
|
2
|
+
class Orca::Package
|
3
|
+
def file(config)
|
4
|
+
Orca::FileSync.new(self, config).configure
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Orca::FileSync
|
10
|
+
def initialize(parent, config)
|
11
|
+
@parent = parent
|
12
|
+
@config = config
|
13
|
+
raise ArgumentError.new('A file :source must be provided') unless local_path
|
14
|
+
raise ArgumentError.new('A file :destination must be provided') unless remote_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def local_path
|
18
|
+
@config[:source]
|
19
|
+
end
|
20
|
+
|
21
|
+
def remote_path
|
22
|
+
@config[:destination]
|
23
|
+
end
|
24
|
+
|
25
|
+
def permissions
|
26
|
+
@config[:permissions]
|
27
|
+
end
|
28
|
+
|
29
|
+
def user
|
30
|
+
@config[:user]
|
31
|
+
end
|
32
|
+
|
33
|
+
def group
|
34
|
+
@config[:group]
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_dir
|
38
|
+
@config[:create_dir] || @config[:create_dirs]
|
39
|
+
end
|
40
|
+
|
41
|
+
def package_name(suffix)
|
42
|
+
"file-#{suffix}[#{remote_path}]"
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure
|
46
|
+
fs = self
|
47
|
+
add_content_package
|
48
|
+
add_permissions_package unless permissions.nil? and user.nil? and group.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_content_package
|
52
|
+
fs = self
|
53
|
+
add_package('content') do |package|
|
54
|
+
package.command :apply do
|
55
|
+
if fs.create_dir
|
56
|
+
mk_dir = fs.create_dir == true ? File.dirname(fs.remote_path) : fs.create_dir
|
57
|
+
sudo("mkdir -p #{mk_dir}")
|
58
|
+
sudo("chown #{fs.user}:#{fs.group || fs.user} #{mk_dir}") if fs.user
|
59
|
+
end
|
60
|
+
local_file = local(fs.local_path)
|
61
|
+
tmp_path = "orca-upload-#{local_file.hash}"
|
62
|
+
local_file.copy_to(remote(tmp_path))
|
63
|
+
sudo("mv #{tmp_path} #{fs.remote_path}")
|
64
|
+
end
|
65
|
+
|
66
|
+
package.command :remove do
|
67
|
+
remote(fs.remote_path).delete!
|
68
|
+
end
|
69
|
+
|
70
|
+
package.command :validate do
|
71
|
+
local(fs.local_path).matches?(remote(fs.remote_path))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_permissions_package
|
77
|
+
fs = self
|
78
|
+
add_package('permissions') do |package|
|
79
|
+
package.command :apply do
|
80
|
+
remote(fs.remote_path).set_owner(fs.user, fs.group) unless fs.user.nil? and fs.group.nil?
|
81
|
+
remote(fs.remote_path).set_permissions(fs.permissions) unless fs.permissions.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
package.command :validate do
|
85
|
+
r_file = remote(fs.remote_path)
|
86
|
+
valid = r_file.permissions == fs.permissions
|
87
|
+
valid = valid && r_file.user == fs.user if fs.user
|
88
|
+
valid = valid && r_file.group == fs.group if fs.group
|
89
|
+
valid
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_package(suffix)
|
95
|
+
package = Orca.add_package(package_name(suffix))
|
96
|
+
yield(package)
|
97
|
+
@parent.triggers(package.name)
|
98
|
+
package
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Orca::LocalFile
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = resolve(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def resolve(path)
|
12
|
+
if path =~ /^\//
|
13
|
+
path
|
14
|
+
else
|
15
|
+
File.join(Orca.root, path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash
|
20
|
+
return nil unless exists?
|
21
|
+
Digest::SHA1.file(path).hexdigest
|
22
|
+
end
|
23
|
+
|
24
|
+
def exists?
|
25
|
+
File.exists?(@path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# deosnt check permissions or user. should it?
|
29
|
+
def matches?(other)
|
30
|
+
self.exists? && other.exists? && self.hash == other.hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_to(destination)
|
34
|
+
if destination.is_local?
|
35
|
+
duplicate(destination)
|
36
|
+
else
|
37
|
+
upload(destination)
|
38
|
+
end
|
39
|
+
destination
|
40
|
+
end
|
41
|
+
|
42
|
+
def copy_from(destination)
|
43
|
+
destination.copy_to(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def duplicate(destination)
|
47
|
+
FileUtils.cp(path, destination.path)
|
48
|
+
destination
|
49
|
+
end
|
50
|
+
|
51
|
+
def upload(destination)
|
52
|
+
destination.upload(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete!
|
56
|
+
FileUtils.rm(path) if exists?
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_permissions(mask)
|
61
|
+
FileUtils.chmod_R(mask, path)
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def permissions
|
66
|
+
File.stat(path).mode & 0777
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_owner(user, group=nil)
|
70
|
+
FileUtils.chown_R(user, group, path)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def is_local?
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
data/lib/orca/node.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/sftp'
|
3
|
+
|
4
|
+
class Orca::Node
|
5
|
+
attr_reader :name, :host
|
6
|
+
|
7
|
+
def self.find(name)
|
8
|
+
return name if name.is_a?(Orca::Node)
|
9
|
+
@nodes[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register(node)
|
13
|
+
@nodes ||= {}
|
14
|
+
@nodes[node.name] = node
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(name, host, options={})
|
18
|
+
@name = name
|
19
|
+
@host = host
|
20
|
+
@options = options
|
21
|
+
@connection = nil
|
22
|
+
Orca::Node.register(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def upload(from, to)
|
26
|
+
log('sftp', "UPLOAD: #{from} => #{to}")
|
27
|
+
sftp.upload!(from, to)
|
28
|
+
end
|
29
|
+
|
30
|
+
def download(from, to)
|
31
|
+
log('sftp', "DOWLOAD: #{from} => #{to}")
|
32
|
+
sftp.download!(from, to)
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(path)
|
36
|
+
log('sftp', "REMOVE: #{path}")
|
37
|
+
sftp.remove!(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def stat(path)
|
41
|
+
log('sftp', "STAT: #{path}")
|
42
|
+
sftp.stat!(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def setstat(path, opts)
|
46
|
+
log('sftp', "SET: #{path} - #{opts.inspect}")
|
47
|
+
sftp.setstat!(path, opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
def sftp
|
51
|
+
@sftp ||= connection.sftp.connect
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute(cmd)
|
55
|
+
log('execute', cmd.cyan)
|
56
|
+
output = ""
|
57
|
+
connection.exec! cmd do |channel, stream, data|
|
58
|
+
output += data if stream == :stdout
|
59
|
+
data.split("\n").each do |line|
|
60
|
+
msg = stream == :stdout ? line.green : line.red
|
61
|
+
log(stream, msg)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
output
|
65
|
+
end
|
66
|
+
|
67
|
+
def sudo(cmd)
|
68
|
+
execute("sudo #{cmd}")
|
69
|
+
end
|
70
|
+
|
71
|
+
def log(context, msg)
|
72
|
+
puts "#{self.to_s} [#{context.to_s.bold}] #{msg}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def connection
|
76
|
+
return @connection if @connection
|
77
|
+
@connetion = Net::SSH.start(@host, (@options[:user] || 'root'), @options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
"#{name}(#{host})"
|
82
|
+
end
|
83
|
+
end
|
data/lib/orca/package.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
class Orca::Package
|
2
|
+
attr_reader :name, :dependancies, :actions, :children
|
3
|
+
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
@dependancies = []
|
7
|
+
@children = []
|
8
|
+
@actions = {}
|
9
|
+
@commands = {}
|
10
|
+
@remove = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def depends_on(*pkg_names)
|
14
|
+
pkg_names.each do |pkg_name|
|
15
|
+
@dependancies << pkg_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def triggers(*pkg_names)
|
20
|
+
pkg_names.each do |pkg_name|
|
21
|
+
@children << pkg_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate(&definition)
|
26
|
+
command(:validate, &definition)
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply(&definition)
|
30
|
+
command(:apply, &definition)
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove(&definition)
|
34
|
+
command(:remove, &definition)
|
35
|
+
end
|
36
|
+
|
37
|
+
def action(name, &definition)
|
38
|
+
@actions[name] = definition
|
39
|
+
end
|
40
|
+
|
41
|
+
def command(name, &definition)
|
42
|
+
if block_given?
|
43
|
+
(@commands[name.to_sym] ||= []) << definition
|
44
|
+
else
|
45
|
+
@commands[name.to_sym]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def provides_command?(name)
|
50
|
+
@commands.has_key?(name.to_sym)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Orca::PackageIndex
|
2
|
+
attr_reader :index_name
|
3
|
+
|
4
|
+
def initialize(index_name)
|
5
|
+
@index_name = index_name
|
6
|
+
@packages = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.default
|
10
|
+
@default ||= new('default')
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(pkg)
|
14
|
+
@packages[pkg.name] = pkg
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(pkg_name)
|
18
|
+
pkg = @packages[pkg_name]
|
19
|
+
raise MissingPackageError.new(index_name, pkg_name) if pkg.nil?
|
20
|
+
pkg
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear!
|
24
|
+
@packages = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
class MissingPackageError < StandardError
|
28
|
+
def initialize(index_name, package_name)
|
29
|
+
@index_name = index_name
|
30
|
+
@package_name = package_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def message
|
34
|
+
"package #{@package_name} could not be found in the index #{@index_name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Orca::RemoteFile
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(context, path)
|
8
|
+
@context = context
|
9
|
+
@path = path
|
10
|
+
@exists = nil
|
11
|
+
@permissions = nil
|
12
|
+
@owner = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
return nil unless exists?
|
17
|
+
@hash ||= @context.run("sha1sum #{path}")[0...40]
|
18
|
+
end
|
19
|
+
|
20
|
+
def exists?
|
21
|
+
return @exists unless @exists.nil?
|
22
|
+
result = @context.run(%[if [ -f #{path} ]; then echo "true"; else echo "false"; fi])
|
23
|
+
@exists = result.strip == 'true'
|
24
|
+
end
|
25
|
+
|
26
|
+
# deosnt check permissions or user. should it?
|
27
|
+
def matches?(other)
|
28
|
+
self.exists? && other.exists? && self.hash == other.hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def copy_to(destination)
|
32
|
+
if destination.is_local?
|
33
|
+
download(destination)
|
34
|
+
else
|
35
|
+
duplicate(destination)
|
36
|
+
end
|
37
|
+
destination
|
38
|
+
end
|
39
|
+
|
40
|
+
def copy_from(destination)
|
41
|
+
destination.copy_to(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def duplicate(destination)
|
45
|
+
@context.sudo("cp #{path} #{destination.path}")
|
46
|
+
destination
|
47
|
+
end
|
48
|
+
|
49
|
+
def download(destination)
|
50
|
+
@context.download(path, destination.path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def upload(source)
|
54
|
+
@context.upload(source.path, path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete!
|
58
|
+
@context.remove(path)
|
59
|
+
invalidate!
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_permissions(mask)
|
64
|
+
@context.sudo("chmod -R #{sprintf("%o",mask)} #{path}")
|
65
|
+
invalidate!
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def permissions
|
70
|
+
@permissions ||= @context.run("stat --format=%a #{path}").strip.to_i(8)
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_owner(user, group=nil)
|
74
|
+
@context.sudo("chown -R #{user}:#{group} #{path}")
|
75
|
+
invalidate!
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def owner
|
80
|
+
@owner ||= begin
|
81
|
+
user, group = @context.run("stat --format=%U:%G #{path}").chomp.split(":")
|
82
|
+
{:user => user, :group => group}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def user
|
87
|
+
owner[:user]
|
88
|
+
end
|
89
|
+
|
90
|
+
def group
|
91
|
+
owner[:group]
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_local?
|
95
|
+
false
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def invalidate!
|
101
|
+
@permissions = nil
|
102
|
+
@owner = nil
|
103
|
+
end
|
104
|
+
end
|