bow 0.0.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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