mybot 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/Botfile +7 -61
  2. data/CHANGELOG.md +2 -25
  3. data/README.md +72 -189
  4. data/lib/mybot.rb +20 -21
  5. data/lib/mybot/base.rb +6 -2
  6. data/lib/mybot/cli.rb +45 -5
  7. data/lib/mybot/command.rb +37 -28
  8. data/lib/mybot/fmt.rb +143 -52
  9. data/lib/mybot/helpers.rb +24 -0
  10. data/lib/mybot/installer.rb +31 -0
  11. data/lib/mybot/lib.rb +20 -59
  12. data/lib/mybot/libs.rb +31 -28
  13. data/lib/mybot/loader.rb +25 -0
  14. data/lib/mybot/node.rb +100 -47
  15. data/lib/mybot/nodes.rb +16 -0
  16. data/lib/mybot/tasks.rb +47 -11
  17. data/lib/mybot/template.rb +8 -8
  18. data/lib/mybot/templates.rb +15 -0
  19. data/lib/mybot/version.rb +1 -1
  20. data/vendor/lib/{core/.gitkeep → .gitkeep} +0 -0
  21. data/vendor/{recipes → plugins}/.gitkeep +0 -0
  22. data/vendor/{tpl/core → tasks}/.gitkeep +0 -0
  23. data/vendor/tpl/.gitkeep +0 -0
  24. metadata +11 -34
  25. data/lib/mybot/aws.rb +0 -54
  26. data/lib/mybot/commands.rb +0 -108
  27. data/lib/mybot/core-ext/array.rb +0 -9
  28. data/lib/mybot/core-ext/class.rb +0 -53
  29. data/lib/mybot/core-ext/kernel.rb +0 -21
  30. data/lib/mybot/messages.rb +0 -30
  31. data/lib/mybot/recipes.rb +0 -25
  32. data/lib/mybot/result.rb +0 -26
  33. data/lib/mybot/utils.rb +0 -7
  34. data/vendor/lib/core/fs.rb +0 -191
  35. data/vendor/lib/core/osx/git.rb +0 -31
  36. data/vendor/lib/core/osx/rails.rb +0 -31
  37. data/vendor/lib/core/osx/static.rb +0 -31
  38. data/vendor/lib/core/ubuntu/apache.rb +0 -42
  39. data/vendor/lib/core/ubuntu/apt.rb +0 -57
  40. data/vendor/lib/core/ubuntu/git.rb +0 -48
  41. data/vendor/lib/core/ubuntu/mysql.rb +0 -214
  42. data/vendor/lib/core/ubuntu/nginx.rb +0 -43
  43. data/vendor/lib/core/ubuntu/passenger.rb +0 -55
  44. data/vendor/lib/core/ubuntu/php.rb +0 -76
  45. data/vendor/lib/core/ubuntu/rails.rb +0 -105
  46. data/vendor/lib/core/ubuntu/ruby.rb +0 -166
  47. data/vendor/lib/core/ubuntu/service.rb +0 -30
  48. data/vendor/lib/core/ubuntu/static.rb +0 -50
  49. data/vendor/lib/core/ubuntu/users.rb +0 -76
  50. data/vendor/lib/core/ubuntu/vim.rb +0 -31
  51. data/vendor/tpl/core/ubuntu/apache/apache2.conf.erb +0 -80
  52. data/vendor/tpl/core/ubuntu/nginx/nginx.conf.erb +0 -72
  53. data/vendor/tpl/core/ubuntu/php/php.ini.erb +0 -1818
@@ -0,0 +1,31 @@
1
+ require "fileutils"
2
+
3
+ module Mybot
4
+ module Installer
5
+ def install(options = {})
6
+ if File.exists? Mybot::MYBOT_PATH
7
+ error "mybot is already installed"
8
+ end
9
+
10
+ info "installing mybot to #{Mybot::MYBOT_PATH}"
11
+ FileUtils.mkdir Mybot::MYBOT_PATH
12
+ FileUtils.cp_r Mybot::VENDOR_PATH, Mybot::MYBOT_PATH
13
+ ["lib", "plugins", "tasks", "tpl"].each do |dir|
14
+ FileUtils.rm File.join(Mybot::MYBOT_PATH, dir, ".gitkeep")
15
+ end
16
+ end
17
+
18
+ def uninstall(options = {})
19
+ unless File.exists? Mybot::MYBOT_PATH
20
+ error "mybot is not installed"
21
+ end
22
+
23
+ info "uninstalling mybot"
24
+ if yes? "Do you really want to delete ~/.mybot? [y/n]: "
25
+ FileUtils.rm_rf Mybot::MYBOT_PATH
26
+ else
27
+ info "uninstallation aborted"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,70 +1,31 @@
1
1
  module Mybot
2
2
  class Lib
3
- include Messages
4
- include Commands
5
- include Utils
6
-
7
- def method_missing(name, options = {}, &block)
8
- lib_path = lib_to_path(self.class.to_s)
9
-
10
- if name =~ /require\_([a-zA-Z_]*)installed/
11
- base_name = $1.chomp "_"
12
-
13
- methods = []
14
-
15
- if base_name != ""
16
- methods = [
17
- "#{base_name}_installed?",
18
- "#{base_name}_install",
19
- "#{base_name}_reconfigure"
20
- ]
21
- else
22
- methods = ["installed?", "install", "reconfigure"]
23
- end
24
-
25
- methods[0..-2].each do |m|
26
- unless respond_to?(m)
27
- error "lib '#{lib_path}' does not have method '#{m}'"
28
- end
29
- end
30
-
31
- unless send(methods[0], options)
32
- send(methods[1], options)
33
- end
34
-
35
- if respond_to?(methods[2])
36
- send(methods[2], options)
37
- end
38
-
39
- return
40
- elsif name =~ /require\_([a-zA-Z_]*)created/
41
- base_name = $1.chomp "_"
3
+ def initialize(lib)
4
+ @lib = lib
5
+ end
42
6
 
43
- methods = []
7
+ def method_missing(name, *args, &block)
8
+ unless @lib[:funcs][name]
9
+ error "func '#{@lib[:name]}'.'#{name}' is not defined"
10
+ end
44
11
 
45
- if base_name != ""
46
- methods = [
47
- "#{base_name}_exists?",
48
- "#{base_name}_create"
49
- ]
50
- else
51
- methods = ["exists?", "create"]
52
- end
12
+ if args[0].nil? || !args[0].is_a?(Node)
13
+ error "1st arg should be node in func '#{@lib[:name]}'.'#{name}'"
14
+ end
53
15
 
54
- methods.each do |m|
55
- unless respond_to?(m)
56
- error "lib '#{lib_path}' does not have method '#{m}'"
57
- end
58
- end
16
+ if args[1].nil? || !args[1].is_a?(Hash)
17
+ error "2nd arg should be options in func '#{@lib[:name]}'.'#{name}'"
18
+ end
59
19
 
60
- unless send(methods[0], options)
61
- send(methods[1], options)
62
- end
20
+ node = args[0]
21
+ options = args[1]
63
22
 
64
- return
65
- end
23
+ options[:sudo] ||= false
24
+ options[:cwd] ||= ""
25
+ options[:env] ||= {}
26
+ options.merge!(@lib[:options])
66
27
 
67
- error "library '#{lib_path}' does not provide method #{name}"
28
+ return @lib[:funcs][name][:block].call(node, options)
68
29
  end
69
30
  end
70
31
  end
@@ -1,41 +1,44 @@
1
1
  module Mybot
2
2
  module Libs
3
- def use(lib)
4
- if lib.is_a?(Array)
5
- lib.each do |l|
6
- _use(l)
7
- end
8
- else
9
- _use(lib)
10
- end
11
- end
3
+ def libs
4
+ @libs ||= {}
5
+ end
12
6
 
13
- def _use(lib)
14
- lib_name = lib.split("/").last
15
- lib_file = File.join(LIB_PATH, "#{lib}.rb")
16
-
17
- unless File.exists?(lib_file)
18
- error "cannot load lib '#{lib_file}'"
7
+ def library(name, &block)
8
+ @library = name
9
+ unless libs[name]
10
+ libs[name] = {
11
+ :name => name,
12
+ :options => {},
13
+ :funcs => {}
14
+ }
19
15
  end
20
16
 
21
- require lib_file
22
- const = lib.split("/").map { |x| x.capitalize }.join("::")
23
- klass = Kernel.qualified_const_get(const)
17
+ yield
18
+ ensure
19
+ @library = nil
20
+ end
24
21
 
25
- if klass.instance_method(:initialize).arity == 1
26
- @libs[lib_name.to_sym] ||= klass.new(self)
27
- else
28
- warning "#{lib_name.capitalize} library constructor should " +
29
- "take one argument"
22
+ def library_options(options)
23
+ unless @library
24
+ error "library_options should be used only inside library definition"
30
25
  end
26
+
27
+ libs[@library][:options].merge!(options)
31
28
  end
32
29
 
33
- def method_missing(name, *args, &block)
34
- if @libs[name.to_sym]
35
- @libs[name.to_sym]
36
- else
37
- error "no such library loaded: #{name}"
30
+ def func(name, &block)
31
+ libs[@library][:funcs][name] = {
32
+ :block => block
33
+ }
34
+ end
35
+
36
+ def lib(name)
37
+ unless libs[name]
38
+ error "there is no lib '#{name}'"
38
39
  end
40
+
41
+ Lib.new libs[name]
39
42
  end
40
43
  end
41
44
  end
@@ -0,0 +1,25 @@
1
+ module Mybot
2
+ module Loader
3
+ def load_files
4
+ ( Dir[File.join(MYBOT_PATH, "lib", "**", "*.rb")].flatten +
5
+ Dir[File.join(MYBOT_PATH, "plugins", "**", "lib", "**", "*.rb")].flatten +
6
+ Dir["Botfile"] +
7
+ Dir[File.join(".mybot", "**", "*.rb")].flatten +
8
+ Dir[File.join(MYBOT_PATH, "tasks", "**", "*.rb")].flatten +
9
+ Dir[File.join(MYBOT_PATH, "plugins", "**", "tasks", "**", "*.rb")].flatten +
10
+ Dir[File.join(GEM_PATH, "Botfile")]
11
+ ).reject do |f|
12
+ !File.exists?(f)
13
+ end.each do |f|
14
+ self.load_file(f)
15
+ end
16
+ end
17
+
18
+ def load_file(f)
19
+ instance_eval(File.read(f), __FILE__, __LINE__)
20
+ rescue Exception => e
21
+ warning "cannot load #{f}"
22
+ raise(e)
23
+ end
24
+ end
25
+ end
@@ -3,107 +3,160 @@ require "net/sftp"
3
3
 
4
4
  module Mybot
5
5
  class Node
6
- include Messages
7
- include Libs
6
+ include Fmt
8
7
 
9
- attr_accessor :host, :user, :options, :ssh, :sftp, :fmt
8
+ attr_accessor :host, :user, :options
10
9
 
11
10
  def initialize(host, user, options = {})
12
11
  @host, @user, @options = host, user, options
13
- @fmt = Fmt.new(@host, @user)
14
- @libs = {}
15
12
  end
16
13
 
17
14
  def ssh
18
- @ssh ||= Net::SSH.start(@host, @user, @options)
15
+ @ssh ||= Net::SSH.start @host, @user, @options
19
16
  end
20
17
 
21
18
  def sftp
22
- @sftp ||= Net::SFTP.start(@host, @user, @options)
19
+ @sftp ||= Net::SFTP.start @host, @user, @options
23
20
  end
24
21
 
25
- def run(options, &block)
26
- error "cmd not specified" unless options[:cmd]
22
+ def run(cmd, options = {}, &block)
23
+ error "cmd cannot be empty" if cmd == ""
27
24
 
28
- command = Command.new(self)
25
+ start_time = Time.now
26
+ options[:sudo] ||= false
27
+ options[:cwd] ||= ""
28
+ options[:env] ||= {}
29
+
30
+ command = Command.new
29
31
  yield command if block_given?
30
-
31
- ssh.open_channel do |ch|
32
+
33
+ ssh.open_channel do |ch, ok|
32
34
  ch.request_pty do |ch, ok|
33
- @fmt.error "cannot request pty" unless ok
35
+ error "cannot request pty" unless ok
34
36
  end
35
37
 
36
38
  command.channel = ch
37
39
 
38
- inject_env(options)
40
+ run_info cmd, {
41
+ :host => @host,
42
+ :user => @user,
43
+ :sudo => options[:sudo],
44
+ :cwd => options[:cwd],
45
+ :env => options[:env]
46
+ }
39
47
 
40
48
  if options[:sudo]
41
- options[:cmd] = "sudo #{options[:cmd]}"
42
- command.handle_sudo
49
+ cmd = "sudo #{cmd}"
50
+ handle_sudo(command)
43
51
  end
44
52
 
45
- prepend_cwd(options)
46
- prepend_sources(options)
53
+ unless options[:env].empty?
54
+ values = options[:env].map { |k, v| "#{k}=#{v}" }.join " "
55
+ cmd = "#{values} #{cmd}"
56
+ end
47
57
 
48
- @fmt.run(options[:cmd])
58
+ if options[:cwd] != ""
59
+ cmd = "cd #{options[:cwd]} && #{cmd}"
60
+ end
49
61
 
50
- ch.exec options[:cmd] do |ch, ok|
51
- @fmt.error "cannot exec #{options[:cmd]}" unless ok
62
+ ch.exec cmd do |ch, ok|
63
+ error "cannot exec '#{cmd}'" unless ok
52
64
 
53
65
  ch.on_data do |ch, data|
54
- command.handle_stdout(data)
66
+ command.handle_stdout data
55
67
  end
56
68
 
57
69
  ch.on_extended_data do |ch, data|
58
- command.handle_stderr(data)
70
+ command.handle_stderr data
59
71
  end
60
72
 
61
73
  ch.on_request("exit-status") do |ch, data|
62
- command.result.exit_status = data.read_long
74
+ command.exit_status = data.read_long
63
75
  end
64
76
 
65
77
  ch.on_close do |ch|
66
- #
78
+ command.time = Time.now - start_time
79
+ command.handle_close
67
80
  end
68
81
  end
69
82
  end
70
83
 
71
84
  ssh.loop
85
+
72
86
  return command.result
73
87
  end
74
88
 
75
- private
76
-
77
- def inject_env(options)
78
- if options[:env]
79
- env = []
80
- options[:env].each do |k, v|
81
- env << "#{k}='#{v}'"
89
+ def upload(file, options = {})
90
+ error "file '#{file}' does not exists" unless File.exists?(file)
91
+ error "to is required" unless options[:to]
92
+
93
+ start_time = Time.now
94
+ upload_info({
95
+ :host => @host,
96
+ :user => @user,
97
+ :file => file,
98
+ :to => options[:to]
99
+ })
100
+
101
+ sftp.upload!(file, options[:to]) do |event, uploader, *args|
102
+ case event
103
+ when :put
104
+ n = (args[1].to_f * 100 / args[0].size.to_f).to_i
105
+ progress(n)
106
+ when :finish
107
+ progress(100)
82
108
  end
83
- options[:cmd] = "#{env.join(" ")} #{options[:cmd]}"
84
109
  end
110
+
111
+ time_info Time.now - start_time
85
112
  end
86
113
 
87
- def prepend_cwd(options)
88
- if options[:cwd]
89
- options[:cmd] = "cd #{options[:cwd]} && #{options[:cmd]}"
114
+ def download(file, options = {})
115
+ error "to is required" unless options[:to]
116
+
117
+ start_time = Time.now
118
+ download_info({
119
+ :host => @host,
120
+ :user => @user,
121
+ :file => file,
122
+ :to => options[:to]
123
+ })
124
+
125
+ sftp.download!(file, options[:to]) do |event, uploader, *args|
126
+ case event
127
+ when :get
128
+ size = 0
129
+ if args[0].size
130
+ size = args[0].size
131
+ else
132
+ size = sftp.stat!(file).size
133
+ end
134
+ n = (args[1].to_f * 100 / size.to_f).to_i
135
+ progress(n)
136
+ when :finish
137
+ progress(100)
138
+ end
90
139
  end
140
+
141
+ time_info Time.now - start_time
91
142
  end
92
143
 
93
- def prepend_sources(options)
94
- sources = [
95
- File.join("~", ".bashrc"),
96
- File.join("~", ".bash_profile"),
97
- File.join("~", ".profile")
98
- ].map { |f| File.expand_path(f) }
144
+ private
99
145
 
100
- sources.reject! { |f| !File.exists?(f) }
146
+ def handle_sudo(command)
147
+ @options[:password] ||= ""
148
+
149
+ command.on "[sudo] password" do
150
+ command.write @options[:password], :shadow => true
151
+ end
101
152
 
102
- prefix = sources.map do |s|
103
- "source #{File.expand_path(s)}"
104
- end.join(" && ")
153
+ command.on "Password:" do
154
+ command.write @options[:password], :shadow => true
155
+ end
105
156
 
106
- options[:cmd] = "#{prefix} && #{options[:cmd]}"
157
+ command.on "Sorry, try again." do
158
+ error "cannot handle sudo automatically, try manually"
159
+ end
107
160
  end
108
161
  end
109
162
  end
@@ -0,0 +1,16 @@
1
+ module Mybot
2
+ module Nodes
3
+ def node(host, user, options = {})
4
+ Node.new host, user, options
5
+ end
6
+
7
+ def run(*args, &block)
8
+ @nodes ||= []
9
+ error "no node specified" if @nodes.empty?
10
+
11
+ @nodes.each do |node|
12
+ node.run(*args, &block)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,11 +1,49 @@
1
1
  module Mybot
2
2
  module Tasks
3
- include Messages
4
-
5
3
  def tasks
6
4
  @tasks ||= []
7
5
  end
8
6
 
7
+ def namespace(name)
8
+ outer_ns, @namespace = @namespace, name.to_s
9
+ if outer_ns && outer_ns != ""
10
+ @namespace = "#{outer_ns}:#{@namespace}"
11
+ end
12
+
13
+ yield
14
+ ensure
15
+ @namespace = outer_ns
16
+ end
17
+
18
+ def task(name, &block)
19
+ name, deps = *case name
20
+ when Hash
21
+ name.shift
22
+ else
23
+ [name, []]
24
+ end
25
+
26
+ deps = Array(deps).map do |d|
27
+ case d
28
+ when Symbol
29
+ "#{@namespace}:#{d}"
30
+ when String
31
+ d.include?(":") ? d : "#{@namespace}:#{d}"
32
+ end
33
+ end.uniq
34
+
35
+ task = tasks.find do |t|
36
+ t[:name] == name.to_s && t[:namespace] == @namespace
37
+ end
38
+
39
+ tasks.push({
40
+ :name => name.to_s,
41
+ :namespace => @namespace,
42
+ :deps => deps,
43
+ :block => block
44
+ }) unless task
45
+ end
46
+
9
47
  def find_task(name)
10
48
  ns = name.split ":"
11
49
  task = ns.pop
@@ -16,21 +54,19 @@ module Mybot
16
54
  end
17
55
  end
18
56
 
19
- def run_task(name)
20
- task_info name
57
+ def run_task(name, options)
21
58
  task = find_task(name)
22
59
 
23
- unless task
24
- warning "cannot find task '#{name}'"
25
- find_task("mybot:tasks:list")[:block].call
26
- abort
27
- end
60
+ error "cannot find task '#{name}' " +
61
+ "use 'bot tasks:list' to see available tasks" unless task
62
+
63
+ task_info name
28
64
 
29
65
  task[:deps].each do |d|
30
- run_task(d)
66
+ run_task(d, options)
31
67
  end
32
68
 
33
- task[:block].call
69
+ task[:block].call(options)
34
70
  end
35
71
  end
36
72
  end