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