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.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bow
4
+ module Commands
5
+ class Apply < Command
6
+ def description
7
+ 'Apply provision on remote hosts.'
8
+ end
9
+
10
+ def run
11
+ ThreadPool.new do |t|
12
+ t.from_enumerable targets do |host|
13
+ result = app.ssh_helper(host).prepare_provision(PROVISION_PATH)
14
+ ResponseFormatter.pretty_print(host, result)
15
+
16
+ cmd = "'cd #{PROVISION_PATH} && rake #{host.group}:provision'"
17
+ result = app.ssh_helper(host).execute(cmd)
18
+ ResponseFormatter.pretty_print(host, result)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry'
4
+ require 'pp'
5
+
6
+ module Bow
7
+ module Commands
8
+ class Exec < Command
9
+ def description
10
+ 'Exec command on remote hosts.'
11
+ end
12
+
13
+ def usage
14
+ "bow #{command_name} command [args] [options]"
15
+ end
16
+
17
+ def run
18
+ raise ArgumentError, 'Command required!' unless @argv && !@argv&.empty?
19
+ cmd = @argv.shift
20
+ ThreadPool.new do |t|
21
+ t.from_enumerable targets do |host|
22
+ result = app.ssh_helper(host).execute(cmd)
23
+ ResponseFormatter.pretty_print(host, result)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bow
4
+ module Commands
5
+ class Init < Command
6
+ def description
7
+ 'Initialize minimal bow scaffold.'
8
+ end
9
+
10
+ def run
11
+ InventoryExample.new.init
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bow
4
+ module Commands
5
+ class Ping < Command
6
+ def description
7
+ 'Check connectivity to all given hosts.'
8
+ end
9
+
10
+ def run
11
+ @argv << 'echo pong'
12
+ Exec.new(@app, @argv).run
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bow
4
+ module Commands
5
+ class Prepare < Command
6
+ def description
7
+ 'Install RVM, Ruby and Rake on provisioned hosts'
8
+ end
9
+
10
+ def run
11
+ ThreadPool.new do |t|
12
+ t.from_enumerable targets do |host|
13
+ result = app.ssh_helper(host).prepare_provision(PROVISION_PATH)
14
+ ResponseFormatter.pretty_print(host, result)
15
+
16
+ provision_cmd = "bash #{PROVISION_PATH}/preprovision.sh"
17
+ result = app.ssh_helper(host).execute(provision_cmd)
18
+ ResponseFormatter.pretty_print(host, result)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bow
4
+ class Config
5
+ class << self
6
+ def host
7
+ @host ||= Config.new(:host, HOST_BASE_DIR)
8
+ end
9
+
10
+ def guest
11
+ @guest ||= Config.new(:guest, GUEST_BASE_DIR)
12
+ end
13
+
14
+ def guest_from_host
15
+ @guest_from_host ||= Config.new(:guest, '~')
16
+ end
17
+ end
18
+
19
+ HOST_BASE_DIR = File.dirname(File.dirname(File.dirname(__FILE__)))
20
+
21
+ GUEST_BASE_DIR = "#{Dir.home}/.bow"
22
+
23
+ GUEST_FROM_HOST_BASE_DIR = '~/.bow'
24
+
25
+ PATHS = {
26
+ guest: {
27
+ base_dir: '%s',
28
+ rake_dir: '%s/rake',
29
+ history: '%s/.history',
30
+ pre_script: '%s/provision.sh'
31
+ },
32
+ host: {
33
+ base_dir: '%s',
34
+ pre_script: '%s/src/provision.sh'
35
+ }
36
+ }.freeze
37
+
38
+ def initialize(type, base_dir)
39
+ @type = type
40
+ @base_dir = base_dir
41
+ end
42
+
43
+ def get(name)
44
+ name.to_sym
45
+ value = PATHS[@type][name]
46
+ substitude_dir(value)
47
+ end
48
+
49
+ def substitude_dir(orig)
50
+ format(orig, @base_dir) if orig
51
+ end
52
+
53
+ def [](name)
54
+ get(name)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/file_list'
4
+
5
+ module Bow
6
+ class Inventory
7
+ DEFAULT_RAKEFILES = [
8
+ 'Rakefile',
9
+ 'rakefile',
10
+ 'Rakefile.rb',
11
+ 'rakefile.rb'
12
+ ].freeze
13
+
14
+ DEFAULT_TARGETFILES = [
15
+ # 'bow.yaml',
16
+ # 'bow.rb',
17
+ 'targets.json'
18
+ ].freeze
19
+
20
+ attr_reader :rakefile, :targetfile, :location
21
+
22
+ def initialize
23
+ @rakefiles = DEFAULT_RAKEFILES.dup
24
+ @targetfiles = DEFAULT_TARGETFILES.dup
25
+ @original_dir = Dir.pwd
26
+ end
27
+
28
+ def parse
29
+ return if @inventory_loaded
30
+ rakefile, targetfile, @location = inventory_location
31
+ @rakefile = File.join(@location, rakefile) if rakefile
32
+ @targetfile = File.join(@location, targetfile) if targetfile
33
+ @inventory_loaded = true
34
+ end
35
+
36
+ def valid?
37
+ parse
38
+ !!(rakefile && targetfile && location)
39
+ end
40
+
41
+ def ensure!
42
+ parse
43
+ [
44
+ ['Rakefile', @rakefile, @rakefiles],
45
+ ['Target file', @targetfile, @targetfiles]
46
+ ].each do |name, file, variants|
47
+ unless file
48
+ raise "No #{name} found (looking for: #{variants.join(', ')})"
49
+ end
50
+ end
51
+ self
52
+ end
53
+
54
+ private
55
+
56
+ def inventory_location
57
+ reset_path do
58
+ here = @original_dir
59
+ until (files = inspect_dir)
60
+ break if here == '/'
61
+ Dir.chdir('..')
62
+ here = Dir.pwd
63
+ end
64
+ [files, here].flatten
65
+ end
66
+ end
67
+
68
+ def reset_path
69
+ yield
70
+ ensure
71
+ Dir.chdir(@original_dir)
72
+ end
73
+
74
+ def inspect_dir
75
+ rakefile = detect_files(@rakefiles)
76
+ targetfile = detect_files(@targetfiles)
77
+ [rakefile, targetfile]
78
+ end
79
+
80
+ def detect_files(variants)
81
+ variants.detect { |fn| File.exist?(fn) }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ module Bow
5
+ class InventoryExample
6
+ TARGETING_EXAMPLE = {
7
+ 'example_group1' => [
8
+ '192.168.50.27',
9
+ '192.168.50.37'
10
+ ],
11
+ 'example_group2' => [
12
+ '192.168.50.47',
13
+ '192.168.50.57'
14
+ ]
15
+ }.freeze
16
+
17
+ RAKEFILE_EXAMPLE = <<-RAKEFILE.gsub(/^ {6}/, '').freeze
18
+ require 'bow/rake'
19
+
20
+ Rake.application.options.trace_rules = true
21
+
22
+ PROVISION_DIR = '/tmp/rake_provision'
23
+
24
+ namespace :example_group1 do
25
+ task provision: :print_hello do
26
+ end
27
+
28
+ flow run: :once
29
+ task :print_hello do
30
+ sh 'echo "Hello from example group #1 server!"'
31
+ end
32
+ end
33
+
34
+ namespace :example_group2 do
35
+ task provision: :print_hello do
36
+ end
37
+
38
+ flow enabled: false, revert_task: :print_goodbye
39
+ task :print_hello do
40
+ sh 'echo "Hello from example group #2 server!"'
41
+ end
42
+
43
+ task :print_goodbye do
44
+ sh 'echo "Goodbye! The task at example group #2 is disabled!"'
45
+ end
46
+ end
47
+ RAKEFILE
48
+
49
+ def init
50
+ raise 'Can not init. Directory not empty!' unless Dir.empty?(Dir.pwd)
51
+ {
52
+ 'Rakefile' => RAKEFILE_EXAMPLE,
53
+ 'targets.json' => JSON.pretty_generate(TARGETING_EXAMPLE)
54
+ }.each do |targ, code|
55
+ f = File.open(targ, 'w')
56
+ f.write(code)
57
+ f.close
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,180 @@
1
+ require 'fileutils'
2
+ require 'objspace'
3
+
4
+ # Locker is a task state manager.
5
+ # It tracks the task status and saves it to the history file.
6
+ module Bow
7
+ class Locker
8
+ class << self
9
+ attr_accessor :file_path
10
+
11
+ def load
12
+ @instance ||= new
13
+ end
14
+
15
+ def load!
16
+ @instance = new
17
+ end
18
+ end
19
+
20
+ SEPARATOR = ";\t".freeze
21
+ LINE_SEP = "\n".freeze
22
+ NOT_FOUND = :not_found
23
+
24
+ attr_accessor :runtime_cache
25
+
26
+ def initialize
27
+ @file_path = self.class.file_path || Config.guest[:history]
28
+ @modified = false
29
+ @file_opened = false
30
+ @runtime_cache = {}
31
+ end
32
+
33
+ def applied?(task)
34
+ record = parse(find(task))
35
+ !!(record && record[1])
36
+ end
37
+
38
+ def reverted?(task)
39
+ record = parse(find(task))
40
+ !!(record && record[2])
41
+ end
42
+
43
+ def apply(task)
44
+ return if applied?(task)
45
+ add(task, true, reverted?(task))
46
+ end
47
+
48
+ def revert(task)
49
+ return if reverted?(task)
50
+ add(task, applied?(task), true)
51
+ end
52
+
53
+ def parse(meta)
54
+ return false if meta[:record] == NOT_FOUND
55
+ record = meta[:record].split(SEPARATOR)
56
+ record.map do |v|
57
+ v.strip!
58
+ case v
59
+ when 'true' then true
60
+ when 'false' then false
61
+ else v
62
+ end
63
+ end
64
+ end
65
+
66
+ def reset(task)
67
+ meta = find(task)
68
+ reset_cache(task)
69
+ return if meta[:record] == NOT_FOUND
70
+ file.seek(meta[:first_c], IO::SEEK_SET)
71
+ str_len = meta[:last_c] - meta[:first_c] + 1
72
+ file.write ' ' * str_len
73
+ end
74
+
75
+ def find(task)
76
+ cached = from_cache(task)
77
+ return cached if cached
78
+ result = pure_find(task)
79
+ result
80
+ end
81
+
82
+ def from_cache(task)
83
+ @runtime_cache[task]
84
+ end
85
+
86
+ def to_cache(task, result)
87
+ @runtime_cache[task] = result
88
+ end
89
+
90
+ def reset_cache(task)
91
+ @runtime_cache[task] = nil
92
+ end
93
+
94
+ # rubocop:disable Metrics/MethodLength
95
+ # rubocop:disable Style/PerlBackrefs
96
+ def pure_find(task)
97
+ file.rewind
98
+ current_line = ''
99
+ first_c = 0
100
+ last_c = 0
101
+ file.each_char.with_index do |c, idx|
102
+ if c == LINE_SEP
103
+ current_line =~ /^([^\s]+)#{SEPARATOR}/
104
+ to_cache(task, record: $1, first_c: first_c, last_c: last_c)
105
+ if task == $1
106
+ return { record: current_line, first_c: first_c, last_c: last_c }
107
+ end
108
+ current_line = ''
109
+ first_c = idx + 1
110
+ last_c = first_c
111
+ else
112
+ current_line << c
113
+ last_c = idx
114
+ end
115
+ end
116
+ { record: NOT_FOUND, first_c: nil, last_c: nil }
117
+ end
118
+ # rubocop:enable Style/PerlBackrefs
119
+ # rubocop:enable Metrics/MethodLength
120
+
121
+ def add(task, applied = false, reverted = false)
122
+ reset(task)
123
+ file.seek(0, IO::SEEK_END)
124
+ write(task, applied, reverted)
125
+ self
126
+ end
127
+
128
+ def write(task, applied = false, reverted = false)
129
+ reset_cache(task)
130
+ record = [task, applied, reverted].join(SEPARATOR)
131
+ first_c = file.tell
132
+ file.puts(record)
133
+ last_c = first_c + record.size - 1
134
+ to_cache(task, record: record, first_c: first_c, last_c: last_c)
135
+ @modified = true
136
+ self
137
+ end
138
+
139
+ def flush
140
+ return unless @modified && @file_opened
141
+ file.close
142
+ @file_opened = false
143
+ @modified = false
144
+ self
145
+ end
146
+
147
+ def compact
148
+ compressed = ''
149
+ file.each do |l|
150
+ compressed << l unless l[0] == ' '
151
+ end
152
+ file.rewind
153
+ file.write(compressed)
154
+ file.truncate(compressed.size)
155
+ @runtime_cache = {}
156
+ @modified = true
157
+ self
158
+ end
159
+
160
+ def empty!
161
+ file.rewind
162
+ file.truncate(0)
163
+ @runtime_cache = {}
164
+ end
165
+
166
+ def file
167
+ return @file if @file && @file_opened
168
+ ensure_file(@file_path)
169
+ @file = File.open(@file_path, 'r+')
170
+ @file_opened = true
171
+ file
172
+ end
173
+
174
+ def ensure_file(name)
175
+ return if File.exist?(name)
176
+ FileUtils.mkdir_p(File.dirname(name))
177
+ FileUtils.touch(name)
178
+ end
179
+ end
180
+ end