bow 0.0.0 → 0.5.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/.gitignore +12 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +156 -0
- data/Rakefile +11 -0
- data/bin/bow +6 -0
- data/bow.gemspec +10 -4
- data/lib/bow.rb +25 -0
- data/lib/bow/application.rb +96 -0
- data/lib/bow/command.rb +60 -0
- data/lib/bow/commands/apply.rb +24 -0
- data/lib/bow/commands/exec.rb +29 -0
- data/lib/bow/commands/init.rb +15 -0
- data/lib/bow/commands/ping.rb +16 -0
- data/lib/bow/commands/prepare.rb +24 -0
- data/lib/bow/config.rb +57 -0
- data/lib/bow/inventory.rb +84 -0
- data/lib/bow/inventory_example.rb +61 -0
- data/lib/bow/locker.rb +180 -0
- data/lib/bow/memorable.rb +37 -0
- data/lib/bow/options.rb +74 -0
- data/lib/bow/rake.rb +118 -0
- data/lib/bow/response_formatter.rb +39 -0
- data/lib/bow/ssh/rsync.rb +24 -0
- data/lib/bow/ssh/scp.rb +29 -0
- data/lib/bow/ssh_helper.rb +84 -0
- data/lib/bow/targets.rb +54 -0
- data/lib/bow/thread_pool.rb +67 -0
- data/lib/bow/version.rb +5 -0
- data/src/preprovision.sh +42 -0
- metadata +48 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f786de3bf3a07e01c6e1163fb70958356e0c219
|
4
|
+
data.tar.gz: 1c04ee09cd9e2262f217ac3a21530a6b39ff6546
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fae7064eb48e541ac88fd12512c99e13b49dc23a964c41f9391cd3b59046342a4ad3713b2951aa4befc33995713d3d360ee32d0ca39ac49b7615f6199be37890
|
7
|
+
data.tar.gz: 40251d5f6b674fee4d5429b67d3ade045867a80f0bb68b62d62ceab2e6b725163ca82517a51ca0d8d2c73902a31d53a6d635a64e9f64c84b0178b8107ef47a18
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Zinovyev Ivan
|
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,156 @@
|
|
1
|
+
# bow
|
2
|
+
|
3
|
+
Automate your infrastructure provisioning and configuration with Rake.
|
4
|
+
|
5
|
+
[](https://travis-ci.org/zinovyev/bow)
|
6
|
+
|
7
|
+
## About
|
8
|
+
|
9
|
+
Bow is a system that allows you to write Rake tasks and execute them remotely
|
10
|
+
to provision and configure you servers.
|
11
|
+
|
12
|
+
## Why?
|
13
|
+
|
14
|
+
Well...
|
15
|
+
|
16
|
+
I do know about dozens of [configuration management](https://en.wikipedia.org/wiki/Comparison_of_open-source_configuration_management_software) software. And I've also used to use some of them.
|
17
|
+
And I actually like a couple of them too.
|
18
|
+
|
19
|
+
But when it comes to a simple goal like configuring 2 or 3 VPS nodes I don't
|
20
|
+
really want to create a complex infrastructure (I don't want to create any
|
21
|
+
infrastructure to be honest) for it and I like to do it in Ruby (well, maybe
|
22
|
+
in Bash and Ruby).
|
23
|
+
|
24
|
+
And there's already exists a great tool that can execute tasks and is written
|
25
|
+
in Ruby. And it's called Rake.
|
26
|
+
|
27
|
+
So. Bow is simple, agentless and it doesn't bring it's own DSL to life, cause it
|
28
|
+
uses Rake instead.
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
1. If you're not familiar with Rake [Rake docs](https://ruby.github.io/rake/) and [Rake home](https://github.com/ruby/rake) can be a good place to start from;
|
33
|
+
|
34
|
+
2. Install the `bow` gem with:
|
35
|
+
|
36
|
+
```bash
|
37
|
+
|
38
|
+
gem install bow
|
39
|
+
|
40
|
+
```
|
41
|
+
|
42
|
+
3. Create an empty folder and init a basic structure there:
|
43
|
+
|
44
|
+
```bash
|
45
|
+
|
46
|
+
mkdir ~/bow-test
|
47
|
+
|
48
|
+
cd ~/bow-test
|
49
|
+
|
50
|
+
bow init
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
4. The command `bow init` called above created a bare structure wich consits
|
55
|
+
of a `Rakefile` and a list of targets (managed servers) in `targets.json` file.
|
56
|
+
|
57
|
+
The example targets file contains 4 IPs combined in two managed groups:
|
58
|
+
|
59
|
+
```json
|
60
|
+
|
61
|
+
{
|
62
|
+
"example_group1": [
|
63
|
+
"192.168.50.27",
|
64
|
+
"192.168.50.37"
|
65
|
+
],
|
66
|
+
"example_group2": [
|
67
|
+
"192.168.50.47",
|
68
|
+
"192.168.50.57"
|
69
|
+
]
|
70
|
+
}
|
71
|
+
|
72
|
+
```
|
73
|
+
|
74
|
+
A `Rakefile` contains two tasks for provisioning packed in namespaces wich are
|
75
|
+
called by the name of the server groups from the `targets.json` file. The main
|
76
|
+
task of the group MUST always be called **provision** and can be bound to any
|
77
|
+
number of additional tasks. Sometimes (always) it is convinient to put
|
78
|
+
additional tasks them to separate files into the [rakelib](https://ruby.github.io/rake/doc/rakefile_rdoc.html#label-Multiple+Rake+Files) folder.
|
79
|
+
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
|
83
|
+
require 'bow/rake'
|
84
|
+
|
85
|
+
Rake.application.options.trace_rules = true
|
86
|
+
|
87
|
+
PROVISION_DIR = '/tmp/rake_provision'.freeze
|
88
|
+
|
89
|
+
namespace :web do
|
90
|
+
task provision: :print_hello do
|
91
|
+
end
|
92
|
+
|
93
|
+
flow run: :once
|
94
|
+
task :print_hello do
|
95
|
+
sh 'echo "Hello from example group #1 server!"'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
namespace :example_group2 do
|
100
|
+
task provision: :print_hello do
|
101
|
+
end
|
102
|
+
|
103
|
+
flow enabled: false, revert_task: :print_goodbye
|
104
|
+
task :print_hello do
|
105
|
+
sh 'echo "Hello from example group #2 server!"'
|
106
|
+
end
|
107
|
+
|
108
|
+
task :print_goodbye do
|
109
|
+
sh 'echo "Goodbye! The task at example group #2 is disabled!"'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
```
|
114
|
+
|
115
|
+
5. Now run `bow apply` and your provisioning tasks will be executed on servers
|
116
|
+
listed in `targets.json` file;
|
117
|
+
|
118
|
+
6. To find more about available commands (`ping`, `exec` etc.) type `bow -h`;
|
119
|
+
|
120
|
+
## Task flow
|
121
|
+
|
122
|
+
While that is not necessary, it can be convinient to install a `bow` gem on the
|
123
|
+
client server too. So you will be able to use a little Rake DSL enhancement
|
124
|
+
wich bring a better controll of the flow of your tasks.
|
125
|
+
|
126
|
+
The only thing you will be needed to do afterwards to enable the feature is to
|
127
|
+
require it by putting `require bow/rake` to the top of your `Rakefile`.
|
128
|
+
|
129
|
+
Now you'll be able to put this `flow` line before the task definition:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
|
133
|
+
flow run: :once, enabled: :true, revert: :revert_simple_task
|
134
|
+
task :simple_task do
|
135
|
+
# some code here ...
|
136
|
+
end
|
137
|
+
|
138
|
+
task :revert_simple_task do
|
139
|
+
# remove evertything that simple task have done >/
|
140
|
+
end
|
141
|
+
|
142
|
+
```
|
143
|
+
|
144
|
+
The 3 options are:
|
145
|
+
|
146
|
+
* `run` wich can be either `:once` or `:always`. If set to `once` the task will
|
147
|
+
be run only once on remote server;
|
148
|
+
|
149
|
+
* `enabled` wich takes a boolean value. If set to false, the task will be
|
150
|
+
disabled and won't run at all;
|
151
|
+
|
152
|
+
* `revert` wich defines a task that can revert changes done by the original
|
153
|
+
task when the original task is disabled (by `enabled: false` option). Actually
|
154
|
+
it's something similar to the down migration when dealing with databases;
|
155
|
+
|
156
|
+
|
data/Rakefile
ADDED
data/bin/bow
ADDED
data/bow.gemspec
CHANGED
@@ -1,18 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/bow/version.rb'
|
4
|
+
|
1
5
|
Gem::Specification.new do |s|
|
2
6
|
s.name = 'bow'
|
3
|
-
s.version =
|
4
|
-
s.date = '
|
5
|
-
s.summary = 'Simple provisioning
|
6
|
-
s.description = 'Simple provisioning
|
7
|
+
s.version = Bow::VERSION
|
8
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
9
|
+
s.summary = 'Simple provisioning via rake tasks'
|
10
|
+
s.description = 'Simple provisioning via rake tasks'
|
7
11
|
s.authors = ['Zinovyev Ivan']
|
8
12
|
s.email = 'zinovyev.id@gmail.com'
|
9
13
|
s.files = `git ls-files -z`.split("\x0").reject do |f|
|
10
14
|
f.match(%r{^(test|spec|features)/})
|
11
15
|
end - %w[.rubocop.yml .travis.yml appveyor.yml]
|
16
|
+
s.executables << 'bow'
|
12
17
|
s.homepage = 'https://github.com/zinovyev/bow'
|
13
18
|
s.license = 'MIT'
|
14
19
|
s.add_runtime_dependency 'rake'
|
15
20
|
s.add_development_dependency 'pry'
|
16
21
|
s.add_development_dependency 'rspec'
|
22
|
+
s.add_development_dependency 'mocha'
|
17
23
|
s.add_development_dependency 'rubocop'
|
18
24
|
end
|
data/lib/bow.rb
CHANGED
@@ -1,2 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bow/config'
|
4
|
+
require 'bow/memorable'
|
5
|
+
require 'bow/rake'
|
6
|
+
require 'bow/locker'
|
7
|
+
require 'bow/version'
|
8
|
+
require 'bow/options'
|
9
|
+
require 'bow/targets'
|
10
|
+
require 'bow/ssh/scp'
|
11
|
+
require 'bow/ssh/rsync'
|
12
|
+
require 'bow/ssh_helper'
|
13
|
+
require 'bow/application'
|
14
|
+
require 'bow/thread_pool'
|
15
|
+
require 'bow/inventory'
|
16
|
+
require 'bow/inventory_example'
|
17
|
+
require 'bow/response_formatter'
|
18
|
+
require 'bow/command'
|
19
|
+
require 'bow/commands/ping'
|
20
|
+
require 'bow/commands/exec'
|
21
|
+
require 'bow/commands/init'
|
22
|
+
require 'bow/commands/apply'
|
23
|
+
require 'bow/commands/prepare'
|
24
|
+
|
1
25
|
module Bow
|
26
|
+
BASE_DIR = File.dirname(File.dirname(__FILE__)).freeze
|
2
27
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Bow
|
4
|
+
class Application
|
5
|
+
BANNER = 'Usage: bow command arguments [options]'.freeze
|
6
|
+
COLUMN_WIDTH = 32
|
7
|
+
EMPTY_COLUMN = (' ' * COLUMN_WIDTH).freeze
|
8
|
+
|
9
|
+
attr_accessor :argv, :options, :debug
|
10
|
+
|
11
|
+
def initialize(argv)
|
12
|
+
@options = {
|
13
|
+
user: 'root',
|
14
|
+
group: 'all',
|
15
|
+
inventory: 'hosts.json'
|
16
|
+
}
|
17
|
+
@argv = argv.dup
|
18
|
+
@ssh_helpers = {}
|
19
|
+
@debug = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# rubocop:disable Lint/ShadowingOuterLocalVariable
|
23
|
+
def run
|
24
|
+
opts = OptionParser.new do |opts|
|
25
|
+
opts.banner = build_banner
|
26
|
+
options_parser.parse(opts)
|
27
|
+
end
|
28
|
+
opts.parse!(argv)
|
29
|
+
command = parse_arguments(opts)
|
30
|
+
command.run
|
31
|
+
end
|
32
|
+
# rubocop:enable Lint/ShadowingOuterLocalVariable
|
33
|
+
|
34
|
+
def inventory
|
35
|
+
return @inventory if @inventory
|
36
|
+
@inventory ||= Inventory.new
|
37
|
+
@inventory.ensure!
|
38
|
+
end
|
39
|
+
|
40
|
+
def config
|
41
|
+
Config
|
42
|
+
end
|
43
|
+
|
44
|
+
def ssh_helper(host)
|
45
|
+
conn = host.conn
|
46
|
+
@ssh_helpers[conn] ||= SshHelper.new(conn, self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def targets(user)
|
50
|
+
@targets ||= Targets.new(inventory.targetfile, user)
|
51
|
+
end
|
52
|
+
|
53
|
+
def debug?
|
54
|
+
!!@debug
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def build_banner
|
60
|
+
banner = BANNER.dup.to_s
|
61
|
+
banner << "\n\nCOMMANDS\n\n"
|
62
|
+
Command.all.each do |command|
|
63
|
+
banner << format_command(command)
|
64
|
+
end
|
65
|
+
banner << "OPTIONS\n\n"
|
66
|
+
banner
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_arguments(opts)
|
70
|
+
if argv.empty?
|
71
|
+
argv << '-h'
|
72
|
+
opts.parse!(argv)
|
73
|
+
end
|
74
|
+
build_command(argv.shift, argv)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_command(name, argv = {})
|
78
|
+
raise "Unknown command #{name}!" unless command_exists? name
|
79
|
+
Command.find(name).new(self, argv)
|
80
|
+
end
|
81
|
+
|
82
|
+
def command_exists?(name)
|
83
|
+
Command.names.include? name.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_command(command)
|
87
|
+
tail = ' ' * (32 - command.command_name.length)
|
88
|
+
str = " #{command.command_name}#{tail}#{command.description}\n"
|
89
|
+
str << " #{EMPTY_COLUMN}Usage: #{command.usage}\n\n"
|
90
|
+
end
|
91
|
+
|
92
|
+
def options_parser
|
93
|
+
@options_parser ||= Options.new(@options)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/bow/command.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
module Bow
|
6
|
+
class Command
|
7
|
+
class << self
|
8
|
+
attr_reader :all, :names
|
9
|
+
|
10
|
+
def inherited(command_class)
|
11
|
+
@all ||= []
|
12
|
+
@names ||= []
|
13
|
+
@all << command_class
|
14
|
+
@names << command_class.command_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def command_name
|
18
|
+
@command_name ||= name.to_s
|
19
|
+
.split('::')
|
20
|
+
.last
|
21
|
+
.split(/([A-Z]{1}[^A-Z]*)/)
|
22
|
+
.reject(&:empty?)
|
23
|
+
.join('_')
|
24
|
+
.downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def find(name)
|
28
|
+
index = @names.index name.to_s
|
29
|
+
@all[index] if index
|
30
|
+
end
|
31
|
+
|
32
|
+
%w[usage description].each { |m| define_method(m) { new.send m } }
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :app
|
36
|
+
|
37
|
+
def initialize(app = nil, argv = [])
|
38
|
+
@app = app
|
39
|
+
@argv = argv
|
40
|
+
end
|
41
|
+
|
42
|
+
def description
|
43
|
+
@description ||= "Command #{command_name} description"
|
44
|
+
end
|
45
|
+
|
46
|
+
def usage
|
47
|
+
@usage ||= "bow #{command_name} [args] [options]"
|
48
|
+
end
|
49
|
+
|
50
|
+
def command_name
|
51
|
+
self.class.command_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def targets
|
55
|
+
user = app.options[:user]
|
56
|
+
group = app.options[:group]
|
57
|
+
app.targets(user).hosts(group)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|