mybot 0.0.4 → 0.1.0

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