orca 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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