mybot 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec
data/README.md CHANGED
@@ -1,190 +1,141 @@
1
1
  # mybot
2
2
 
3
- **mybot**, personal bot
3
+ My personal bot
4
4
 
5
5
  ## Installation
6
6
 
7
- ```
8
- gem install mybot
9
- bot install
10
- ```
11
-
12
- **mybot** would be installed to `~/.mybot`
13
-
14
- ## Tasks
7
+ Add this line to your application's Gemfile:
15
8
 
16
- Define task
17
-
18
-
19
- ```ruby
20
- require "mybot"
9
+ gem 'mybot'
21
10
 
22
- namespace :foo do
23
- task :bar do
24
- puts "foo:bar"
25
- end
26
- end
27
- ```
11
+ And then execute:
28
12
 
29
- and run it by typing `bot foo:bar`.
13
+ $ bundle
30
14
 
31
- **mybot** searches for tasks in following places:
15
+ Or install it yourself as:
32
16
 
33
- * `./Botfile`
34
- * `.mybot/*.rb`
35
- * `~/.mybot/tasks`
17
+ $ gem install mybot
36
18
 
37
- To list all available tasks type `bot tasks:list`.
19
+ ## Usage
38
20
 
39
- ## Nodes
21
+ ### Tasks
40
22
 
41
- **mybot** operates on nodes which are practically computers you can SSH into. For the most part you will be executing commands using `node.run`:
23
+ It's all about executing tasks. **mybot** will search for recipes in `~/.mybot/*.rb` and `Botfile` in current directory. Syntax is similar to **rake** and **capistrano**
42
24
 
43
25
  ```ruby
44
26
  require "mybot"
45
27
 
46
- srv = node "1.2.3.4", "user", :password => "123"
47
-
48
28
  namespace :foo do
49
- task :bar do
50
- srv.run "uname -a"
29
+ task :baz do
30
+ puts "foo:baz"
31
+ end
32
+
33
+ task :bar => :baz do |options|
34
+ puts options.inspect
51
35
  end
52
36
  end
53
37
  ```
54
38
 
55
- It will execute your command on specified node (mine was Ubuntu Server).
56
- The output will be more or less:
39
+ To run task, use `bot` command:
57
40
 
58
41
  ```
59
- $ bot foo:bar
60
- TASK foo
61
- [user@1.2.3.4] RUN sudo pwd
62
- EXIT 0
63
- TIME 0.353989s
42
+ % bot foo:bar --no-foo -bar -baz=12
43
+ task foo:bar
44
+ task foo:baz
45
+ foo:baz
46
+ {:foo=>false, :bar=>true, :baz=>12}
64
47
  ```
65
48
 
66
- You can also use SSH keys:
49
+ ### Nodes & interactive commands
50
+
51
+ **mybot** operates on nodes which are in fact computers you can ssh into. You can run commands and interactively handle it's output:
67
52
 
68
53
  ```ruby
69
- srv = node "1.2.3.4", "user", :keys => ["/path/to/key"]
70
- ```
71
-
72
- ## Interaction
54
+ require "mybot"
73
55
 
74
- Executed commands can ask for your input and you can handle it with `on`:
56
+ server = node "1.2.3.4", "user", :password => "123"
75
57
 
76
- ```ruby
77
58
  task :foo do
78
- node.run "sudo pwd" do |cmd|
79
- cmd.on "[sudo] password" do
80
- cmd.write "123"
59
+ wait "Press any key to continue..."
60
+ options = {
61
+ :sudo => true,
62
+ :cwd => "/home",
63
+ :env => {
64
+ "PARAM" => "value"
65
+ }
66
+ }
67
+ server.run "uname -a", options do |cmd|
68
+ cmd.on "Password:" do
69
+ cmd.write! ask "Type password: "
81
70
  end
82
71
  end
83
- end
84
- ```
85
-
86
- Here's the output:
87
-
88
- ```
89
- TASK foo
90
- [user@1.2.3.4] RUN sudo pwd
91
- HANDLE [sudo] password
92
- WRITE 123<LF>
93
- EXIT 0
94
- TIME 0.353989s
95
- ```
96
-
97
- ### Handling sudo
98
-
99
- Handling sudo is such repetitive task that you can try to handle it automatically by **mybot**:
100
-
101
- ```ruby
102
- task :foo do
103
- node.run "pwd", :sudo => true
104
- end
105
- ```
106
-
107
- It would try to use password provided with node declaration and will fail when the password doesn't work asking you to handle sudo manually. Note that you don't have to prepend command with **sudo** when using `:sudo => true`.
108
-
109
- ## Templates
110
-
111
- ```ruby
112
- mytpl = tpl "param: <%= param %>"
113
72
 
114
- task :foo do
115
- puts mytpl.render({ param: "value" })
73
+ if yes? "Is that all (y/n): "
74
+ puts "we're done!"
75
+ else
76
+ puts "wait a minute!"
77
+ end
116
78
  end
117
79
  ```
118
80
 
119
- In fact you don't want to clutter your recipes with template strings so better use template files:
81
+ and you should see:
120
82
 
121
- ```ruby
122
- mytpl = tpl :file => "path/to/tpl"
123
83
  ```
124
-
125
- **mybot** will look for templates in `~/.mybot/tpl`
126
-
127
- ## Download and upload files
128
-
129
- ```ruby
130
- task :foo do
131
- node.upload "/Users/seba/Downloads/test.zip",
132
- :to => "/home/user/test.zip"
133
-
134
- node.download "/home/user/test.zip",
135
- :to => "/Users/seba/Downloads/t.zip"
136
- end
84
+ % bot foo:bar
85
+ task foo
86
+ wait Press any key to continue...
87
+ run cd /home && sudo PARAM='value' uname -a
88
+ handle Password:
89
+ ask Type password:
90
+ write ***
91
+ time 0.151334
92
+ exit 0
93
+ ask Is that all (y/n):
94
+ we're done!
137
95
  ```
138
96
 
139
- There's always *progress bar* so you know if you have time for coffee :)
97
+ Note that bang version of `write` would replace sent data with asterisks.
140
98
 
141
- ## CLI helpers
99
+ ### File operations & templates
142
100
 
143
- * `wait` will display *Press any key to continue...* message and will wait for any user interaction
144
- * `ask` will display question passed as argument and return the answer
145
- * `yes?` will display question too but it should return true or false based on user's answer. Only `yes | Yes | y` and similar answers are acceptable. When boolean value cannot be guessed the question is asked again.
146
-
147
- ## Libs
148
-
149
- You can group node operations into lib
101
+ Couple of most common file operations are available.
150
102
 
151
103
  ```ruby
152
104
  require "mybot"
153
105
 
154
- library "ubuntu:apt" do
155
- library_options({
156
- :env => {
157
- "DEBIAN_FRONTEND" => "noninteractive"
158
- }
159
- })
160
-
161
- func :update do |node, options|
162
- node.run "apt-get update", options do |cmd|
163
- cmd.on "dpkg was interrupted" do
164
- error "Cannot update"
165
- end
166
-
167
- cmd.on "Could not get lock" do
168
- error "Cannot update"
169
- end
170
- end
171
- end
106
+ server = node "1.2.3.4", "user", :password => "123"
107
+ file_tpl = tpl :file => "/path/to/tpl"
108
+
109
+ namespace :server do
110
+ task :foo do
111
+ unless server.exists? "/path/to/file"
112
+ server.create "/path/to/file", :content => file_tpl.render({
113
+ :param => "value",
114
+ :param => "value"
115
+ })
116
+ end
117
+ end
172
118
  end
119
+
173
120
  ```
174
121
 
175
- and use them in this way
122
+ ### Upload & download
176
123
 
177
124
  ```ruby
178
125
  require "mybot"
179
126
 
180
- apt = lib "ubuntu:apt"
127
+ server = node "1.2.3.4", "user", :password => "123"
181
128
 
182
- apt.update node, {}
129
+ task :foo do
130
+ server.upload "/Users/user/from.txt", "/home/user/to.txt"
131
+ server.download "/home/user/to.txt", "/Users/user/file.txt"
132
+ end
183
133
  ```
184
134
 
185
- ## Uninstallation
135
+ ## Contributing
186
136
 
187
- ```
188
- bot uninstall
189
- gem uninstall -Iax mybot
190
- ```
137
+ 1. Fork it
138
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
139
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
140
+ 4. Push to the branch (`git push origin my-new-feature`)
141
+ 5. Create new Pull Request
@@ -1,28 +1,21 @@
1
- $USE_COLOR = true
2
- $SHOW_CMD = true
3
- $LIVE_STDOUT = false
4
- $LIVE_STDERR = false
5
- $LIVE_STDIN = false
6
-
7
1
  module Mybot
8
- autoload :Base, "mybot/base"
9
- autoload :Cli, "mybot/cli"
10
- autoload :Command, "mybot/command"
11
- autoload :Fmt, "mybot/fmt"
12
- autoload :Helpers, "mybot/helpers"
13
- autoload :Installer, "mybot/installer"
14
- autoload :Lib, "mybot/lib"
15
- autoload :Libs, "mybot/libs"
16
- autoload :Loader, "mybot/loader"
17
- autoload :Node, "mybot/node"
18
- autoload :Nodes, "mybot/nodes"
19
- autoload :Tasks, "mybot/tasks"
20
- autoload :Template, "mybot/template"
21
- autoload :Templates, "mybot/templates"
22
- autoload :VERSION, "mybot/version"
23
-
24
- GEM_PATH = File.dirname(File.dirname(__FILE__))
25
- VENDOR_PATH = File.join(GEM_PATH, "vendor", ".")
2
+ GEM_PATH = File.dirname(File.dirname(__FILE__))
26
3
  HOME_PATH = File.expand_path "~"
27
- MYBOT_PATH = File.join HOME_PATH, ".mybot"
4
+
5
+ autoload :Base, "mybot/base"
6
+ autoload :Cli, "mybot/cli"
7
+ autoload :Command, "mybot/command"
8
+ autoload :Fmt, "mybot/fmt"
9
+ autoload :Helpers, "mybot/helpers"
10
+ autoload :Node, "mybot/node"
11
+ autoload :Nodes, "mybot/nodes"
12
+ autoload :Recipes, "mybot/recipes"
13
+ autoload :Tasks, "mybot/tasks"
14
+ autoload :Template, "mybot/template"
15
+ autoload :Templates, "mybot/templates"
16
+ autoload :VERSION, "mybot/version"
28
17
  end
18
+
19
+ $MYBOT_USE_COLORS = true
20
+ $MYBOT_SHOW_CMD = true
21
+ $MYBOT_SHOW_OUTPUT = false
@@ -1,12 +1,9 @@
1
1
  module Mybot
2
- module Base
3
- include Fmt
4
- include Helpers
5
- include Libs
6
- include Loader
7
- include Nodes
8
- include Tasks
9
- include Templates
10
- extend self
11
- end
2
+ module Base
3
+ include Helpers
4
+ include Nodes
5
+ include Recipes
6
+ include Tasks
7
+ extend self
8
+ end
12
9
  end
@@ -1,26 +1,22 @@
1
1
  module Mybot
2
- module Cli
3
- include Fmt
4
- include Helpers
5
- include Installer
6
- extend self
2
+ module Cli
3
+ include Fmt
4
+ extend self
7
5
 
8
- def start(args)
9
- cmd = args.shift
10
- options = parse(args)
6
+ def start(args)
7
+ cmd = args.shift
8
+ options = parse_args(args)
11
9
 
12
- error "usage: bot [install|uninstall|<task>] <options>" unless cmd
13
-
14
- case cmd
15
- when /(install|uninstall)/
16
- self.send($1, options)
17
- else
18
- Base.load_files
19
- Base.run_task cmd, options
10
+ unless cmd
11
+ print_cmd! "error", "usage: bot <task> [<options>]", :red, :bold
12
+ abort
20
13
  end
21
- end
22
14
 
23
- def parse(args)
15
+ Base.load_recipes
16
+ Base.run_task cmd, options
17
+ end
18
+
19
+ def parse_args(args)
24
20
  options = {}
25
21
 
26
22
  args.each_with_index do |arg, i|
@@ -52,5 +48,5 @@ module Mybot
52
48
  end
53
49
  end
54
50
  end
55
- end
51
+ end
56
52
  end
@@ -1,67 +1,80 @@
1
1
  module Mybot
2
- class Command
3
- include Fmt
2
+ class Command
3
+ include Fmt
4
4
 
5
- attr_accessor :channel, :exit_status, :time
5
+ attr_accessor :channel, :exit
6
6
 
7
- def initialize
8
- @out = @stdout = @stderr = ""
9
- @handlers = {}
10
- @exit_status = -1
11
- @time = 0
12
- end
7
+ def initialize
8
+ @out = @stdout = @stderr = ""
9
+ @handlers = {}
10
+ @time = Time.now
11
+ @exit = -1
12
+ end
13
13
 
14
- def on(s, &block)
15
- @handlers[s] = block
16
- end
14
+ def on(s, &block)
15
+ @handlers[s] = block
16
+ end
17
17
 
18
- def handle_stdout(data)
19
- @out = data
20
- @stdout += data
21
- stdout_live(data)
22
- @handlers.each do |s, proc|
23
- if @out.include? s
24
- handle_info s, :type => :stdout
25
- proc.call
26
- end
27
- end
28
- end
18
+ def handle_stdout(data)
19
+ @out = data
20
+ @stdout += data
21
+ print_stdout data
22
+ run_handlers
23
+ end
29
24
 
30
- def handle_stderr(data)
31
- @out = data
32
- @stderr += data
33
- stderr_live(data)
34
- @handlers.each do |s, proc|
35
- if @out.include? s
36
- handle_info s, :type => :stderr
37
- proc.call
38
- end
39
- end
40
- end
25
+ def handle_stderr(data)
26
+ @out = data
27
+ @stderr += data
28
+ print_stderr data
29
+ run_handlers
30
+ end
41
31
 
42
- def handle_close
43
- status_info result
44
- error "command exited with error" unless result[:exit_status] == 0
45
- end
32
+ def handle_close
33
+ @time = Time.now - @time
34
+ print_cmd! "time", @time, :blue, :bold
35
+ print_cmd! "exit", @exit, :blue, :bold
46
36
 
47
- def write(data, options = {})
48
- write_info data, options
49
- @channel.send_data "#{data}\n"
50
- end
37
+ unless @exit == 0
38
+ print_cmd! "error", "invalid exit status", :red, :bold
39
+ print_stdout @stdout, true
40
+ print_stderr @stderr, true
41
+ abort
42
+ end
43
+ end
51
44
 
52
- def result
53
- {
54
- :stdout => filter(@stdout),
55
- :stderr => filter(@stderr),
56
- :exit_status => @exit_status,
57
- :time => @time
58
- }
59
- end
45
+ def write(data)
46
+ print_cmd! "write", data, :blue, :bold
47
+ @channel.send_data "#{data}\n"
48
+ end
49
+
50
+ def write!(data)
51
+ print_cmd! "write", asterisks(data), :blue, :bold
52
+ @channel.send_data "#{data}\n"
53
+ end
54
+
55
+ def result
56
+ {
57
+ :stdout => filter(@stdout),
58
+ :stderr => filter(@stderr),
59
+ :exit => @exit,
60
+ :time => @time
61
+ }
62
+ end
63
+
64
+ private
60
65
 
61
- private
66
+ def run_handlers
67
+ @handlers.each do |s, block|
68
+ if @out.include? s
69
+ puts if @out[-3..-1] == "\r\n" && $MYBOT_SHOW_OUTPUT
70
+ print_cmd! "handle", s, :blue, :bold
71
+ block.call
72
+ end
73
+ end
74
+ end
62
75
 
63
- def filter(data)
76
+ def filter(data)
64
77
  data.gsub(/\[sudo\]\ password\ for\ [a-zA-Z0-9_].+\:/, "")
65
78
  end
66
- end
79
+ end
67
80
  end