runbook 0.14.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dockerignore +17 -0
- data/.gitignore +4 -0
- data/.ruby-version +1 -1
- data/.travis.yml +23 -7
- data/Appraisals +8 -0
- data/CHANGELOG.md +71 -0
- data/README.md +159 -25
- data/Rakefile +7 -1
- data/TODO.md +316 -46
- data/dockerfiles/Dockerfile-runbook +18 -0
- data/dockerfiles/Dockerfile-sshd +4 -0
- data/{samples → examples}/hooks_runbook.rb +0 -0
- data/{samples → examples}/layout_runbook.rb +0 -0
- data/{samples → examples}/restart_nginx.rb +0 -0
- data/{samples → examples}/simple_runbook.rb +0 -0
- data/examples/suppress_capture_output.rb +47 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/activesupport_5.gemfile +7 -0
- data/gemfiles/activesupport_6.gemfile +7 -0
- data/lib/runbook.rb +28 -6
- data/lib/runbook/airbrussh_context.rb +25 -0
- data/lib/runbook/cli.rb +17 -13
- data/lib/runbook/configuration.rb +7 -1
- data/lib/runbook/entities/book.rb +2 -2
- data/lib/runbook/entities/section.rb +2 -2
- data/lib/runbook/entities/setup.rb +7 -0
- data/lib/runbook/entities/step.rb +2 -2
- data/lib/runbook/entity.rb +7 -5
- data/lib/runbook/extensions/add.rb +1 -0
- data/lib/runbook/extensions/sections.rb +6 -2
- data/lib/runbook/extensions/setup.rb +17 -0
- data/lib/runbook/extensions/ssh_config.rb +2 -0
- data/lib/runbook/extensions/statements.rb +6 -1
- data/lib/runbook/extensions/steps.rb +12 -2
- data/lib/runbook/generators/project/project.rb +32 -9
- data/lib/runbook/helpers/tmux_helper.rb +6 -4
- data/lib/runbook/node.rb +10 -0
- data/lib/runbook/run.rb +14 -8
- data/lib/runbook/runner.rb +2 -0
- data/lib/runbook/runs/ssh_kit.rb +20 -13
- data/lib/runbook/statement.rb +0 -2
- data/lib/runbook/statements/ask.rb +3 -2
- data/lib/runbook/statements/assert.rb +11 -2
- data/lib/runbook/toolbox.rb +3 -9
- data/lib/runbook/util/repo.rb +4 -3
- data/lib/runbook/util/runbook.rb +4 -0
- data/lib/runbook/util/stored_pose.rb +4 -3
- data/lib/runbook/version.rb +1 -1
- data/lib/runbook/views/markdown.rb +15 -7
- data/runbook.gemspec +12 -8
- metadata +108 -29
@@ -1,9 +1,11 @@
|
|
1
1
|
module Runbook::Helpers
|
2
2
|
module TmuxHelper
|
3
|
+
FILE_PERMISSIONS = 0600
|
4
|
+
|
3
5
|
def setup_layout(structure, runbook_title:)
|
4
6
|
_remove_stale_layouts
|
5
7
|
layout_file = _layout_file(_slug(runbook_title))
|
6
|
-
if File.
|
8
|
+
if File.exist?(layout_file)
|
7
9
|
stored_layout = ::YAML::load_file(layout_file)
|
8
10
|
if _all_panes_exist?(stored_layout)
|
9
11
|
return stored_layout
|
@@ -11,14 +13,14 @@ module Runbook::Helpers
|
|
11
13
|
end
|
12
14
|
|
13
15
|
_setup_layout(structure).tap do |layout_panes|
|
14
|
-
File.open(layout_file, 'w') do |f|
|
16
|
+
File.open(layout_file, 'w', FILE_PERMISSIONS) do |f|
|
15
17
|
f.write(layout_panes.to_yaml)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
22
|
def send_keys(command, target)
|
21
|
-
`tmux send-keys -t #{target} #{_pager_escape_sequence}
|
23
|
+
`tmux send-keys -t #{target} #{_pager_escape_sequence} #{Shellwords.escape(command)} C-m`
|
22
24
|
end
|
23
25
|
|
24
26
|
def kill_all_panes(layout_panes)
|
@@ -41,7 +43,7 @@ module Runbook::Helpers
|
|
41
43
|
end
|
42
44
|
|
43
45
|
def _slug(title)
|
44
|
-
title.titleize.gsub(
|
46
|
+
title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
|
45
47
|
end
|
46
48
|
|
47
49
|
def _all_panes_exist?(stored_layout)
|
data/lib/runbook/node.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Runbook
|
2
2
|
class Node
|
3
|
+
attr_accessor :parent
|
4
|
+
|
3
5
|
def initialize
|
4
6
|
raise "Should not be initialized"
|
5
7
|
end
|
@@ -19,5 +21,13 @@ module Runbook
|
|
19
21
|
def visited?
|
20
22
|
@visited
|
21
23
|
end
|
24
|
+
|
25
|
+
def parent_entity
|
26
|
+
node = self
|
27
|
+
while(node && !node.is_a?(Runbook::Entity))
|
28
|
+
node = node.parent
|
29
|
+
end
|
30
|
+
node
|
31
|
+
end
|
22
32
|
end
|
23
33
|
end
|
data/lib/runbook/run.rb
CHANGED
@@ -36,6 +36,10 @@ module Runbook
|
|
36
36
|
metadata[:toolbox].output("Section #{metadata[:position]}: #{object.title}\n\n")
|
37
37
|
end
|
38
38
|
|
39
|
+
def runbook__entities__setup(object, metadata)
|
40
|
+
metadata[:toolbox].output("Setup:\n\n")
|
41
|
+
end
|
42
|
+
|
39
43
|
def runbook__entities__step(object, metadata)
|
40
44
|
toolbox = metadata[:toolbox]
|
41
45
|
title = " #{object.title}".rstrip
|
@@ -66,11 +70,12 @@ module Runbook
|
|
66
70
|
|
67
71
|
if metadata[:noop]
|
68
72
|
default_msg = default ? " (default: #{default})" : ""
|
69
|
-
|
73
|
+
echo_msg = object.echo ? "" : " (echo: false)"
|
74
|
+
metadata[:toolbox].output("[NOOP] Ask: #{object.prompt} (store in: #{object.into})#{default_msg}#{echo_msg}")
|
70
75
|
return
|
71
76
|
end
|
72
77
|
|
73
|
-
result = metadata[:toolbox].ask(object.prompt, default: default)
|
78
|
+
result = metadata[:toolbox].ask(object.prompt, default: default, echo: object.echo)
|
74
79
|
|
75
80
|
target = object.parent.dsl
|
76
81
|
target.singleton_class.class_eval { attr_accessor object.into }
|
@@ -152,7 +157,7 @@ module Runbook
|
|
152
157
|
next_index = metadata[:index] + 1
|
153
158
|
parent_items = object.parent.items
|
154
159
|
remaining_items = parent_items.slice!(next_index..-1)
|
155
|
-
object.parent.dsl.instance_exec(object, metadata, &object.block)
|
160
|
+
object.parent.dsl.instance_exec(object, metadata, self, &object.block)
|
156
161
|
parent_items[next_index..-1].each { |item| item.dynamic! }
|
157
162
|
parent_items.push(*remaining_items)
|
158
163
|
end
|
@@ -228,9 +233,9 @@ module Runbook
|
|
228
233
|
return
|
229
234
|
when :skip
|
230
235
|
position = metadata[:position]
|
231
|
-
|
232
|
-
new_step =
|
233
|
-
start_at =
|
236
|
+
split_position = position.split(".")
|
237
|
+
new_step = (split_position.pop.to_i + 1).to_s
|
238
|
+
start_at = (split_position << new_step).join(".")
|
234
239
|
metadata[:start_at] = start_at
|
235
240
|
when :jump
|
236
241
|
result = toolbox.ask("What position would you like to jump to?")
|
@@ -253,7 +258,7 @@ module Runbook
|
|
253
258
|
:after,
|
254
259
|
Runbook::Entities::Book,
|
255
260
|
) do |object, metadata|
|
256
|
-
next if metadata[:noop] || metadata[:layout_panes].none?
|
261
|
+
next if metadata[:noop] || metadata[:layout_panes].none? || metadata[:keep_panes]
|
257
262
|
if metadata[:auto]
|
258
263
|
metadata[:toolbox].output("Killing all opened tmux panes...")
|
259
264
|
kill_all_panes(metadata[:layout_panes])
|
@@ -273,7 +278,8 @@ module Runbook
|
|
273
278
|
:after,
|
274
279
|
Runbook::Statement,
|
275
280
|
) do |object, metadata|
|
276
|
-
if object.parent.is_a?(Runbook::Entities::Step)
|
281
|
+
if object.parent.is_a?(Runbook::Entities::Step) ||
|
282
|
+
object.parent.is_a?(Runbook::Entities::Setup)
|
277
283
|
if object.parent.items.last == object
|
278
284
|
metadata[:toolbox].output("\n")
|
279
285
|
end
|
data/lib/runbook/runner.rb
CHANGED
@@ -11,6 +11,7 @@ module Runbook
|
|
11
11
|
noop: false,
|
12
12
|
auto: false,
|
13
13
|
paranoid: true,
|
14
|
+
keep_panes: false,
|
14
15
|
start_at: "0"
|
15
16
|
)
|
16
17
|
run = "Runbook::Runs::#{run.to_s.camelize}".constantize
|
@@ -21,6 +22,7 @@ module Runbook
|
|
21
22
|
paranoid: Util::Glue.new(paranoid),
|
22
23
|
start_at: Util::Glue.new(start_at || "0"),
|
23
24
|
toolbox: Util::Glue.new(toolbox),
|
25
|
+
keep_panes: keep_panes,
|
24
26
|
book_title: book.title,
|
25
27
|
}).
|
26
28
|
merge(Runbook::Entities::Book.initial_run_metadata).
|
data/lib/runbook/runs/ssh_kit.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
module Runbook::Runs
|
2
2
|
module SSHKit
|
3
3
|
include Runbook::Run
|
4
|
-
extend Runbook::Helpers::SSHKitHelper
|
5
4
|
|
6
5
|
def self.included(base)
|
7
6
|
base.extend(ClassMethods)
|
8
7
|
end
|
9
8
|
|
10
9
|
module ClassMethods
|
10
|
+
include Runbook::Helpers::SSHKitHelper
|
11
|
+
|
12
|
+
def runbook__entities__step(object, metadata)
|
13
|
+
airbrussh_context = Runbook.configuration._airbrussh_context
|
14
|
+
airbrussh_context.set_current_task_name(object.title)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
11
18
|
def runbook__statements__assert(object, metadata)
|
12
19
|
cmd_ssh_config = find_ssh_config(object, :cmd_ssh_config)
|
13
20
|
|
@@ -19,18 +26,18 @@ module Runbook::Runs
|
|
19
26
|
if object.timeout > 0 || object.attempts > 0
|
20
27
|
timeout_msg = object.timeout > 0 ? "#{object.timeout} second(s)" : nil
|
21
28
|
attempts_msg = object.attempts > 0 ? "#{object.attempts} attempts" : nil
|
22
|
-
|
23
|
-
metadata[:toolbox].output(
|
24
|
-
if object.
|
25
|
-
object.
|
26
|
-
object.
|
29
|
+
abort_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, abort..."
|
30
|
+
metadata[:toolbox].output(abort_msg)
|
31
|
+
if object.abort_statement
|
32
|
+
object.abort_statement.parent = object.parent
|
33
|
+
object.abort_statement.run(self, metadata.dup)
|
27
34
|
end
|
28
35
|
metadata[:toolbox].output("and exit")
|
29
36
|
end
|
30
37
|
return
|
31
38
|
end
|
32
39
|
|
33
|
-
|
40
|
+
should_abort = false
|
34
41
|
test_args = ssh_kit_command(object.cmd, raw: object.cmd_raw)
|
35
42
|
test_options = ssh_kit_command_options(cmd_ssh_config)
|
36
43
|
|
@@ -39,12 +46,12 @@ module Runbook::Runs
|
|
39
46
|
count = object.attempts
|
40
47
|
while !(test(*test_args, test_options))
|
41
48
|
if ((count -= 1) == 0)
|
42
|
-
|
49
|
+
should_abort = true
|
43
50
|
break
|
44
51
|
end
|
45
52
|
|
46
53
|
if (object.timeout > 0 && Time.now - time > object.timeout)
|
47
|
-
|
54
|
+
should_abort = true
|
48
55
|
break
|
49
56
|
end
|
50
57
|
|
@@ -52,12 +59,12 @@ module Runbook::Runs
|
|
52
59
|
end
|
53
60
|
end
|
54
61
|
|
55
|
-
if
|
62
|
+
if should_abort
|
56
63
|
error_msg = "Error! Assertion `#{object.cmd}` failed"
|
57
64
|
metadata[:toolbox].error(error_msg)
|
58
|
-
if object.
|
59
|
-
object.
|
60
|
-
object.
|
65
|
+
if object.abort_statement
|
66
|
+
object.abort_statement.parent = object.parent
|
67
|
+
object.abort_statement.run(self, metadata.dup)
|
61
68
|
end
|
62
69
|
raise Runbook::Runner::ExecutionError, error_msg
|
63
70
|
end
|
data/lib/runbook/statement.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Runbook::Statements
|
2
2
|
class Ask < Runbook::Statement
|
3
|
-
attr_reader :prompt, :into, :default
|
3
|
+
attr_reader :prompt, :into, :default, :echo
|
4
4
|
|
5
|
-
def initialize(prompt, into:, default: nil)
|
5
|
+
def initialize(prompt, into:, default: nil, echo: true)
|
6
6
|
@prompt = prompt
|
7
7
|
@into = into
|
8
8
|
@default = default
|
9
|
+
@echo = echo
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
@@ -2,7 +2,12 @@ module Runbook::Statements
|
|
2
2
|
class Assert < Runbook::Statement
|
3
3
|
attr_reader :cmd, :cmd_ssh_config, :cmd_raw
|
4
4
|
attr_reader :interval, :timeout, :attempts
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :abort_statement
|
6
|
+
|
7
|
+
def timeout_statement
|
8
|
+
Runbook.deprecator.deprecation_warning(:timeout_statement, :abort_statement)
|
9
|
+
@abort_statement
|
10
|
+
end
|
6
11
|
|
7
12
|
def initialize(
|
8
13
|
cmd,
|
@@ -11,6 +16,7 @@ module Runbook::Statements
|
|
11
16
|
interval: 1,
|
12
17
|
timeout: 0,
|
13
18
|
attempts: 0,
|
19
|
+
abort_statement: nil,
|
14
20
|
timeout_statement: nil
|
15
21
|
)
|
16
22
|
@cmd = cmd
|
@@ -19,7 +25,10 @@ module Runbook::Statements
|
|
19
25
|
@interval = interval
|
20
26
|
@timeout = timeout
|
21
27
|
@attempts = attempts
|
22
|
-
|
28
|
+
if timeout_statement
|
29
|
+
Runbook.deprecator.deprecation_warning(:timeout_statement, :abort_statement)
|
30
|
+
end
|
31
|
+
@abort_statement = abort_statement || timeout_statement
|
23
32
|
end
|
24
33
|
end
|
25
34
|
end
|
data/lib/runbook/toolbox.rb
CHANGED
@@ -6,8 +6,8 @@ module Runbook
|
|
6
6
|
@prompt = TTY::Prompt.new
|
7
7
|
end
|
8
8
|
|
9
|
-
def ask(msg, default: nil)
|
10
|
-
prompt.ask(msg, default: default)
|
9
|
+
def ask(msg, default: nil, echo: true)
|
10
|
+
prompt.ask(msg, default: default, echo: echo)
|
11
11
|
end
|
12
12
|
|
13
13
|
def expand(msg, choices)
|
@@ -15,13 +15,7 @@ module Runbook
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def yes?(msg)
|
18
|
-
|
19
|
-
prompt.yes?(msg)
|
20
|
-
rescue TTY::Prompt::ConversionError
|
21
|
-
err_msg = "Unknown input: Please type 'y' or 'n'."
|
22
|
-
warn(err_msg)
|
23
|
-
retry
|
24
|
-
end
|
18
|
+
prompt.yes?(msg)
|
25
19
|
end
|
26
20
|
|
27
21
|
def output(msg)
|
data/lib/runbook/util/repo.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Runbook::Util
|
2
2
|
module Repo
|
3
3
|
FILE_ID = "data"
|
4
|
+
FILE_PERMISSIONS = 0600
|
4
5
|
|
5
6
|
def self.load(metadata)
|
6
7
|
title = metadata[:book_title]
|
7
8
|
file = _file(title)
|
8
|
-
if File.
|
9
|
+
if File.exist?(file)
|
9
10
|
msg = "Repo file #{file} detected. Loading previous state..."
|
10
11
|
metadata[:toolbox].output(msg)
|
11
12
|
metadata[:repo] = ::YAML::load_file(file)
|
@@ -13,7 +14,7 @@ module Runbook::Util
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.save(repo, book_title:)
|
16
|
-
File.open(_file(book_title), 'w') do |f|
|
17
|
+
File.open(_file(book_title), 'w', FILE_PERMISSIONS) do |f|
|
17
18
|
f.write(repo.to_yaml)
|
18
19
|
end
|
19
20
|
end
|
@@ -27,7 +28,7 @@ module Runbook::Util
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def self._slug(title)
|
30
|
-
title.titleize.gsub(
|
31
|
+
title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
|
31
32
|
end
|
32
33
|
|
33
34
|
def self.register_save_repo_hook(base)
|
data/lib/runbook/util/runbook.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
module Runbook::Util
|
2
2
|
module StoredPose
|
3
3
|
FILE_ID = "current_position"
|
4
|
+
FILE_PERMISSIONS = 0600
|
4
5
|
|
5
6
|
def self.load(metadata)
|
6
7
|
title = metadata[:book_title]
|
7
8
|
file = _file(title)
|
8
|
-
if File.
|
9
|
+
if File.exist?(file)
|
9
10
|
::YAML::load_file(file)
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.save(repo, book_title:)
|
14
|
-
File.open(_file(book_title), 'w') do |f|
|
15
|
+
File.open(_file(book_title), 'w', FILE_PERMISSIONS) do |f|
|
15
16
|
f.write(repo.to_yaml)
|
16
17
|
end
|
17
18
|
end
|
@@ -25,7 +26,7 @@ module Runbook::Util
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def self._slug(title)
|
28
|
-
title.titleize.gsub(
|
29
|
+
title.titleize.gsub(/[\/\s]+/, "").underscore.dasherize
|
29
30
|
end
|
30
31
|
|
31
32
|
def self.register_save_pose_hook(base)
|
data/lib/runbook/version.rb
CHANGED
@@ -13,6 +13,14 @@ module Runbook::Views
|
|
13
13
|
output << "#{heading} #{metadata[:index]+1}. #{object.title}\n\n"
|
14
14
|
end
|
15
15
|
|
16
|
+
def self.runbook__entities__setup(object, output, metadata)
|
17
|
+
output << "[] #{object.title}\n\n"
|
18
|
+
|
19
|
+
ssh_config = find_ssh_config(object)
|
20
|
+
ssh_config_output = render_ssh_config_output(ssh_config)
|
21
|
+
output << "#{ssh_config_output}\n" unless ssh_config_output.empty?
|
22
|
+
end
|
23
|
+
|
16
24
|
def self.runbook__entities__step(object, output, metadata)
|
17
25
|
output << "#{metadata[:index]+1}. [] #{object.title}\n\n"
|
18
26
|
|
@@ -23,7 +31,7 @@ module Runbook::Views
|
|
23
31
|
|
24
32
|
def self.runbook__statements__ask(object, output, metadata)
|
25
33
|
default_msg = object.default ? " (default: #{object.default})" : ""
|
26
|
-
output << " #{object.prompt} into
|
34
|
+
output << " #{object.prompt} into `#{object.into}`#{default_msg}\n\n"
|
27
35
|
end
|
28
36
|
|
29
37
|
def self.runbook__statements__assert(object, output, metadata)
|
@@ -31,21 +39,21 @@ module Runbook::Views
|
|
31
39
|
if object.timeout > 0 || object.attempts > 0
|
32
40
|
timeout_msg = object.timeout > 0 ? "#{object.timeout} second(s)" : nil
|
33
41
|
attempts_msg = object.attempts > 0 ? "#{object.attempts} attempts" : nil
|
34
|
-
|
35
|
-
output << " #{
|
36
|
-
if object.
|
37
|
-
object.
|
42
|
+
abort_msg = "after #{[timeout_msg, attempts_msg].compact.join(" or ")}, abort..."
|
43
|
+
output << " #{abort_msg}\n\n"
|
44
|
+
if object.abort_statement
|
45
|
+
object.abort_statement.render(self, output, metadata.dup)
|
38
46
|
end
|
39
47
|
output << " and exit\n\n"
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
43
51
|
def self.runbook__statements__capture(object, output, metadata)
|
44
|
-
output << " capture: `#{object.cmd}` into
|
52
|
+
output << " capture: `#{object.cmd}` into `#{object.into}`\n\n"
|
45
53
|
end
|
46
54
|
|
47
55
|
def self.runbook__statements__capture_all(object, output, metadata)
|
48
|
-
output << " capture_all: `#{object.cmd}` into
|
56
|
+
output << " capture_all: `#{object.cmd}` into `#{object.into}`\n\n"
|
49
57
|
end
|
50
58
|
|
51
59
|
def self.runbook__statements__command(object, output, metadata)
|
data/runbook.gemspec
CHANGED
@@ -31,19 +31,23 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
spec.add_runtime_dependency
|
35
|
-
spec.add_runtime_dependency "method_source", "~> 0
|
36
|
-
spec.add_runtime_dependency "
|
34
|
+
spec.add_runtime_dependency "activesupport", ">= 5.0.1.x", "< 7.0"
|
35
|
+
spec.add_runtime_dependency "method_source", "~> 1.0"
|
36
|
+
spec.add_runtime_dependency "ruby2_keywords", "~> 0.0.4"
|
37
|
+
spec.add_runtime_dependency "sshkit", "1.21.0"
|
37
38
|
spec.add_runtime_dependency "sshkit-sudo", "~> 0.1"
|
38
|
-
spec.add_runtime_dependency "airbrussh", "~> 1.
|
39
|
+
spec.add_runtime_dependency "airbrussh", "~> 1.4"
|
39
40
|
spec.add_runtime_dependency "thor", "~> 0.20"
|
40
41
|
spec.add_runtime_dependency "tty-progressbar", "~> 0.14"
|
41
|
-
spec.add_runtime_dependency "tty-prompt", "~> 0.
|
42
|
+
spec.add_runtime_dependency "tty-prompt", "~> 0.20"
|
42
43
|
|
44
|
+
spec.add_development_dependency "appraisal", "~> 2.2"
|
43
45
|
spec.add_development_dependency "aruba", "~> 0.14"
|
44
|
-
spec.add_development_dependency "bundler", "~>
|
45
|
-
spec.add_development_dependency "
|
46
|
+
spec.add_development_dependency "bundler", "~> 2.2"
|
47
|
+
spec.add_development_dependency "bcrypt_pbkdf", ">= 1.0", "< 2.0"
|
48
|
+
spec.add_development_dependency "ed25519", ">= 1.2", "< 2.0"
|
49
|
+
spec.add_development_dependency "pry", "~> 0.13"
|
46
50
|
spec.add_development_dependency "pry-byebug", "~> 3.6"
|
47
|
-
spec.add_development_dependency "rake", "~>
|
51
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
48
52
|
spec.add_development_dependency "rspec", "~> 3.0"
|
49
53
|
end
|