grifork 0.3.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +79 -0
- data/Rakefile +26 -0
- data/bin/grifork +5 -0
- data/example/Griforkfile.grifork +33 -0
- data/example/Griforkfile.standalone +22 -0
- data/grifork.gemspec +31 -0
- data/lib/grifork/cli.rb +55 -0
- data/lib/grifork/config.rb +53 -0
- data/lib/grifork/dsl.rb +81 -0
- data/lib/grifork/executor/grifork.rb +26 -0
- data/lib/grifork/executor/task.rb +15 -0
- data/lib/grifork/executor.rb +1 -0
- data/lib/grifork/graph/node.rb +68 -0
- data/lib/grifork/graph.rb +94 -0
- data/lib/grifork/logger.rb +21 -0
- data/lib/grifork/mixin/configured.rb +5 -0
- data/lib/grifork/mixin/executable.rb +59 -0
- data/lib/grifork/mixin/loggable.rb +5 -0
- data/lib/grifork/version.rb +3 -0
- data/lib/grifork.rb +43 -0
- data/tmp/.keep +0 -0
- metadata +170 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 39cd6c990f5c21321b737f9c12c7651ff843a7a2
|
4
|
+
data.tar.gz: 5a02758138e9f35fada790135e896d7bcea1a249
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c87db79f590d4356a7e4c6225cd8f546895e0ec3ebcbba02c45bfe6e742006d2726ff523c22a00ff19aa3d747a0416adf576c0b3bdf1ef9c65c8460f2f28c07
|
7
|
+
data.tar.gz: 6dfbb038e924db9aa075c4ea212d15c41d32ec6c4ccbe4143dbcb89488a3b6c185178c69b93a74d659c851965233e0c18e560b0c4a59fc76547fc6d1a503825e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
rvm:
|
2
|
+
- 2.0.0
|
3
|
+
- 2.1.8
|
4
|
+
- 2.2.0
|
5
|
+
|
6
|
+
install:
|
7
|
+
- gem install bundler -v 1.12.5
|
8
|
+
- bundle _1.12.5_ install
|
9
|
+
|
10
|
+
script:
|
11
|
+
- bundle _1.12.5_ exec rake spec
|
12
|
+
|
13
|
+
notifications:
|
14
|
+
slack:
|
15
|
+
rooms:
|
16
|
+
secure: FahF4aDqUG6GNTurc3lYmaudpB25dgEQmd2HCXMU5o9MHsMa5UMNX1yP5vykhW+n9zGBq5yJKOXZeVlJsVSFGuPOjrFXUqbc75RO1sjov0r7vPb59mDRTOTpiodBwMBwdrATokBNnzZGujN8C5LoLpFXlPehCvoEfZfFS5tuCq9CE9UOXdapVArBwM/UkBaLgkSLi+JHb3fYKcZXsHkZ6SB1RdJKONbbHFW+7TuuWagXjEE80oLHKMClLedUXhg5uC9f39aqUar5PawPjR3r6pMMXtBsbeZylszL6Xzx2Bt6TRB5cE+479zpfbvre8NvbZ4HJDZgHto+hUo9RyyzxxJU6QIdweU4H0LiKBtFeSqpTcWTf/8h9OL6I7TviGyQ1PdX/jWhoJwfhGhy1yXE7ZLNI6kczb9DHDdMK+aUOvuFiPBWyUrPSsqfDm0jRAWfiUqzdxrn8B7N3C8W99u6gYkhC/Zy8j7mLjH8NnAPcBS8cd/hH9qWtJ8v9Fr3dhEPsKjiGU61cVk2OFOxJ2YXttNe3iqOV3hKS86632jkIEICllT4ZYNClNi0znS7J6kuQYYwDdaqSYY+jTFIAyeV4+toHGNcTg9fb5s81XwfKgg5E8LwiWIWFr+/HMsh0RkaaIaQInsLTIBTnARUwM+BYWoaZ/FGrCjFH8mExBI/F0M=
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
## 0.3.0 (2016/10/4)
|
2
|
+
|
3
|
+
Release as a RubyGem.
|
4
|
+
|
5
|
+
## 0.2.0 (2016/10/2)
|
6
|
+
|
7
|
+
Feature:
|
8
|
+
|
9
|
+
- Implement "grifork" mode to exec "grifork" command in parallel and recursively
|
10
|
+
among target hosts #1
|
11
|
+
|
12
|
+
## 0.1.0 (2016/9/28)
|
13
|
+
|
14
|
+
Initial release.
|
15
|
+
|
16
|
+
Feature:
|
17
|
+
|
18
|
+
- Implement "standalone" mode to run defined tasks for target hosts
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 IKEDA Kiyoshi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
[](https://travis-ci.org/key-amb/grifork)
|
2
|
+
|
3
|
+
# grifork
|
4
|
+
|
5
|
+
Fast propagative task runner for systems which consist of a lot of servers.
|
6
|
+
|
7
|
+
# Concept
|
8
|
+
|
9
|
+
**Grifork** runs defined tasks on the system in a way like tree's branching.
|
10
|
+
Give **grifork** a list of hosts, then it creates a tree graph internally, and runs
|
11
|
+
tasks in a top-down way.
|
12
|
+
|
13
|
+
**Grifork** has two modes to work:
|
14
|
+
|
15
|
+
1. **Standalone** mode. This requires **grifork** program only on the root server
|
16
|
+
in the tree graph: i.e. the server which invokes tasks.
|
17
|
+
1. **Grifork** mode. On the other hand, this requires **grifork** program to work
|
18
|
+
on every node in the graph.
|
19
|
+
|
20
|
+
Take a look at each mechanism.
|
21
|
+
|
22
|
+
## Standalone Mode
|
23
|
+
|
24
|
+
The image below illustrates a 3-depth tree of 13 nodes including root node.
|
25
|
+
|
26
|
+

|
27
|
+
|
28
|
+
We are running a task to copy a file tree to every host.
|
29
|
+
1st stage is completed: to copy them to nodes in 1st generation.
|
30
|
+
|
31
|
+
Now at 2nd stage, root node logins each of its children by _ssh_ and kicks _rsync_
|
32
|
+
program there, in order to copy the file tree from 1st to 2nd generation.
|
33
|
+
|
34
|
+
NOTE:
|
35
|
+
|
36
|
+
- Max concurrency of running task in **standalone** mode is the number of nodes
|
37
|
+
at the generation which holds max.
|
38
|
+
This is the last genaration or the genartion before the last.
|
39
|
+
|
40
|
+
## Grifork Mode
|
41
|
+
|
42
|
+
The image below is similar to previous situation except that this is in **grifork** mode.
|
43
|
+
|
44
|
+

|
45
|
+
|
46
|
+
In this mode, parent nodes in the graph invokes _grifork_ command on every child
|
47
|
+
node via _ssh_, giving the graph tree which descends from each child node.
|
48
|
+
|
49
|
+
# System Requirements
|
50
|
+
|
51
|
+
- Ruby v2
|
52
|
+
- ssh, rsync
|
53
|
+
|
54
|
+
# Installation
|
55
|
+
|
56
|
+
```sh
|
57
|
+
git clone https://github.com/key-amb/grifork.git
|
58
|
+
cd grifork
|
59
|
+
bundle
|
60
|
+
```
|
61
|
+
|
62
|
+
# Usage
|
63
|
+
|
64
|
+
```sh
|
65
|
+
edit Griforkfile
|
66
|
+
./bin/grifork [--[f]ile path/to/Griforkfile]
|
67
|
+
```
|
68
|
+
|
69
|
+
See [example](example) directory for sample of Griforkfiles.
|
70
|
+
|
71
|
+
# Authors
|
72
|
+
|
73
|
+
IKEDA Kiyoshi <yasutake.kiyoshi@gmail.com>
|
74
|
+
|
75
|
+
# License
|
76
|
+
|
77
|
+
The MIT License (MIT)
|
78
|
+
|
79
|
+
Copyright (c) 2016 IKEDA Kiyoshi
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
desc 'Open a pry session preloaded with the library'
|
4
|
+
task :console do
|
5
|
+
sh 'pry --gem'
|
6
|
+
end
|
7
|
+
task :c => :console
|
8
|
+
|
9
|
+
require 'rspec/core'
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
12
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
13
|
+
end
|
14
|
+
task :default => :spec
|
15
|
+
|
16
|
+
namespace :git do
|
17
|
+
desc 'Bump git tag and release to origin'
|
18
|
+
task :bump do
|
19
|
+
require_relative 'lib/grifork'
|
20
|
+
version = Grifork::VERSION
|
21
|
+
sh "git commit -m #{version}"
|
22
|
+
sh "git tag -a v#{version} -m #{version}"
|
23
|
+
sh "git push origin master"
|
24
|
+
sh "git push origin v#{version}"
|
25
|
+
end
|
26
|
+
end
|
data/bin/grifork
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Configuration
|
2
|
+
#mode :grifork
|
3
|
+
branches 4
|
4
|
+
log file: 'tmp/grifork.log', level: 'debug'
|
5
|
+
|
6
|
+
# Setting to exec grifork on remote
|
7
|
+
# Implies to set mode as :grifork
|
8
|
+
grifork do
|
9
|
+
user 'someone' # ssh user to exec grifork on remote
|
10
|
+
chdir '/path/to/your-app'
|
11
|
+
tmpdir '/path/to/tmpdir'
|
12
|
+
exec '/path/to/grifork'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Define hosts as array
|
16
|
+
hosts ['web1.internal', 'web2.internal', '192.168.10.1', '192.168.10.2']
|
17
|
+
|
18
|
+
# Define task run on localhost
|
19
|
+
local do
|
20
|
+
sh :echo, %W(LOCAL: #{src} => #{dst})
|
21
|
+
ssh dst, :mkdir, %W(-p /path/to/dest), user: 'someone'
|
22
|
+
rsync '/path/to/src/', '/path/to/dest/'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define task run on remote hosts
|
26
|
+
# NOTE: This task is run as "local" task on remote
|
27
|
+
# different from "remote" task in :standalone mode
|
28
|
+
remote do
|
29
|
+
sh :echo, %W(REMOTE: #{src} => #{dst})
|
30
|
+
ssh dst, :mkdir, %W(-p /path/to/dest), user: 'someone'
|
31
|
+
rsync '/path/to/src/', '/path/to/dest/'
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Configuration
|
2
|
+
#mode :standalone # Default mode
|
3
|
+
branches 4
|
4
|
+
log file: 'tmp/grifork.log', level: 'debug'
|
5
|
+
|
6
|
+
# Define hosts as array
|
7
|
+
hosts ['web1.internal', 'web2.internal', '192.168.10.1', '192.168.10.2']
|
8
|
+
|
9
|
+
# Define task run on localhost
|
10
|
+
local do
|
11
|
+
sh :echo, %W(LOCAL: #{src} => #{dst})
|
12
|
+
ssh dst, :mkdir, %W(-p /path/to/dest), user: 'someone'
|
13
|
+
rsync '/path/to/src/', '/path/to/dest/'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Define task run on remote hosts
|
17
|
+
remote do
|
18
|
+
sh :echo, %W(REMOTE: #{src} => #{dst})
|
19
|
+
ssh dst, :mkdir, %W(-p /path/to/dest)
|
20
|
+
rsync_remote '/path/to/src/', '/path/to/dest/', user: 'someone'
|
21
|
+
end
|
22
|
+
|
data/grifork.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'grifork/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "grifork"
|
8
|
+
spec.version = Grifork::VERSION
|
9
|
+
spec.authors = ["key-amb"]
|
10
|
+
spec.email = ["yasutake.kiyoshi@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Fast Propagative Task Runner}
|
13
|
+
spec.description = %q{Fast propagative task runner for systems which consist of a lot of servers.}
|
14
|
+
spec.homepage = "https://github.com/key-amb/grifork"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.executables = ["grifork"]
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.0")
|
22
|
+
|
23
|
+
spec.add_runtime_dependency "net-ssh", "~> 3.0"
|
24
|
+
spec.add_runtime_dependency "parallel", "~> 1.9"
|
25
|
+
spec.add_runtime_dependency "stdlogger", "~> 0.3.0"
|
26
|
+
|
27
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
end
|
data/lib/grifork/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
class Grifork::CLI
|
2
|
+
def run(argv)
|
3
|
+
OptionParser.new do |opt|
|
4
|
+
opt.on('-f', '--file Griforkfile') { |f| @taskfile = f }
|
5
|
+
opt.on('-o', '--override-by FILE') { |f| @override_file = f }
|
6
|
+
opt.on('-r', '--on-remote') { @on_remote = true }
|
7
|
+
opt.on('-v', '--version') { @version = true }
|
8
|
+
opt.parse!(argv)
|
9
|
+
end
|
10
|
+
if @version
|
11
|
+
puts Grifork::VERSION
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
config = load_taskfiles.freeze
|
16
|
+
Grifork.configure!(config)
|
17
|
+
|
18
|
+
graph = Grifork::Graph.new(config.hosts)
|
19
|
+
|
20
|
+
if @on_remote
|
21
|
+
puts "Start on remote. Hosts: #{config.hosts}"
|
22
|
+
end
|
23
|
+
|
24
|
+
case config.mode
|
25
|
+
when :standalone
|
26
|
+
graph.launch_tasks
|
27
|
+
when :grifork
|
28
|
+
graph.grifork
|
29
|
+
else
|
30
|
+
# Never comes here
|
31
|
+
raise "Unexpected mode! #{config.mode}"
|
32
|
+
end
|
33
|
+
|
34
|
+
if @on_remote
|
35
|
+
puts "End on remote."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def load_taskfiles
|
42
|
+
puts "Load settings from #{taskfile}"
|
43
|
+
dsl = Grifork::DSL.load_file(taskfile, on_remote: @on_remote)
|
44
|
+
if @override_file
|
45
|
+
dsl.load_and_merge_config_by!(@override_file)
|
46
|
+
end
|
47
|
+
config = dsl.to_config
|
48
|
+
config.griforkfile = taskfile
|
49
|
+
config
|
50
|
+
end
|
51
|
+
|
52
|
+
def taskfile
|
53
|
+
@taskfile || ENV['GRIFORKFILE'] || Grifork::DEFAULT_TASKFILE
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Grifork::Config
|
2
|
+
attr_reader :branches, :hosts, :log, :local_task, :remote_task, :grifork
|
3
|
+
attr_accessor :griforkfile
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
args.each do |key, val|
|
7
|
+
instance_variable_set("@#{key}", val)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def mode
|
12
|
+
@mode || :standalone
|
13
|
+
end
|
14
|
+
|
15
|
+
class Log
|
16
|
+
attr :file, :level
|
17
|
+
|
18
|
+
def initialize(args)
|
19
|
+
@file = args[:file]
|
20
|
+
@level = args[:level] || 'info'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Grifork
|
25
|
+
attr :dir, :cmd, :login
|
26
|
+
|
27
|
+
def initialize(&config)
|
28
|
+
instance_eval(&config)
|
29
|
+
end
|
30
|
+
|
31
|
+
def workdir
|
32
|
+
@tmpdir || Dir.tmpdir
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def user(login)
|
38
|
+
@login = login
|
39
|
+
end
|
40
|
+
|
41
|
+
def chdir(path)
|
42
|
+
@dir = path
|
43
|
+
end
|
44
|
+
|
45
|
+
def exec(cmd)
|
46
|
+
@cmd = cmd
|
47
|
+
end
|
48
|
+
|
49
|
+
def tmpdir(path)
|
50
|
+
@tmpdir = path
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/grifork/dsl.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
class Grifork::DSL
|
2
|
+
attr :config
|
3
|
+
|
4
|
+
class LoadError < StandardError; end
|
5
|
+
|
6
|
+
# Load DSL file to object
|
7
|
+
# @param path [String]
|
8
|
+
# @param on_remote [Boolean] whether process is invoked by remote host in :grifork mode or not
|
9
|
+
def self.load_file(path, on_remote: false)
|
10
|
+
content = File.binread(path)
|
11
|
+
dsl = new(on_remote)
|
12
|
+
dsl.instance_eval(content)
|
13
|
+
dsl
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(on_remote)
|
17
|
+
@config = {}
|
18
|
+
@on_remote = on_remote
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_config
|
22
|
+
Grifork::Config.new(@config)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Load another DSL file and merge its config
|
26
|
+
def load_and_merge_config_by!(path)
|
27
|
+
content = File.binread(path)
|
28
|
+
other = self.class.new(@on_remote)
|
29
|
+
other.instance_eval(content)
|
30
|
+
@config.merge!(other.config)
|
31
|
+
end
|
32
|
+
|
33
|
+
def mode(m)
|
34
|
+
unless Grifork::MODES.has_key?(m)
|
35
|
+
raise LoadError, "Undefined mode! #{m}"
|
36
|
+
end
|
37
|
+
config_set(:mode, m)
|
38
|
+
end
|
39
|
+
|
40
|
+
def grifork(&command)
|
41
|
+
if @config[:mode] == :standalone
|
42
|
+
raise LoadError, "Can't configure grifork in standalone mode"
|
43
|
+
end
|
44
|
+
@config[:mode] = :grifork
|
45
|
+
config_set(:grifork, Grifork::Config::Grifork.new(&command))
|
46
|
+
end
|
47
|
+
|
48
|
+
def branches(num)
|
49
|
+
config_set(:branches, num)
|
50
|
+
end
|
51
|
+
|
52
|
+
def log(args)
|
53
|
+
config_set(:log, Grifork::Config::Log.new(args))
|
54
|
+
end
|
55
|
+
|
56
|
+
def hosts(list)
|
57
|
+
config_set(:hosts, list)
|
58
|
+
end
|
59
|
+
|
60
|
+
def local(&task)
|
61
|
+
return if @on_remote
|
62
|
+
config_set(:local_task, Grifork::Executor::Task.new(:local, &task))
|
63
|
+
end
|
64
|
+
|
65
|
+
def remote(&task)
|
66
|
+
if @on_remote
|
67
|
+
config_set(:local_task, Grifork::Executor::Task.new(:local, &task))
|
68
|
+
else
|
69
|
+
config_set(:remote_task, Grifork::Executor::Task.new(:remote, &task))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def config_set(key, value)
|
76
|
+
if @config[key]
|
77
|
+
raise LoadError, %(Config "#{key}" is already defined!)
|
78
|
+
end
|
79
|
+
@config[key] = value
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Grifork::Executor::Grifork
|
2
|
+
include Grifork::Executable
|
3
|
+
attr :config
|
4
|
+
|
5
|
+
# @param cfg [Grifork::Config::Grifork] configured by DSL#grifork
|
6
|
+
def initialize(cfg)
|
7
|
+
@config = cfg
|
8
|
+
end
|
9
|
+
|
10
|
+
# Run grifork command on remote node:
|
11
|
+
# 1. Create Griforkfile and copy it to remote
|
12
|
+
# 2. ssh remote host and exec grifork
|
13
|
+
def run(node)
|
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"]
|
17
|
+
hostsfile = Tempfile.create('Griforkfile.hosts')
|
18
|
+
hostsfile.write(<<-EOS)
|
19
|
+
hosts #{node.all_descendant_nodes.map(&:host)}
|
20
|
+
EOS
|
21
|
+
hostsfile.flush
|
22
|
+
sh :rsync, ['-avzc', hostsfile.path, "#{node.host}:#{c.workdir}/Griforkfile.hosts"]
|
23
|
+
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
|
+
end
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
module Grifork::Executor; end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Grifork::Graph::Node
|
2
|
+
include Grifork::Configured
|
3
|
+
attr :index, :level, :number, :children, :parent, :host
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr :count
|
7
|
+
def add
|
8
|
+
@count ||= 0
|
9
|
+
@count += 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(host, parent: nil)
|
14
|
+
@host = host
|
15
|
+
@children = []
|
16
|
+
if parent
|
17
|
+
@parent = parent
|
18
|
+
@level = parent.level + 1
|
19
|
+
@number = parent.children.size
|
20
|
+
else
|
21
|
+
@level = 0
|
22
|
+
@number = 0
|
23
|
+
end
|
24
|
+
@index = self.class.count || 0
|
25
|
+
self.class.add
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"<#{index}:#{id}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
@id ||= -> {
|
34
|
+
if parent
|
35
|
+
"#{parent.id}-#{level}.#{number}"
|
36
|
+
else
|
37
|
+
"#{level}.#{number}"
|
38
|
+
end
|
39
|
+
}.call
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_child(child)
|
43
|
+
unless acceptable?
|
44
|
+
raise "Unacceptable!"
|
45
|
+
end
|
46
|
+
@children << child
|
47
|
+
end
|
48
|
+
|
49
|
+
def local?
|
50
|
+
parent ? false : true
|
51
|
+
end
|
52
|
+
|
53
|
+
def acceptable?
|
54
|
+
children.size < config.branches
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_descendant_nodes
|
58
|
+
@descendants = @children
|
59
|
+
@descendables = @children.select { |c| c.children.size > 0 }
|
60
|
+
while @descendables.size > 0
|
61
|
+
child = @descendables.shift
|
62
|
+
@descendants.concat(child.children)
|
63
|
+
new_descendables = child.children { |n| n.children.size > 0 }
|
64
|
+
@descendables.concat(new_descendables)
|
65
|
+
end
|
66
|
+
@descendants
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class Grifork::Graph
|
2
|
+
include Grifork::Configured
|
3
|
+
include Grifork::Loggable
|
4
|
+
attr :root, :nodes, :depth
|
5
|
+
|
6
|
+
def initialize(hosts = [])
|
7
|
+
@root = Node.new('localhost')
|
8
|
+
@depth = @root.level
|
9
|
+
@nodes = 1
|
10
|
+
@acceptable_nodes = [@root]
|
11
|
+
hosts.each do |host|
|
12
|
+
self.add_node_by_host(host)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_node_by_host(host)
|
17
|
+
parent = @acceptable_nodes.first
|
18
|
+
node = Node.new(host, parent: parent)
|
19
|
+
parent.add_child(node)
|
20
|
+
unless parent.acceptable?
|
21
|
+
@acceptable_nodes.shift
|
22
|
+
end
|
23
|
+
@last = node
|
24
|
+
@depth = node.level
|
25
|
+
@nodes += 1
|
26
|
+
@acceptable_nodes << node
|
27
|
+
parent
|
28
|
+
end
|
29
|
+
|
30
|
+
# Launch local and remote tasks through whole graph
|
31
|
+
def launch_tasks
|
32
|
+
# level = 1
|
33
|
+
Parallel.map(root.children, in_threads: root.children.size) do |node|
|
34
|
+
logger.info("Run locally. localhost => #{node.host}")
|
35
|
+
config.local_task.run(root.host, node.host)
|
36
|
+
end
|
37
|
+
# level in (2..depth)
|
38
|
+
fork_remote_tasks(root.children)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Run grifork command on child nodes recursively
|
42
|
+
def grifork
|
43
|
+
if root.children.size.zero?
|
44
|
+
logger.debug("#{root} Reached leaf. Nothing to do.")
|
45
|
+
return
|
46
|
+
end
|
47
|
+
Parallel.map(root.children, in_processes: root.children.size) do |child|
|
48
|
+
logger.info("Run locally. localhost => #{child.host}")
|
49
|
+
config.local_task.run(root.host, child.host)
|
50
|
+
Grifork::Executor::Grifork.new(config.grifork).run(child)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Print graph structure for debug usage
|
55
|
+
def print(node = root)
|
56
|
+
puts %( ) * node.level + "#{node}"
|
57
|
+
node.children.each do |child|
|
58
|
+
print(child)
|
59
|
+
end
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Launch remote tasks recursively
|
66
|
+
def fork_remote_tasks(parents)
|
67
|
+
families = []
|
68
|
+
next_generation = []
|
69
|
+
parents.each do |parent|
|
70
|
+
if parent.children.size.zero?
|
71
|
+
logger.debug("#{parent} Reached leaf. Nothing to do.")
|
72
|
+
next
|
73
|
+
end
|
74
|
+
parent.children.each do |child|
|
75
|
+
families << [parent, child]
|
76
|
+
next_generation << child
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if families.size.zero?
|
81
|
+
logger.info("Reached bottom of the tree. Nothing to do.")
|
82
|
+
return
|
83
|
+
end
|
84
|
+
|
85
|
+
Parallel.map(families, in_threads: families.size) do |family|
|
86
|
+
parent = family[0]
|
87
|
+
child = family[1]
|
88
|
+
logger.info("Run remote [#{parent.level}]. #{parent.host} => #{child.host}")
|
89
|
+
config.remote_task.run(parent.host, child.host)
|
90
|
+
end
|
91
|
+
|
92
|
+
fork_remote_tasks(next_generation)
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Grifork::Logger
|
2
|
+
def self.create
|
3
|
+
c = Grifork.config
|
4
|
+
if c.log.file
|
5
|
+
logger = StdLogger.create c.log.file
|
6
|
+
else
|
7
|
+
logger = StdLogger.create
|
8
|
+
end
|
9
|
+
logger.level = log_level(c.log.level)
|
10
|
+
logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.log_level(arg_level = 'info')
|
14
|
+
level = arg_level.upcase
|
15
|
+
if ::Logger.const_defined?(level)
|
16
|
+
::Logger.const_get(level)
|
17
|
+
else
|
18
|
+
::Logger::INFO
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Grifork::Executable
|
2
|
+
include Grifork::Loggable
|
3
|
+
|
4
|
+
class CommandFailure < StandardError; end
|
5
|
+
class SSHCommandFailure < StandardError; end
|
6
|
+
|
7
|
+
def sh(cmd, args = [])
|
8
|
+
logger.info("#sh start - #{cmd} #{args}")
|
9
|
+
stat = Open3.popen3(cmd.to_s, *args) do |stdin, stdout, stderr, wait_thr|
|
10
|
+
stdin.close
|
11
|
+
stdout.each { |l| logger.info("#sh [out] #{l.chomp}") }
|
12
|
+
stderr.each { |l| logger.warn("#sh [err] #{l.chomp}") }
|
13
|
+
wait_thr.value
|
14
|
+
end
|
15
|
+
|
16
|
+
unless stat.success?
|
17
|
+
raise CommandFailure, "Failed to exec command! #{cmd} #{args}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ssh(host, cmd, args = [], user: nil)
|
22
|
+
command = "#{cmd} #{args.shelljoin}"
|
23
|
+
logger.info("#ssh start - to: #{host}, command: #{cmd} #{args}")
|
24
|
+
ssh_args = [host]
|
25
|
+
ssh_args << user if user
|
26
|
+
Net::SSH.start(*ssh_args) do |ssh|
|
27
|
+
channel = ssh.open_channel do |ch|
|
28
|
+
ch.exec(command) do |ch, success|
|
29
|
+
unless success
|
30
|
+
raise SSHCommandFailure, "Failed to exec ssh command! on: #{host} command: #{cmd} #{args}"
|
31
|
+
end
|
32
|
+
|
33
|
+
ch.on_data do |c, d|
|
34
|
+
d.each_line { |l| logger.info("#ssh @#{host} [out] #{l.chomp}") }
|
35
|
+
end
|
36
|
+
ch.on_extended_data do |c, t, d|
|
37
|
+
d.each_line { |l| logger.warn("#ssh @#{host} [err] #{l.chomp}") }
|
38
|
+
end
|
39
|
+
ch.on_close { logger.debug("#ssh @#{host} end.") }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
channel.wait
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def rsync(from, to = nil)
|
47
|
+
to ||= from
|
48
|
+
sh :rsync, [*rsync_opts, from, "#{dst}:#{to}"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def rsync_remote(from, to = nil, user: nil)
|
52
|
+
to ||= from
|
53
|
+
ssh src, :rsync, [*rsync_opts, from, "#{dst}:#{to}"], user: user
|
54
|
+
end
|
55
|
+
|
56
|
+
def rsync_opts
|
57
|
+
%w(-avzc --delete)
|
58
|
+
end
|
59
|
+
end
|
data/lib/grifork.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'optparse'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'shellwords'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'net/ssh'
|
7
|
+
require 'parallel'
|
8
|
+
require 'stdlogger'
|
9
|
+
|
10
|
+
class Grifork
|
11
|
+
require_relative 'grifork/config'
|
12
|
+
require_relative 'grifork/mixin/configured'
|
13
|
+
require_relative 'grifork/logger'
|
14
|
+
require_relative 'grifork/mixin/loggable'
|
15
|
+
require_relative 'grifork/graph'
|
16
|
+
require_relative 'grifork/graph/node'
|
17
|
+
require_relative 'grifork/executor'
|
18
|
+
require_relative 'grifork/mixin/executable'
|
19
|
+
require_relative 'grifork/executor/grifork'
|
20
|
+
require_relative 'grifork/executor/task'
|
21
|
+
require_relative 'grifork/dsl'
|
22
|
+
require_relative 'grifork/cli'
|
23
|
+
require_relative 'grifork/version'
|
24
|
+
|
25
|
+
DEFAULT_TASKFILE = 'Griforkfile'
|
26
|
+
|
27
|
+
MODES = {
|
28
|
+
standalone: 1,
|
29
|
+
grifork: 2,
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
class << self
|
33
|
+
attr :config
|
34
|
+
|
35
|
+
def configure!(config)
|
36
|
+
@config = config
|
37
|
+
end
|
38
|
+
|
39
|
+
def logger
|
40
|
+
@logger ||= -> { Grifork::Logger.create }.call
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/tmp/.keep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grifork
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- key-amb
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-ssh
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: parallel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.9'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: stdlogger
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.12'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.12'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
description: Fast propagative task runner for systems which consist of a lot of servers.
|
112
|
+
email:
|
113
|
+
- yasutake.kiyoshi@gmail.com
|
114
|
+
executables:
|
115
|
+
- grifork
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".rspec"
|
121
|
+
- ".travis.yml"
|
122
|
+
- CHANGELOG.md
|
123
|
+
- Gemfile
|
124
|
+
- LICENSE
|
125
|
+
- README.md
|
126
|
+
- Rakefile
|
127
|
+
- bin/grifork
|
128
|
+
- example/Griforkfile.grifork
|
129
|
+
- example/Griforkfile.standalone
|
130
|
+
- grifork.gemspec
|
131
|
+
- lib/grifork.rb
|
132
|
+
- lib/grifork/cli.rb
|
133
|
+
- lib/grifork/config.rb
|
134
|
+
- lib/grifork/dsl.rb
|
135
|
+
- lib/grifork/executor.rb
|
136
|
+
- lib/grifork/executor/grifork.rb
|
137
|
+
- lib/grifork/executor/task.rb
|
138
|
+
- lib/grifork/graph.rb
|
139
|
+
- lib/grifork/graph/node.rb
|
140
|
+
- lib/grifork/logger.rb
|
141
|
+
- lib/grifork/mixin/configured.rb
|
142
|
+
- lib/grifork/mixin/executable.rb
|
143
|
+
- lib/grifork/mixin/loggable.rb
|
144
|
+
- lib/grifork/version.rb
|
145
|
+
- tmp/.keep
|
146
|
+
homepage: https://github.com/key-amb/grifork
|
147
|
+
licenses:
|
148
|
+
- MIT
|
149
|
+
metadata: {}
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '2.0'
|
159
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
requirements: []
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 2.6.6
|
167
|
+
signing_key:
|
168
|
+
specification_version: 4
|
169
|
+
summary: Fast Propagative Task Runner
|
170
|
+
test_files: []
|