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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48aeceb9c881c29b019fd6940b0245c65b125bdd
4
- data.tar.gz: e3688953ea94b0537e3eb644eec7912941b9e50a
3
+ metadata.gz: 6f786de3bf3a07e01c6e1163fb70958356e0c219
4
+ data.tar.gz: 1c04ee09cd9e2262f217ac3a21530a6b39ff6546
5
5
  SHA512:
6
- metadata.gz: 3d55431cd057ff1f88eb995180e2956374f5b00edce9eab0cd80cb610caeb103d1551f4bcbd66f200f92e8b6cdfb246ace58ec916051388ba0fc975ca3219fbd
7
- data.tar.gz: 7928f3029439c1a3a3b883cc284cc4a901c3b1d4428b12dfb6b367ef7ee953e3cc90a9dfbefe280f7f9582d6a935017a01488cdd24b3fafd03df0536b7b71059
6
+ metadata.gz: fae7064eb48e541ac88fd12512c99e13b49dc23a964c41f9391cd3b59046342a4ad3713b2951aa4befc33995713d3d360ee32d0ca39ac49b7615f6199be37890
7
+ data.tar.gz: 40251d5f6b674fee4d5429b67d3ade045867a80f0bb68b62d62ceab2e6b725163ca82517a51ca0d8d2c73902a31d53a6d635a64e9f64c84b0178b8107ef47a18
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *gem
11
+ .rspec_status
12
+ test
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gemspec
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.
@@ -0,0 +1,156 @@
1
+ # bow
2
+
3
+ Automate your infrastructure provisioning and configuration with Rake.
4
+
5
+ [![Build Status](https://travis-ci.org/zinovyev/bow.svg?branch=master)](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
+
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec'
4
+ require 'rake/testtask'
5
+
6
+ task :test do
7
+ exec('bundle exec rspec && bundle exec rubocop')
8
+ end
9
+
10
+ desc 'Run tests'
11
+ task default: :test
data/bin/bow ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bow'
5
+
6
+ Bow::Application.new(ARGV).run
@@ -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 = '0.0.0'
4
- s.date = '2017-09-09'
5
- s.summary = 'Simple provisioning with rake tasks'
6
- s.description = 'Simple provisioning with rake tasks'
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
@@ -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