grifork 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/.yardopts +4 -0
- data/CHANGELOG.md +18 -0
- data/README.md +29 -2
- data/example/Griforkfile.grifork +20 -3
- data/example/Griforkfile.standalone +20 -3
- data/lib/grifork/cli.rb +9 -1
- data/lib/grifork/config.rb +76 -1
- data/lib/grifork/dsl.rb +63 -1
- data/lib/grifork/executor/grifork.rb +15 -13
- data/lib/grifork/executor/task.rb +28 -3
- data/lib/grifork/graph.rb +8 -4
- data/lib/grifork/mixin/executable.rb +40 -15
- data/lib/grifork/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 051978666d0eb719ebe0d1fb7396767f2849b60b
|
4
|
+
data.tar.gz: d1db36aff5d670f124ab13dadfa8288347ed1998
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 728ccefbb4558dc6caccd3a8a1504a52ad19fd8627d993046a82003545d0ee812cd66285088b96a858013eac16db4b25257207eadf1d6a94d7fd49b735be692d
|
7
|
+
data.tar.gz: b61507b73b372ba673b1d4eb9a78c85efce6c49d7a4b1534723392ceca9e80b9274c67b926490c044849f91a6753f03dda902b6b1914440a44ce7ae518f1bd57
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## 0.4.0 (2016/10/9)
|
2
|
+
|
3
|
+
Bug Fix: (#3)
|
4
|
+
|
5
|
+
- `Grifork::Executor::Task#run` was not thread-safe
|
6
|
+
|
7
|
+
Features: (#3)
|
8
|
+
|
9
|
+
- New DSL methods:
|
10
|
+
- `#parallel` as option for [Parallel.map](https://github.com/grosser/parallel)
|
11
|
+
- `#ssh` as options for [Net::SSH](https://github.com/net-ssh/net-ssh)
|
12
|
+
- `#rsync` as options for `rsync` command
|
13
|
+
- New CLI option `-n|--dry-run`
|
14
|
+
|
15
|
+
Improve: (#3)
|
16
|
+
|
17
|
+
- Mode `:grifork` / Not to fork `grifork` task on node with no children
|
18
|
+
|
1
19
|
## 0.3.0 (2016/10/4)
|
2
20
|
|
3
21
|
Release as a RubyGem.
|
data/README.md
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
[](https://badge.fury.io/rb/grifork)
|
1
2
|
[](https://travis-ci.org/key-amb/grifork)
|
2
3
|
|
3
4
|
# grifork
|
@@ -63,10 +64,36 @@ bundle
|
|
63
64
|
|
64
65
|
```sh
|
65
66
|
edit Griforkfile
|
66
|
-
./bin/grifork [--[f]ile path/to/Griforkfile]
|
67
|
+
./bin/grifork [--[f]ile path/to/Griforkfile] [-n|--dry-run]
|
67
68
|
```
|
68
69
|
|
69
|
-
|
70
|
+
## Griforkfile
|
71
|
+
|
72
|
+
**Griforkfile** is DSL file for _grifork_ which configures and defines the tasks
|
73
|
+
to be executed by _grifork_.
|
74
|
+
|
75
|
+
Here is a small example:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
branches 4
|
79
|
+
log file: 'grifork.log'
|
80
|
+
hosts ['web1.internal', 'web2.internal', 'db1.internal', 'db2.internal', ...]
|
81
|
+
|
82
|
+
local do
|
83
|
+
rsync '/path/to/myapp/'
|
84
|
+
end
|
85
|
+
|
86
|
+
remote do
|
87
|
+
rsync_remote '/path/to/myapp/'
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
If you run `grifork` with this _Griforkfile_, it just syncs `/path/to/myapp/` in
|
92
|
+
localhost to target `hosts` by `rsync` command.
|
93
|
+
|
94
|
+
See [example](https://github.com/key-amb/grifork/tree/master/example) directory for more examples of _Griforkfile_.
|
95
|
+
|
96
|
+
And refer to [Grifork::DSL](http://www.rubydoc.info/gems/grifork/Grifork/DSL) as API document of _Griforkfile_.
|
70
97
|
|
71
98
|
# Authors
|
72
99
|
|
data/example/Griforkfile.grifork
CHANGED
@@ -1,12 +1,24 @@
|
|
1
|
+
# Example of Grifork DSL for :grifok mode
|
2
|
+
# See Grifork::DSL for more informaion about DSL format
|
3
|
+
|
1
4
|
# Configuration
|
2
5
|
#mode :grifork
|
3
6
|
branches 4
|
4
7
|
log file: 'tmp/grifork.log', level: 'debug'
|
5
8
|
|
9
|
+
# Parallel mode for "parallel"
|
10
|
+
# Defaults to :in_threads
|
11
|
+
#parallel :in_processes
|
12
|
+
|
13
|
+
# Configure ssh options for #ssh & #grifork
|
14
|
+
# With private key auth:
|
15
|
+
ssh user: 'someone', keys: ['path/to/identity_file'], passphrase: 'xxx'
|
16
|
+
# With password auth:
|
17
|
+
#ssh user: 'someone', password: 'xxx'
|
18
|
+
|
6
19
|
# Setting to exec grifork on remote
|
7
20
|
# Implies to set mode as :grifork
|
8
21
|
grifork do
|
9
|
-
user 'someone' # ssh user to exec grifork on remote
|
10
22
|
chdir '/path/to/your-app'
|
11
23
|
tmpdir '/path/to/tmpdir'
|
12
24
|
exec '/path/to/grifork'
|
@@ -15,10 +27,15 @@ end
|
|
15
27
|
# Define hosts as array
|
16
28
|
hosts ['web1.internal', 'web2.internal', '192.168.10.1', '192.168.10.2']
|
17
29
|
|
30
|
+
# Configure rsync options
|
31
|
+
# Defaults to ['-az', '--delete']
|
32
|
+
# Available full Hash options are bellow:
|
33
|
+
rsync delete: true, bwlimit: 4096, verbose: false, excludes: [], rsh: nil, dry_run: false
|
34
|
+
|
18
35
|
# Define task run on localhost
|
19
36
|
local do
|
20
37
|
sh :echo, %W(LOCAL: #{src} => #{dst})
|
21
|
-
ssh dst, :mkdir, %W(-p /path/to/dest)
|
38
|
+
ssh dst, :mkdir, %W(-p /path/to/dest)
|
22
39
|
rsync '/path/to/src/', '/path/to/dest/'
|
23
40
|
end
|
24
41
|
|
@@ -27,7 +44,7 @@ end
|
|
27
44
|
# different from "remote" task in :standalone mode
|
28
45
|
remote do
|
29
46
|
sh :echo, %W(REMOTE: #{src} => #{dst})
|
30
|
-
ssh dst, :mkdir, %W(-p /path/to/dest)
|
47
|
+
ssh dst, :mkdir, %W(-p /path/to/dest)
|
31
48
|
rsync '/path/to/src/', '/path/to/dest/'
|
32
49
|
end
|
33
50
|
|
@@ -1,15 +1,32 @@
|
|
1
|
-
#
|
1
|
+
# Example of Grifork DSL for :standalone mode
|
2
|
+
# See Grifork::DSL for more informaion about DSL format
|
3
|
+
|
2
4
|
#mode :standalone # Default mode
|
3
5
|
branches 4
|
4
6
|
log file: 'tmp/grifork.log', level: 'debug'
|
5
7
|
|
8
|
+
# Parallel mode for "parallel"
|
9
|
+
# Defaults to :in_threads
|
10
|
+
#parallel :in_processes
|
11
|
+
|
6
12
|
# Define hosts as array
|
7
13
|
hosts ['web1.internal', 'web2.internal', '192.168.10.1', '192.168.10.2']
|
8
14
|
|
15
|
+
# Configure ssh options for #ssh
|
16
|
+
# With private key auth:
|
17
|
+
ssh user: 'someone', keys: ['path/to/identity_file'], passphrase: 'xxx'
|
18
|
+
# With password auth:
|
19
|
+
#ssh user: 'someone', password: 'xxx'
|
20
|
+
|
21
|
+
# Configure rsync options
|
22
|
+
# Defaults to ['-az', '--delete']
|
23
|
+
# Available full Hash options are bellow:
|
24
|
+
rsync delete: true, bwlimit: 4096, verbose: false, excludes: [], rsh: nil, dry_run: false
|
25
|
+
|
9
26
|
# Define task run on localhost
|
10
27
|
local do
|
11
28
|
sh :echo, %W(LOCAL: #{src} => #{dst})
|
12
|
-
ssh dst, :mkdir, %W(-p /path/to/dest)
|
29
|
+
ssh dst, :mkdir, %W(-p /path/to/dest)
|
13
30
|
rsync '/path/to/src/', '/path/to/dest/'
|
14
31
|
end
|
15
32
|
|
@@ -17,6 +34,6 @@ end
|
|
17
34
|
remote do
|
18
35
|
sh :echo, %W(REMOTE: #{src} => #{dst})
|
19
36
|
ssh dst, :mkdir, %W(-p /path/to/dest)
|
20
|
-
rsync_remote '/path/to/src/', '/path/to/dest/'
|
37
|
+
rsync_remote '/path/to/src/', '/path/to/dest/'
|
21
38
|
end
|
22
39
|
|
data/lib/grifork/cli.rb
CHANGED
@@ -4,6 +4,7 @@ class Grifork::CLI
|
|
4
4
|
opt.on('-f', '--file Griforkfile') { |f| @taskfile = f }
|
5
5
|
opt.on('-o', '--override-by FILE') { |f| @override_file = f }
|
6
6
|
opt.on('-r', '--on-remote') { @on_remote = true }
|
7
|
+
opt.on('-n', '--dry-run') { @dry_run = true }
|
7
8
|
opt.on('-v', '--version') { @version = true }
|
8
9
|
opt.parse!(argv)
|
9
10
|
end
|
@@ -12,11 +13,17 @@ class Grifork::CLI
|
|
12
13
|
exit
|
13
14
|
end
|
14
15
|
|
15
|
-
config = load_taskfiles
|
16
|
+
config = load_taskfiles
|
17
|
+
if @dry_run
|
18
|
+
config.dry_run = true
|
19
|
+
end
|
20
|
+
config.freeze
|
16
21
|
Grifork.configure!(config)
|
22
|
+
logger = Grifork.logger
|
17
23
|
|
18
24
|
graph = Grifork::Graph.new(config.hosts)
|
19
25
|
|
26
|
+
logger.info("START | mode: #{config.mode}")
|
20
27
|
if @on_remote
|
21
28
|
puts "Start on remote. Hosts: #{config.hosts}"
|
22
29
|
end
|
@@ -31,6 +38,7 @@ class Grifork::CLI
|
|
31
38
|
raise "Unexpected mode! #{config.mode}"
|
32
39
|
end
|
33
40
|
|
41
|
+
logger.info("END | mode: #{config.mode}")
|
34
42
|
if @on_remote
|
35
43
|
puts "End on remote."
|
36
44
|
end
|
data/lib/grifork/config.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class Grifork::Config
|
2
2
|
attr_reader :branches, :hosts, :log, :local_task, :remote_task, :grifork
|
3
|
-
attr_accessor :griforkfile
|
3
|
+
attr_accessor :griforkfile, :dry_run
|
4
4
|
|
5
5
|
def initialize(args)
|
6
6
|
args.each do |key, val|
|
@@ -12,6 +12,31 @@ class Grifork::Config
|
|
12
12
|
@mode || :standalone
|
13
13
|
end
|
14
14
|
|
15
|
+
def parallel
|
16
|
+
@parallel || :in_threads
|
17
|
+
end
|
18
|
+
|
19
|
+
def dry_run?
|
20
|
+
@dry_run ? true : false
|
21
|
+
end
|
22
|
+
|
23
|
+
def ssh
|
24
|
+
@ssh || SSH.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# Build +rsync+ command-line options from +@rsync+ and +@ssh+ objects
|
28
|
+
# @return [Array<String>] +rsync+ command options
|
29
|
+
# @see Rsync
|
30
|
+
# @see SSH
|
31
|
+
def rsync_opts
|
32
|
+
@rsync ||= Rsync.new
|
33
|
+
opts = @rsync.options
|
34
|
+
if opts.grep(/^(-e|--rsh)$/).size.zero?
|
35
|
+
opts.concat(['--rsh', ssh.command_for_rsync])
|
36
|
+
end
|
37
|
+
opts
|
38
|
+
end
|
39
|
+
|
15
40
|
class Log
|
16
41
|
attr :file, :level
|
17
42
|
|
@@ -21,6 +46,56 @@ class Grifork::Config
|
|
21
46
|
end
|
22
47
|
end
|
23
48
|
|
49
|
+
class SSH
|
50
|
+
attr :options
|
51
|
+
def initialize(opts = {})
|
52
|
+
@options = opts
|
53
|
+
end
|
54
|
+
|
55
|
+
# Build +ssh+ command with options
|
56
|
+
# @return [String] +ssh+ command with options
|
57
|
+
def command_for_rsync
|
58
|
+
args = []
|
59
|
+
args << "-l #{@options[:user]}" if @options[:user]
|
60
|
+
args << "-p #{@options[:port]}" if @options[:port]
|
61
|
+
if @options[:keys]
|
62
|
+
@options[:keys].each { |key| args << "-i #{key}" }
|
63
|
+
end
|
64
|
+
"ssh #{args.join(' ')}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Rsync
|
69
|
+
def initialize(args = %w(-az --delete))
|
70
|
+
case args
|
71
|
+
when Array
|
72
|
+
@options = args
|
73
|
+
when Hash
|
74
|
+
@delete = args[:delete] || true
|
75
|
+
@verbose = args[:verbose] || false
|
76
|
+
@bwlimit = args[:bwlimit] || nil
|
77
|
+
@excludes = args[:excludes] || []
|
78
|
+
@rsh = args[:rsh] || nil
|
79
|
+
@dry_run = args[:dry_run] || false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def options
|
84
|
+
@options ||= -> {
|
85
|
+
opts = []
|
86
|
+
opts << ( @verbose ? '-avz' : '-az' )
|
87
|
+
opts << '--dry-run' if @dry_run
|
88
|
+
opts << '--delete' if @delete
|
89
|
+
opts.concat(['--rsh', @rsh]) if @rsh
|
90
|
+
opts << "--bwlimit=#{@bwlimit}" if @bwlimit
|
91
|
+
if @excludes.size > 0
|
92
|
+
@excludes.each { |ex| opts << "--exclude=#{ex}" }
|
93
|
+
end
|
94
|
+
opts
|
95
|
+
}.call
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
24
99
|
class Grifork
|
25
100
|
attr :dir, :cmd, :login
|
26
101
|
|
data/lib/grifork/dsl.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
# DSL parser for *Griforkfile*
|
2
|
+
#
|
3
|
+
# _Griforkfile_ is interpreted in instance context by an object of this Class.
|
1
4
|
class Grifork::DSL
|
2
5
|
attr :config
|
3
6
|
|
4
7
|
class LoadError < StandardError; end
|
5
8
|
|
6
9
|
# Load DSL file to object
|
7
|
-
# @
|
10
|
+
# @return [Grifork::DSL]
|
11
|
+
# @param path [String] path of DSL file
|
8
12
|
# @param on_remote [Boolean] whether process is invoked by remote host in :grifork mode or not
|
9
13
|
def self.load_file(path, on_remote: false)
|
10
14
|
content = File.binread(path)
|
@@ -18,6 +22,8 @@ class Grifork::DSL
|
|
18
22
|
@on_remote = on_remote
|
19
23
|
end
|
20
24
|
|
25
|
+
# Creates {Grifork::Config} object from holding properties
|
26
|
+
# @return [Grifork::Config]
|
21
27
|
def to_config
|
22
28
|
Grifork::Config.new(@config)
|
23
29
|
end
|
@@ -30,6 +36,8 @@ class Grifork::DSL
|
|
30
36
|
@config.merge!(other.config)
|
31
37
|
end
|
32
38
|
|
39
|
+
# Grifork mode: How it works
|
40
|
+
# @param m [Symbol] +:standalone+ or +:grifork+. Defaults to +:standalone+
|
33
41
|
def mode(m)
|
34
42
|
unless Grifork::MODES.has_key?(m)
|
35
43
|
raise LoadError, "Undefined mode! #{m}"
|
@@ -37,6 +45,9 @@ class Grifork::DSL
|
|
37
45
|
config_set(:mode, m)
|
38
46
|
end
|
39
47
|
|
48
|
+
# Configure grifork settings for +:grifork+ mode
|
49
|
+
# @param &command [Proc]
|
50
|
+
# @see Grifork::Config::Grifork.initialize
|
40
51
|
def grifork(&command)
|
41
52
|
if @config[:mode] == :standalone
|
42
53
|
raise LoadError, "Can't configure grifork in standalone mode"
|
@@ -45,23 +56,74 @@ class Grifork::DSL
|
|
45
56
|
config_set(:grifork, Grifork::Config::Grifork.new(&command))
|
46
57
|
end
|
47
58
|
|
59
|
+
# Branches number for tree of host nodes
|
48
60
|
def branches(num)
|
49
61
|
config_set(:branches, num)
|
50
62
|
end
|
51
63
|
|
64
|
+
# Configure logging
|
65
|
+
# @param args [Hash]
|
66
|
+
# @see Grifork::Config::Log.initialize
|
52
67
|
def log(args)
|
53
68
|
config_set(:log, Grifork::Config::Log.new(args))
|
54
69
|
end
|
55
70
|
|
71
|
+
# Forking method to exec tasks in parallel.
|
72
|
+
# @param how [:Symbol] +:in_threads+ or +:in_processes+. Defaults to +:in_threads+
|
73
|
+
# @see https://github.com/grosser/parallel
|
74
|
+
def parallel(how)
|
75
|
+
unless %i(in_threads in_processes).include?(how)
|
76
|
+
raise LoadError, "Invalid parallel mode! #{how.inspect} / must be :in_threads or :in_processes"
|
77
|
+
end
|
78
|
+
config_set(:parallel, how)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Host list as targets of tasks
|
82
|
+
# @param hosts [Array<String>] List of resolvable hostnames
|
56
83
|
def hosts(list)
|
57
84
|
config_set(:hosts, list)
|
58
85
|
end
|
59
86
|
|
87
|
+
# Configure net-ssh options
|
88
|
+
# @params props [Hash] Options for Net::SSH
|
89
|
+
# @example
|
90
|
+
# # Password authentication
|
91
|
+
# ssh user: 'someone', password: 'xxx'
|
92
|
+
# # Private key authentication
|
93
|
+
# ssh user: 'someone', keys: ['path/to/priv_key'], passphrase: 'xxx'
|
94
|
+
# @see https://github.com/net-ssh/net-ssh
|
95
|
+
def ssh(props)
|
96
|
+
invalid_options = props.keys - Net::SSH::VALID_OPTIONS
|
97
|
+
if invalid_options.size > 0
|
98
|
+
raise LoadError, "#{invalid_options} are invalid for Net::SSH!"
|
99
|
+
end
|
100
|
+
config_set(:ssh, Grifork::Config::SSH.new(props))
|
101
|
+
end
|
102
|
+
|
103
|
+
# Configure rsync command options
|
104
|
+
# @params props [Array, Hash] rsync option parameters
|
105
|
+
# @example
|
106
|
+
# # Available full Hash options are bellow
|
107
|
+
# rsync delete: true, bwlimit: 4096, verbose: false, excludes: %w(.git* .svn*), rsh: nil, dry_run: false
|
108
|
+
# # This is the same with above by Array format:
|
109
|
+
# rsync %w(-az --delete --bwlimit=4096 --exclude=.git* --exclude=.svn*)
|
110
|
+
# # You can set more options by Array format:
|
111
|
+
# rsync %w(-azKc -e=rsh --delete --bwlimit=4096 --exclude-from=path/to/rsync.excludes)
|
112
|
+
def rsync(props)
|
113
|
+
config_set(:rsync, Grifork::Config::Rsync.new(props))
|
114
|
+
end
|
115
|
+
|
116
|
+
# Define tasks to execute at localhost
|
117
|
+
# @param &task [Proc] Codes to be executed by an object of {Grifork::Executor::Task}
|
60
118
|
def local(&task)
|
61
119
|
return if @on_remote
|
62
120
|
config_set(:local_task, Grifork::Executor::Task.new(:local, &task))
|
63
121
|
end
|
64
122
|
|
123
|
+
# Define tasks to execute at remote host
|
124
|
+
# @param &task [Proc] Codes to be executed by an object of {Grifork::Executor::Task}
|
125
|
+
# @note In +:standalone+ mode, the task is executed at localhost actually.
|
126
|
+
# In +:grifork+ mode, it is executed at remote hosts via +grifork+ command on remote hosts
|
65
127
|
def remote(&task)
|
66
128
|
if @on_remote
|
67
129
|
config_set(:local_task, Grifork::Executor::Task.new(:local, &task))
|
@@ -1,26 +1,28 @@
|
|
1
1
|
class Grifork::Executor::Grifork
|
2
2
|
include Grifork::Executable
|
3
|
-
attr :config
|
4
3
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
4
|
+
# Run grifork command on remote node.
|
5
|
+
#
|
6
|
+
# 1. Create +Griforkfile+ and copy it to remote
|
7
|
+
# 2. Login remote host by +ssh+ and execute +grifork+ command
|
8
|
+
def run(node)
|
9
|
+
c = config.grifork
|
10
|
+
ssh node.host, %(test -d "#{c.workdir}" || mkdir -p "#{c.workdir}")
|
11
|
+
rsync(node.host, config.griforkfile, "#{c.workdir}/Griforkfile")
|
12
|
+
prepare_grifork_hosts_file_on_remote(node)
|
13
|
+
ssh node.host, %(cd #{c.dir} && #{c.cmd} --file #{c.workdir}/Griforkfile --override-by #{c.workdir}/Griforkfile.hosts --on-remote)
|
8
14
|
end
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
c = config
|
15
|
-
ssh node.host, %(test -d "#{c.workdir}" || mkdir -p "#{c.workdir}"), user: c.login
|
16
|
-
sh :rsync, ['-avzc', Grifork.config.griforkfile, "#{node.host}:#{c.workdir}/Griforkfile"]
|
16
|
+
private
|
17
|
+
|
18
|
+
def prepare_grifork_hosts_file_on_remote(node)
|
19
|
+
c = config.grifork
|
17
20
|
hostsfile = Tempfile.create('Griforkfile.hosts')
|
18
21
|
hostsfile.write(<<-EOS)
|
19
22
|
hosts #{node.all_descendant_nodes.map(&:host)}
|
20
23
|
EOS
|
21
24
|
hostsfile.flush
|
22
|
-
|
25
|
+
rsync(node.host, hostsfile.path, "#{c.workdir}/Griforkfile.hosts")
|
23
26
|
hostsfile.close
|
24
|
-
ssh node.host, %(cd #{c.dir} && #{c.cmd} --file #{c.workdir}/Griforkfile --override-by #{c.workdir}/Griforkfile.hosts --on-remote), [], user: c.login
|
25
27
|
end
|
26
28
|
end
|
@@ -1,15 +1,40 @@
|
|
1
1
|
class Grifork::Executor::Task
|
2
2
|
include Grifork::Executable
|
3
|
-
attr :src, :dst
|
4
3
|
|
4
|
+
# Initialize with task
|
5
|
+
# @param &task [Proc] task to execute
|
5
6
|
def initialize(type, &task)
|
6
7
|
@type = type
|
7
8
|
@task = task
|
8
9
|
end
|
9
10
|
|
11
|
+
# Run the task
|
12
|
+
# @param src [String] Source hostname
|
13
|
+
# @param dst [String] Target hostname
|
10
14
|
def run(src, dst)
|
11
|
-
|
12
|
-
|
15
|
+
Thread.current[:src] = src
|
16
|
+
Thread.current[:dst] = dst
|
13
17
|
instance_eval(&@task)
|
14
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def src
|
23
|
+
Thread.current[:src]
|
24
|
+
end
|
25
|
+
|
26
|
+
def dst
|
27
|
+
Thread.current[:dst]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Wrapper for {Grifork::Executable#rsync}
|
31
|
+
def rsync(from, to = nil)
|
32
|
+
super(dst, from, to)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Wrapper for {Grifork::Executable#rsync_remote}
|
36
|
+
# @note This is for +remote+ task on +:standalone+ mode
|
37
|
+
def rsync_remote(from, to = nil)
|
38
|
+
super(src, dst, from, to)
|
39
|
+
end
|
15
40
|
end
|
data/lib/grifork/graph.rb
CHANGED
@@ -30,7 +30,7 @@ class Grifork::Graph
|
|
30
30
|
# Launch local and remote tasks through whole graph
|
31
31
|
def launch_tasks
|
32
32
|
# level = 1
|
33
|
-
Parallel.map(root.children,
|
33
|
+
Parallel.map(root.children, config.parallel => root.children.size) do |node|
|
34
34
|
logger.info("Run locally. localhost => #{node.host}")
|
35
35
|
config.local_task.run(root.host, node.host)
|
36
36
|
end
|
@@ -44,10 +44,14 @@ class Grifork::Graph
|
|
44
44
|
logger.debug("#{root} Reached leaf. Nothing to do.")
|
45
45
|
return
|
46
46
|
end
|
47
|
-
Parallel.map(root.children,
|
47
|
+
Parallel.map(root.children, config.parallel => root.children.size) do |child|
|
48
48
|
logger.info("Run locally. localhost => #{child.host}")
|
49
49
|
config.local_task.run(root.host, child.host)
|
50
|
-
|
50
|
+
if child.children.size.zero?
|
51
|
+
logger.debug("#{child} Reached leaf. Nothing to do.")
|
52
|
+
next
|
53
|
+
end
|
54
|
+
Grifork::Executor::Grifork.new.run(child)
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
@@ -82,7 +86,7 @@ class Grifork::Graph
|
|
82
86
|
return
|
83
87
|
end
|
84
88
|
|
85
|
-
Parallel.map(families,
|
89
|
+
Parallel.map(families, config.parallel => families.size) do |family|
|
86
90
|
parent = family[0]
|
87
91
|
child = family[1]
|
88
92
|
logger.info("Run remote [#{parent.level}]. #{parent.host} => #{child.host}")
|
@@ -1,11 +1,20 @@
|
|
1
1
|
module Grifork::Executable
|
2
|
+
include Grifork::Configured
|
2
3
|
include Grifork::Loggable
|
3
4
|
|
4
5
|
class CommandFailure < StandardError; end
|
5
6
|
class SSHCommandFailure < StandardError; end
|
6
7
|
|
8
|
+
# Execute shell command at localhost
|
9
|
+
# @param cmd [String] command
|
10
|
+
# @param args [Array] arguments
|
7
11
|
def sh(cmd, args = [])
|
8
|
-
|
12
|
+
if config.dry_run?
|
13
|
+
logger.info("[Dry-run] #sh | #{cmd} #{args}")
|
14
|
+
return
|
15
|
+
else
|
16
|
+
logger.info("#sh | #{cmd} #{args}")
|
17
|
+
end
|
9
18
|
stat = Open3.popen3(cmd.to_s, *args) do |stdin, stdout, stderr, wait_thr|
|
10
19
|
stdin.close
|
11
20
|
stdout.each { |l| logger.info("#sh [out] #{l.chomp}") }
|
@@ -18,16 +27,23 @@ module Grifork::Executable
|
|
18
27
|
end
|
19
28
|
end
|
20
29
|
|
21
|
-
|
30
|
+
# Execute +ssh+ with command to execute at remote host
|
31
|
+
# @param host [String] hostname
|
32
|
+
# @param cmd [String] command
|
33
|
+
# @param args [Array] arguments
|
34
|
+
def ssh(host, cmd, args = [])
|
22
35
|
command = "#{cmd} #{args.shelljoin}"
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
if config.dry_run?
|
37
|
+
logger.info("[Dry-run] #ssh @#{host} #{config.ssh.options} | #{cmd} #{args}")
|
38
|
+
return
|
39
|
+
else
|
40
|
+
logger.info("#ssh @#{host} #{config.ssh.options} | #{cmd} #{args}")
|
41
|
+
end
|
42
|
+
Net::SSH.start(host, nil, config.ssh.options) do |ssh|
|
27
43
|
channel = ssh.open_channel do |ch|
|
28
44
|
ch.exec(command) do |ch, success|
|
29
45
|
unless success
|
30
|
-
raise SSHCommandFailure, "Failed to exec ssh command!
|
46
|
+
raise SSHCommandFailure, "Failed to exec ssh command! - #{user}@#{host} | #{cmd} #{args}"
|
31
47
|
end
|
32
48
|
|
33
49
|
ch.on_data do |c, d|
|
@@ -43,17 +59,26 @@ module Grifork::Executable
|
|
43
59
|
end
|
44
60
|
end
|
45
61
|
|
46
|
-
|
62
|
+
# Shorthand for +rsync+ command.
|
63
|
+
# Sync contents to target host
|
64
|
+
# @param host [String] Target hostname
|
65
|
+
# @param from [String] Path to source file or directory
|
66
|
+
# @param to [String] Path to destination at remote host.
|
67
|
+
# If you omit this param, it will be the same with +from+ param
|
68
|
+
def rsync(host, from, to = nil)
|
47
69
|
to ||= from
|
48
|
-
sh :rsync, [*rsync_opts, from, "#{
|
70
|
+
sh :rsync, [*config.rsync_opts, from, "#{host}:#{to}"]
|
49
71
|
end
|
50
72
|
|
51
|
-
|
73
|
+
# Shorthand for +rsync+ command run by +ssh+ to source host
|
74
|
+
# Sync contents from source host to target host
|
75
|
+
# @param src [String] Source hostname to login by +ssh+
|
76
|
+
# @param dst [String] Target hostname
|
77
|
+
# @param from [String] Path to source file or directory
|
78
|
+
# @param to [String] Path to destination at remote host.
|
79
|
+
# If you omit this param, it will be the same with +from+ param
|
80
|
+
def rsync_remote(src, dst, from, to = nil)
|
52
81
|
to ||= from
|
53
|
-
ssh src, :rsync, [*rsync_opts, from, "#{dst}:#{to}"]
|
54
|
-
end
|
55
|
-
|
56
|
-
def rsync_opts
|
57
|
-
%w(-avzc --delete)
|
82
|
+
ssh src, :rsync, [*config.rsync_opts, from, "#{dst}:#{to}"]
|
58
83
|
end
|
59
84
|
end
|
data/lib/grifork/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grifork
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- key-amb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- ".gitignore"
|
120
120
|
- ".rspec"
|
121
121
|
- ".travis.yml"
|
122
|
+
- ".yardopts"
|
122
123
|
- CHANGELOG.md
|
123
124
|
- Gemfile
|
124
125
|
- LICENSE
|