inprovise 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +28 -0
  4. data/Gemfile +9 -0
  5. data/LICENSE +8 -0
  6. data/README.md +197 -0
  7. data/Rakefile.rb +9 -0
  8. data/bin/rig +5 -0
  9. data/inprovise.gemspec +22 -0
  10. data/lib/inprovise/channel/ssh.rb +202 -0
  11. data/lib/inprovise/cli/group.rb +86 -0
  12. data/lib/inprovise/cli/node.rb +95 -0
  13. data/lib/inprovise/cli/provision.rb +84 -0
  14. data/lib/inprovise/cli.rb +105 -0
  15. data/lib/inprovise/cmd_channel.rb +100 -0
  16. data/lib/inprovise/cmd_helper.rb +150 -0
  17. data/lib/inprovise/control.rb +326 -0
  18. data/lib/inprovise/execution_context.rb +277 -0
  19. data/lib/inprovise/group.rb +67 -0
  20. data/lib/inprovise/helper/cygwin.rb +43 -0
  21. data/lib/inprovise/helper/linux.rb +181 -0
  22. data/lib/inprovise/helper/windows.rb +123 -0
  23. data/lib/inprovise/infra.rb +122 -0
  24. data/lib/inprovise/local_file.rb +120 -0
  25. data/lib/inprovise/logger.rb +79 -0
  26. data/lib/inprovise/node.rb +271 -0
  27. data/lib/inprovise/remote_file.rb +128 -0
  28. data/lib/inprovise/resolver.rb +36 -0
  29. data/lib/inprovise/script.rb +175 -0
  30. data/lib/inprovise/script_index.rb +46 -0
  31. data/lib/inprovise/script_runner.rb +110 -0
  32. data/lib/inprovise/sniff.rb +46 -0
  33. data/lib/inprovise/sniffer/linux.rb +64 -0
  34. data/lib/inprovise/sniffer/platform.rb +46 -0
  35. data/lib/inprovise/sniffer/unknown.rb +11 -0
  36. data/lib/inprovise/sniffer/windows.rb +32 -0
  37. data/lib/inprovise/template/inprovise.rb.erb +92 -0
  38. data/lib/inprovise/template.rb +38 -0
  39. data/lib/inprovise/trigger_runner.rb +36 -0
  40. data/lib/inprovise/version.rb +10 -0
  41. data/lib/inprovise.rb +145 -0
  42. data/test/cli_test.rb +314 -0
  43. data/test/cli_test_helper.rb +19 -0
  44. data/test/dsl_test.rb +43 -0
  45. data/test/fixtures/example.txt +1 -0
  46. data/test/fixtures/include.rb +4 -0
  47. data/test/fixtures/inprovise.rb +1 -0
  48. data/test/fixtures/myscheme.rb +1 -0
  49. data/test/infra_test.rb +189 -0
  50. data/test/local_file_test.rb +64 -0
  51. data/test/remote_file_test.rb +106 -0
  52. data/test/resolver_test.rb +66 -0
  53. data/test/script_index_test.rb +53 -0
  54. data/test/script_test.rb +56 -0
  55. data/test/test_helper.rb +237 -0
  56. metadata +182 -0
@@ -0,0 +1,181 @@
1
+ # Linux Command helper for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ Inprovise::CmdHelper.define('linux') do
7
+
8
+ def initialize(channel, sudo=false)
9
+ super(channel)
10
+ @exec = sudo ? :sudo_run : :plain_run
11
+ @cwd = nil
12
+ end
13
+
14
+ # platform properties
15
+
16
+ def admin_user
17
+ 'root'
18
+ end
19
+
20
+ def env_reference(varname)
21
+ "\$#{varname}"
22
+ end
23
+
24
+ def cwd
25
+ @cwd || plain_run('pwd').chomp
26
+ end
27
+
28
+ def set_cwd(path)
29
+ old_cwd = @cwd
30
+ @cwd = path ? File.expand_path(path, self.cwd) : path
31
+ old_cwd
32
+ end
33
+
34
+ # generic command execution
35
+
36
+ def run(cmd, forcelog=false)
37
+ cmd = "cd #{cwd}; #{cmd}" if @cwd
38
+ exec(cmd, forcelog)
39
+ end
40
+
41
+ def sudo
42
+ return self if @exec == :sudo_run
43
+ @sudo ||= self.class.new(@channel, true)
44
+ end
45
+
46
+ # file management
47
+
48
+ def upload(from, to)
49
+ @channel.upload(real_path(from), real_path(to))
50
+ end
51
+
52
+ def download(from, to)
53
+ @channel.download(real_path(from), real_path(to))
54
+ end
55
+
56
+ # basic commands
57
+
58
+ def echo(arg)
59
+ exec("echo #{arg}")
60
+ end
61
+
62
+ def cat(path)
63
+ path = real_path(path)
64
+ begin
65
+ @channel.content(path)
66
+ rescue
67
+ exec("cat #{path}")
68
+ end
69
+ end
70
+
71
+ def hash_for(path)
72
+ path = real_path(path)
73
+ exec("sha1sum #{path}")[0...40]
74
+ end
75
+
76
+ def mkdir(path)
77
+ path = real_path(path)
78
+ exec("mkdir -p #{path}")
79
+ end
80
+
81
+ def exists?(path)
82
+ path = real_path(path)
83
+ begin
84
+ @channel.exists?(path)
85
+ rescue
86
+ exec(%{if [ -f #{path} ]; then echo "true"; else echo "false"; fi}).strip == 'true'
87
+ end
88
+ end
89
+
90
+ def file?(path)
91
+ path = real_path(path)
92
+ begin
93
+ @channel.file?(path)
94
+ rescue
95
+ (exec("stat --format=%f #{path}").chomp.hex & 0x8000) == 0x8000
96
+ end
97
+ end
98
+
99
+ def directory?(path)
100
+ path = real_path(path)
101
+ begin
102
+ @channel.file?(path)
103
+ rescue
104
+ (exec("stat --format=%f #{path}").chomp.hex & 0x4000) == 0x4000
105
+ end
106
+ end
107
+
108
+ def copy(from, to)
109
+ exec("cp #{real_path(from)} #{real_path(to)}")
110
+ end
111
+
112
+ def delete(path)
113
+ path = real_path(path)
114
+ begin
115
+ @channel.delete(path)
116
+ rescue
117
+ exec("rm #{path}")
118
+ end
119
+ end
120
+
121
+ def permissions(path)
122
+ path = real_path(path)
123
+ begin
124
+ @channel.permissions(path)
125
+ rescue
126
+ exec("stat --format=%a #{path}").strip.to_i(8)
127
+ end
128
+ end
129
+
130
+ def set_permissions(path, perm)
131
+ path = real_path(path)
132
+ begin
133
+ @channel.set_permissions(path, perm)
134
+ rescue
135
+ exec("chmod -R #{sprintf("%o",perm)} #{path}")
136
+ end
137
+ end
138
+
139
+ def owner(path)
140
+ path = real_path(path)
141
+ begin
142
+ @channel.owner(path)
143
+ rescue
144
+ user, group = exec("stat --format=%U:%G #{path}").chomp.split(":")
145
+ {:user => user, :group => group}
146
+ end
147
+ end
148
+
149
+ def set_owner(path, user, group=nil)
150
+ path = real_path(path)
151
+ begin
152
+ @channel.set_owner(path, user, group)
153
+ rescue
154
+ exec(%{chown -R #{user}#{group ? ":#{group}" : ''} #{path}})
155
+ end
156
+ end
157
+
158
+ def binary_exists?(bin)
159
+ exec("which #{bin}") =~ /\/#{bin}/ ? true : false
160
+ end
161
+
162
+ private
163
+
164
+ def real_path(path)
165
+ return File.expand_path(path, @cwd) if @cwd
166
+ path
167
+ end
168
+
169
+ def exec(cmd, forcelog=false)
170
+ send(@exec, cmd, forcelog)
171
+ end
172
+
173
+ def plain_run(cmd, forcelog=false)
174
+ @channel.run(cmd, forcelog)
175
+ end
176
+
177
+ def sudo_run(cmd, forcelog=false)
178
+ @channel.run("sudo #{cmd}", forcelog)
179
+ end
180
+
181
+ end
@@ -0,0 +1,123 @@
1
+ # Windows Command helper for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ require 'digest/sha1'
7
+
8
+ Inprovise::CmdHelper.define('windows') do
9
+
10
+ # platform properties
11
+
12
+ def admin_user
13
+ 'administrator'
14
+ end
15
+
16
+ def env_reference(varname)
17
+ "%#{varname}%"
18
+ end
19
+
20
+ # generic command runution
21
+
22
+ def sudo
23
+ # not implemented yet
24
+ return self
25
+ end
26
+
27
+ # basic commands
28
+
29
+ def echo(arg)
30
+ run("echo #{arg}")
31
+ end
32
+
33
+ def cat(path)
34
+ begin
35
+ @channel.content(path)
36
+ rescue
37
+ run("type #{path}")
38
+ end
39
+ end
40
+
41
+ def hash_for(path)
42
+ Digest::SHA1.hexdigest(cat(path))
43
+ end
44
+
45
+ def mkdir(path)
46
+ run("mkdir #{path}") # assumes CMD extensions are enabled
47
+ end
48
+
49
+ def exists?(path)
50
+ begin
51
+ @channel.exists?(path)
52
+ rescue
53
+ run(%{if exist #{path} ] (echo true) else (echo false)}).strip == 'true'
54
+ end
55
+ end
56
+
57
+ def file?(path)
58
+ begin
59
+ @channel.file?(path)
60
+ rescue
61
+ !run("for %p in (#{path}) do echo %~ap-").chomp.start_with?('d')
62
+ end
63
+ end
64
+
65
+ def directory?(path)
66
+ begin
67
+ @channel.file?(path)
68
+ rescue
69
+ run("for %p in (#{path}) do echo %~ap-").chomp.start_with?('d')
70
+ end
71
+ end
72
+
73
+ def copy(from, to)
74
+ run("copy #{from} #{to}")
75
+ end
76
+
77
+ def delete(path)
78
+ begin
79
+ @channel.delete(path)
80
+ rescue
81
+ run("del #{path}")
82
+ end
83
+ end
84
+
85
+ def permissions(path)
86
+ begin
87
+ @channel.permissions(path)
88
+ rescue
89
+ # not implemented yet
90
+ 0
91
+ end
92
+ end
93
+
94
+ def set_permissions(path, perm)
95
+ begin
96
+ @channel.set_permissions(path, perm)
97
+ rescue
98
+ # not implemented yet
99
+ end
100
+ end
101
+
102
+ def owner(path)
103
+ begin
104
+ @channel.owner(path)
105
+ rescue
106
+ # not implemented yet
107
+ {:user => nil, :group => nil}
108
+ end
109
+ end
110
+
111
+ def set_owner(path, user, group=nil)
112
+ begin
113
+ @channel.set_owner(path, user, group)
114
+ rescue
115
+ # not implemented yet
116
+ end
117
+ end
118
+
119
+ def binary_exists?(bin)
120
+ run(%{for %p in (#{bin}) do (if exist "%~$PATH:p" echo %~$PATH:p)}).chomp =~ /#{bin}/ ? true : false
121
+ end
122
+
123
+ end
@@ -0,0 +1,122 @@
1
+ # Infrastructure basics for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ require 'json'
7
+ require 'monitor'
8
+
9
+ module Inprovise::Infrastructure
10
+
11
+ # setup JSON parameters
12
+ JSON.load_default_options[:symbolize_names] = true
13
+ JSON.create_id = :json_class
14
+
15
+ class << self
16
+ def targets
17
+ @targets ||= Hash.new.extend(MonitorMixin)
18
+ end
19
+ private :targets
20
+
21
+ def find(name)
22
+ return name if name.is_a?(Target)
23
+ targets.synchronize do
24
+ return targets[name]
25
+ end
26
+ end
27
+
28
+ def names
29
+ targets.synchronize do
30
+ targets.keys.sort
31
+ end
32
+ end
33
+
34
+ def list(type=Target)
35
+ targets.synchronize do
36
+ targets.values.select {|t| type === t}
37
+ end
38
+ end
39
+
40
+ def register(tgt)
41
+ targets.synchronize do
42
+ raise ArgumentError, "Existing [#{targets[tgt.name].to_s}] found" if targets.has_key?(tgt.name)
43
+ targets[tgt.name] = tgt
44
+ end
45
+ end
46
+
47
+ def deregister(tgt)
48
+ targets.synchronize do
49
+ raise ArgumentError, "Invalid infrastructure target [#{tgt.to_s}]" unless targets.delete(Target === tgt ? tgt.name : tgt.to_s)
50
+ targets.each_value {|t| t.remove_target(tgt) }
51
+ end
52
+ end
53
+
54
+ def save
55
+ targets.synchronize do
56
+ data = []
57
+ targets.each_value {|t| t.is_a?(Node) ? data.insert(0,t) : data.push(t) }
58
+ File.open(Inprovise.infra, 'w') {|f| f << JSON.pretty_generate(data) }
59
+ end
60
+ end
61
+
62
+ def init(path)
63
+ File.open(path, 'w') {|f| f << JSON.pretty_generate([]) }
64
+ end
65
+
66
+ def load
67
+ targets.synchronize do
68
+ JSON.load(IO.read(Inprovise.infra)) if File.readable?(Inprovise.infra)
69
+ end
70
+ end
71
+ end
72
+
73
+ class Target
74
+ attr_reader :name, :config
75
+
76
+ def initialize(name, config = {})
77
+ @name = name
78
+ @config = config
79
+ Inprovise::Infrastructure.register(self)
80
+ end
81
+
82
+ def get(option)
83
+ config[option]
84
+ end
85
+
86
+ def set(option, value)
87
+ config[option.to_sym] = value
88
+ end
89
+
90
+ def add_to(grp)
91
+ grp.add_target(self)
92
+ end
93
+
94
+ def remove_from(grp)
95
+ grp.remove_target(self)
96
+ end
97
+
98
+ def add_target(tgt)
99
+ raise RuntimeError, "Cannot add #{tgt.to_s} to #{self.to_s}"
100
+ end
101
+
102
+ def remove_target(tgt)
103
+ # ignore
104
+ end
105
+
106
+ def includes?(tgt)
107
+ false
108
+ end
109
+
110
+ def targets
111
+ [self]
112
+ end
113
+
114
+ def targets_with_config
115
+ {self => @config.dup}
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ require_relative './node.rb'
122
+ require_relative './group.rb'
@@ -0,0 +1,120 @@
1
+ # LocalFile support for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ require 'digest/sha1'
7
+ require 'fileutils'
8
+ require 'etc'
9
+
10
+ class Inprovise::LocalFile
11
+ attr_reader :path
12
+
13
+ def initialize(path)
14
+ @path = resolve(path)
15
+ end
16
+
17
+ def resolve(path)
18
+ if path =~ /^\//
19
+ path
20
+ else
21
+ File.join(Inprovise.root, path)
22
+ end
23
+ end
24
+
25
+ def hash
26
+ return nil unless exists?
27
+ Digest::SHA1.file(path).hexdigest
28
+ end
29
+
30
+ def exists?
31
+ File.exists?(@path)
32
+ end
33
+
34
+ def directory?
35
+ File.directory?(path)
36
+ end
37
+
38
+ def file?
39
+ File.file?(path)
40
+ end
41
+
42
+ def content
43
+ return File.read(@path) if exists?
44
+ nil
45
+ end
46
+
47
+ # deosnt check permissions or user. should it?
48
+ def matches?(other)
49
+ self.exists? && other.exists? && self.hash == other.hash
50
+ end
51
+
52
+ def copy_to(destination)
53
+ if destination.is_local?
54
+ duplicate(destination)
55
+ else
56
+ upload(destination)
57
+ end
58
+ destination
59
+ end
60
+
61
+ def copy_from(source)
62
+ source.copy_to(self)
63
+ end
64
+
65
+ def duplicate(destination)
66
+ FileUtils.cp(path, destination.path)
67
+ destination
68
+ end
69
+
70
+ def upload(destination)
71
+ destination = @context.remote(destination) if String === destination
72
+ if destination.is_local?
73
+ FileUtils.cp(path, destination.path)
74
+ else
75
+ destination.upload(self)
76
+ end
77
+ destination
78
+ end
79
+
80
+ def download(source)
81
+ source = @context.remote(source) if String === source
82
+ if source.is_local?
83
+ FileUtils.cp(source.path, path)
84
+ else
85
+ source.download(self)
86
+ end
87
+ self
88
+ end
89
+
90
+ def delete!
91
+ FileUtils.rm(path) if exists?
92
+ self
93
+ end
94
+
95
+ def set_permissions(mask)
96
+ FileUtils.chmod_R(mask, path)
97
+ self
98
+ end
99
+
100
+ def permissions
101
+ File.stat(path).mode & 0777
102
+ end
103
+
104
+ def set_owner(user, group=nil)
105
+ FileUtils.chown_R(user, group, path)
106
+ self
107
+ end
108
+
109
+ def user
110
+ Etc.getpwuid(File.stat(path).uid).name
111
+ end
112
+
113
+ def group
114
+ Etc.getgrgid(File.stat(path).gid).name
115
+ end
116
+
117
+ def is_local?
118
+ true
119
+ end
120
+ end
@@ -0,0 +1,79 @@
1
+ # Logger for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ class Inprovise::Logger
7
+ attr_accessor :node
8
+ attr_reader :task
9
+
10
+ def initialize(node, task)
11
+ @node = node
12
+ @nl = true
13
+ set_task(task)
14
+ end
15
+
16
+ def clone_for_node(node)
17
+ copy = self.dup
18
+ copy.node = node
19
+ copy
20
+ end
21
+
22
+ def set_task(task)
23
+ oldtask = @task
24
+ @task = task.to_s
25
+ oldtask
26
+ end
27
+
28
+ def command(msg)
29
+ say(msg, :yellow)
30
+ end
31
+
32
+ def local(cmd)
33
+ say(cmd, :bold)
34
+ end
35
+
36
+ def execute(cmd)
37
+ say(cmd, :cyan)
38
+ end
39
+
40
+ def mock_execute(cmd)
41
+ execute(cmd)
42
+ end
43
+
44
+ def cached(cmd)
45
+ execute(cmd)
46
+ end
47
+
48
+ def remote(cmd)
49
+ say(cmd, :blue)
50
+ end
51
+
52
+ def log(msg)
53
+ say(msg)
54
+ end
55
+
56
+ def print(msg)
57
+ Thread.exclusive do
58
+ $stdout.print "#{@node.to_s} [#{@task.bold}] " if @nl
59
+ $stdout.print msg.sub("\r", "\r".to_eol << "#{@node.to_s} [#{@task.bold}] ")
60
+ end
61
+ @nl = false
62
+ end
63
+
64
+ def stdout(msg, force=false)
65
+ say(msg, :green) if force || Inprovise.verbosity>0
66
+ end
67
+
68
+ def stderr(msg, force=false)
69
+ say(msg, :red, $stderr) if force || Inprovise.verbosity>0
70
+ end
71
+
72
+ def say(msg, color=nil, stream=$stdout)
73
+ msg.to_s.split("\n").each do |line|
74
+ out = color ? line.send(color) : line
75
+ Thread.exclusive { stream.puts unless @nl; stream.puts "#{@node.to_s} [#{@task.bold}] #{out}" }
76
+ @nl = true
77
+ end
78
+ end
79
+ end