inprovise 0.2.2

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