really 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +27 -0
- data/Rakefile +8 -0
- data/bin/really +5 -0
- data/lib/really.rb +26 -0
- data/lib/really/cli.rb +22 -0
- data/lib/really/command.rb +28 -0
- data/lib/really/commands.rb +15 -0
- data/lib/really/commands/basic_commands.rb +11 -0
- data/lib/really/commands/file_transfer_commands.rb +11 -0
- data/lib/really/commands/package_management.rb +37 -0
- data/lib/really/commands/rendering_commands.rb +13 -0
- data/lib/really/commands/text_file_commands.rb +17 -0
- data/lib/really/dependency_graph.rb +30 -0
- data/lib/really/drivers.rb +4 -0
- data/lib/really/drivers/driver_base.rb +64 -0
- data/lib/really/drivers/local.rb +36 -0
- data/lib/really/drivers/ssh.rb +145 -0
- data/lib/really/file_transfer_command.rb +42 -0
- data/lib/really/helpers/rendering_helper.rb +21 -0
- data/lib/really/logger.rb +72 -0
- data/lib/really/role.rb +34 -0
- data/lib/really/script.rb +103 -0
- data/lib/really/task.rb +60 -0
- data/lib/really/version.rb +3 -0
- data/really.gemspec +30 -0
- data/really.rb +76 -0
- data/test/minitest_helper.rb +4 -0
- data/test/test_really.rb +11 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3faf3d870217edbb133f4668240d7cb983161efd
|
4
|
+
data.tar.gz: a0ae4da9a06b9ac6830de179c1eebbccd90e65b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d7296fa8668ee667c0dc25c0039e7bf1469f4c11e5e9928eef0ec86b6d36abfe34b5714901940e56ed9453723cfa4812f012ea1a82f238c0ab8df8ce33002fff
|
7
|
+
data.tar.gz: b3921946d18ca45b4c0ffe26cd1a722da4b16655e63e8f9cbb2eb7baad99fe2b6d1fa1bbcbd4a6251e244b4726517b7023e38d1f2c5c2065c59456f581439d0a
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Cody Krieger
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Really
|
2
|
+
|
3
|
+
A really (ha, a pun!) simple machine provisioning tool (like Chef or Puppet,
|
4
|
+
but way simpler). Heavily inspired by [Sprinkle](https://github.com/sprinkle-tool/sprinkle).
|
5
|
+
|
6
|
+
## Really is a work in progress...
|
7
|
+
|
8
|
+
...and as such, there is little documentation, except for the code itself, and
|
9
|
+
the example really.rb manifest in the root of the repository.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install really
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
TODO: Write usage instructions here
|
20
|
+
|
21
|
+
## Contributing
|
22
|
+
|
23
|
+
1. Fork it
|
24
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
25
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
26
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
27
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/really
ADDED
data/lib/really.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'really/version'
|
2
|
+
|
3
|
+
require 'really/cli'
|
4
|
+
require 'really/logger'
|
5
|
+
require 'really/script'
|
6
|
+
|
7
|
+
module Really
|
8
|
+
class << self
|
9
|
+
def start_cli(args)
|
10
|
+
CLI.start args
|
11
|
+
end
|
12
|
+
|
13
|
+
def provision(options = {})
|
14
|
+
options = options.dup
|
15
|
+
message = "Invoked with options: #{options}"
|
16
|
+
|
17
|
+
logger.verbose = options.delete :verbose
|
18
|
+
logger.quiet = options.delete :quiet
|
19
|
+
|
20
|
+
logger.debug message
|
21
|
+
|
22
|
+
script = Script.new options.delete(:config_file), options
|
23
|
+
script.execute
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/really/cli.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Really
|
4
|
+
class CLI < Thor
|
5
|
+
class_option :verbose, type: :boolean, aliases: '-v', default: false
|
6
|
+
class_option :quiet, type: :boolean, aliases: '-q', default: false
|
7
|
+
|
8
|
+
desc '--version', 'Prints the version.'
|
9
|
+
def version
|
10
|
+
puts "really v#{Really::VERSION}"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'provision', 'Provision the hosts provided in really.rb or given on the command line.'
|
14
|
+
option :config_file, aliases: '-c', default: 'really.rb'
|
15
|
+
option :force, type: :boolean, aliases: '-f', default: false
|
16
|
+
option :test_run, type: :boolean, aliases: '-t', default: false
|
17
|
+
# option :runlist
|
18
|
+
def provision
|
19
|
+
Really.provision options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Really
|
2
|
+
class Command
|
3
|
+
attr_accessor :options
|
4
|
+
|
5
|
+
def initialize(command, options = {})
|
6
|
+
@command = command
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def command
|
11
|
+
"#{sudo_command}#{@command}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"`#{@command}`"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def sudo_command
|
21
|
+
"sudo -p '#{sudo_prompt}' " if @options[:sudo]
|
22
|
+
end
|
23
|
+
|
24
|
+
def sudo_prompt
|
25
|
+
'sudo password: '
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Really
|
2
|
+
module Commands
|
3
|
+
class << self
|
4
|
+
def register(*modules)
|
5
|
+
logger.debug "Registering modules: #{modules}"
|
6
|
+
modules.each { |mod| ::Really::Task.send :prepend, mod }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Dir[File.join(__dir__, 'commands', '*.rb')].each do |file|
|
13
|
+
filename = File.basename(file, File.extname(file))
|
14
|
+
require "really/commands/#{filename}"
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Really
|
2
|
+
module Commands
|
3
|
+
module FileTransferCommands
|
4
|
+
def file(source_path, destination_path, options = {})
|
5
|
+
_add_file_transfer source_path, destination_path, options
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Really::Commands.register Really::Commands::FileTransferCommands
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Really
|
2
|
+
module Commands
|
3
|
+
module PackageManagement
|
4
|
+
NONINTERACTIVE_ENV = "env DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive "
|
5
|
+
|
6
|
+
def install_package(package_name, options = {})
|
7
|
+
default_options = { sudo: true }
|
8
|
+
options = default_options.merge options
|
9
|
+
# FIXME: make platform-independent
|
10
|
+
command "#{NONINTERACTIVE_ENV}apt-get install -y #{package_name}", options
|
11
|
+
end
|
12
|
+
|
13
|
+
def install_package_dependencies(package_name, options = {})
|
14
|
+
default_options = { sudo: true }
|
15
|
+
options = default_options.merge options
|
16
|
+
# FIXME: make platform-independent
|
17
|
+
command "#{NONINTERACTIVE_ENV}apt-get build-dep -y #{package_name}", options
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_available_packages(options = {})
|
21
|
+
default_options = { sudo: true }
|
22
|
+
options = default_options.merge options
|
23
|
+
# FIXME: make platform-independent
|
24
|
+
command "#{NONINTERACTIVE_ENV}apt-get update -y", options
|
25
|
+
end
|
26
|
+
|
27
|
+
def upgrade_installed_packages(options = {})
|
28
|
+
default_options = { sudo: true }
|
29
|
+
options = default_options.merge options
|
30
|
+
# FIXME: make platform-independent
|
31
|
+
command "#{NONINTERACTIVE_ENV}apt-get upgrade -y", options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Really::Commands.register Really::Commands::PackageManagement
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'really/helpers/rendering_helper'
|
2
|
+
|
3
|
+
module Really
|
4
|
+
module Commands
|
5
|
+
module RenderingCommands
|
6
|
+
def render_template(*args)
|
7
|
+
Really::Helpers::RenderingHelper.render_template *args
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Really::Commands.register Really::Commands::RenderingCommands
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Really
|
2
|
+
module Commands
|
3
|
+
module TextFileCommands
|
4
|
+
def append_text_to_file(text, file, options = {})
|
5
|
+
# FIXME: implement
|
6
|
+
command "echo 'we should append some text to '#{file}' here!'", options
|
7
|
+
end
|
8
|
+
|
9
|
+
def prepend_text_to_file(text, file, options = {})
|
10
|
+
# FIXME: implement
|
11
|
+
command "echo 'we should prepend some text to '#{file}' here!'", options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Really::Commands.register Really::Commands::TextFileCommands
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'tsort'
|
2
|
+
|
3
|
+
module Really
|
4
|
+
class DependencyGraph
|
5
|
+
include TSort
|
6
|
+
|
7
|
+
def initialize(node_pool = {})
|
8
|
+
@node_pool = node_pool
|
9
|
+
@nodes = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(node)
|
13
|
+
@nodes << node
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :ordered_nodes, :tsort
|
17
|
+
|
18
|
+
def tsort_each_node(&block)
|
19
|
+
@nodes.each &block
|
20
|
+
end
|
21
|
+
|
22
|
+
def tsort_each_child(node, &block)
|
23
|
+
@node_pool[node.name].dependencies.each do |dependency_name|
|
24
|
+
node = @node_pool[dependency_name]
|
25
|
+
raise "Dependency '#{dependency_name}' does not exist." if node.nil?
|
26
|
+
block.call node
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'really/command'
|
2
|
+
require 'really/file_transfer_command'
|
3
|
+
|
4
|
+
module Really
|
5
|
+
module Drivers
|
6
|
+
class DriverBase
|
7
|
+
attr_accessor :role_names
|
8
|
+
|
9
|
+
def initialize(&block)
|
10
|
+
@role_names = []
|
11
|
+
instance_eval &block if block
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"<#{self.class} roles:#{@role_names}>"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public API for use within `via` blocks in really.rb scripts
|
19
|
+
|
20
|
+
def roles(*roles)
|
21
|
+
@role_names += roles
|
22
|
+
end
|
23
|
+
alias_method :role, :roles
|
24
|
+
|
25
|
+
# Internal public API
|
26
|
+
|
27
|
+
def execute_task(task, options = {})
|
28
|
+
task.commands.each do |command|
|
29
|
+
logger.debug "command:#{command}"
|
30
|
+
|
31
|
+
if command.kind_of?(FileTransferCommand)
|
32
|
+
transfer_file command.source_path, command.destination_path, command.options
|
33
|
+
else
|
34
|
+
execute_command command, command.options unless options[:test_run]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def open
|
40
|
+
# Subclasses can override this to perform any driver initialization
|
41
|
+
# that is not appropriate for its constructor (like connecting to
|
42
|
+
# remote hosts, etc.).
|
43
|
+
end
|
44
|
+
|
45
|
+
def close
|
46
|
+
# Subclasses can override this to perform any necessary driver teardown.
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def execute_command(command, options = {})
|
52
|
+
raise "Subclasses must implement #execute_command."
|
53
|
+
end
|
54
|
+
|
55
|
+
def transfer_file(source_path, destination_path, options = {})
|
56
|
+
raise "Subclasses must implement #transfer_file."
|
57
|
+
end
|
58
|
+
|
59
|
+
def command_exited_with_exit_code(exit_code, options = {})
|
60
|
+
raise "Command failed with exit code #{exit_code}." unless exit_code == 0 || options[:allow_failure]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Really
|
4
|
+
module Drivers
|
5
|
+
class Local < DriverBase
|
6
|
+
def initialize(*args)
|
7
|
+
# Super does an instance_eval on the given block, so we need to call
|
8
|
+
# super *after* setting up our internal state.
|
9
|
+
super *args
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def execute_command(command, options = {})
|
15
|
+
stdin, stdout, stderr, thread = Open3.popen3(command.command)
|
16
|
+
exit_status = thread.value.exitstatus
|
17
|
+
|
18
|
+
stdout_string = stdout.read
|
19
|
+
stderr_string = stderr.read
|
20
|
+
|
21
|
+
if options[:log_output]
|
22
|
+
# FIXME: centralize this in DriverBase?
|
23
|
+
logger.status "Output of #{command}:", header: arrow
|
24
|
+
stdout.each_line { |line| logger.status line, header: :arrow }
|
25
|
+
stderr.each_line { |line| logger.status line, header: :arrow }
|
26
|
+
end
|
27
|
+
|
28
|
+
command_exited_with_exit_code exit_status, options
|
29
|
+
end
|
30
|
+
|
31
|
+
def transfer_file(source_path, destination_path, options = {})
|
32
|
+
# FIXME: transfer file
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/ssh/gateway'
|
3
|
+
require 'net/scp'
|
4
|
+
|
5
|
+
require 'strscan'
|
6
|
+
|
7
|
+
module Really
|
8
|
+
module Drivers
|
9
|
+
class SSH < DriverBase
|
10
|
+
KILOBYTE = 1024
|
11
|
+
SCP_CHUNK_SIZE = 32 * KILOBYTE
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
@user = "root"
|
15
|
+
@hosts = []
|
16
|
+
@port = 22
|
17
|
+
@keys = '~/.ssh/id_rsa'
|
18
|
+
@connections = {}
|
19
|
+
|
20
|
+
# Super does an instance_eval on the given block, so we need to call
|
21
|
+
# super *after* setting up our internal state.
|
22
|
+
super *args
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public API for use in `driver` blocks in really.rb scripts
|
26
|
+
|
27
|
+
def user(user)
|
28
|
+
@user = user
|
29
|
+
end
|
30
|
+
|
31
|
+
def hosts(*hosts)
|
32
|
+
@hosts += hosts
|
33
|
+
end
|
34
|
+
alias_method :host, :hosts
|
35
|
+
|
36
|
+
def port(port)
|
37
|
+
@port = port.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
def password(password)
|
41
|
+
@password = password
|
42
|
+
end
|
43
|
+
|
44
|
+
def keys(keys)
|
45
|
+
@keys = File.expand_path keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def gateway(gateway)
|
49
|
+
@gateway = gateway
|
50
|
+
end
|
51
|
+
|
52
|
+
def open
|
53
|
+
@hosts.each do |host|
|
54
|
+
key = connection_key @user, host, @port
|
55
|
+
options = { password: @password, keys: @keys, port: @port }
|
56
|
+
options.delete :password unless @password
|
57
|
+
options.delete :keys unless @keys
|
58
|
+
|
59
|
+
if @gateway
|
60
|
+
@connection_gateway ||= Net::SSH::Gateway.new @gateway, @user
|
61
|
+
@connections[key] ||= gateway.ssh host, @user, options
|
62
|
+
else
|
63
|
+
@connections[key] ||= Net::SSH.start host, @user, options
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
@connections.values.each { |connection| connection.close }
|
70
|
+
@connection_gateway.shutdown! if @connection_gateway
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
# Required overrides from Really::Drivers::DriverBase
|
76
|
+
|
77
|
+
def execute_command(command, options = {})
|
78
|
+
@connections.values.each do |connection|
|
79
|
+
stdout_data = StringScanner.new ""
|
80
|
+
stderr_data = StringScanner.new ""
|
81
|
+
|
82
|
+
connection.open_channel do |channel|
|
83
|
+
channel.on_data do |channel, data|
|
84
|
+
stdout_data << data
|
85
|
+
while (line = stdout_data.scan_until(/\n/))
|
86
|
+
if options[:log_output]
|
87
|
+
logger.status line, header: :arrow, header_color: :yellow
|
88
|
+
else
|
89
|
+
logger.debug line, header: :arrow, header_color: :yellow
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
channel.on_extended_data do |channel, type, data|
|
95
|
+
stderr_data << data
|
96
|
+
while (line = stderr_data.scan_until(/\n/))
|
97
|
+
if options[:log_output]
|
98
|
+
logger.status line, header: :arrow, header_color: :red
|
99
|
+
else
|
100
|
+
logger.debug line, header: :arrow, header_color: :red
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
channel.on_request("exit-status") do |channel, data|
|
106
|
+
command_exited_with_exit_code data.read_long, options
|
107
|
+
end
|
108
|
+
|
109
|
+
channel.on_request("exit-signal") do |channel, data|
|
110
|
+
logger.error "Command #{command} signaled: #{data.read_long}"
|
111
|
+
end
|
112
|
+
|
113
|
+
channel.exec(command.command) do |channel, success|
|
114
|
+
raise "Unable to execute remote command #{command}." unless success
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
logger.status stdout_data.rest, header: :arrow unless stdout_data.eos?
|
119
|
+
logger.error stderr_data.rest, header: :arrow unless stderr_data.eos?
|
120
|
+
|
121
|
+
connection.loop
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def transfer_file(source_path, destination_path, options = {})
|
126
|
+
@connections.values.each do |connection|
|
127
|
+
begin
|
128
|
+
logger.debug "Transferring '#{source_path}' to '#{destination_path}' via SCP..."
|
129
|
+
scp = Net::SCP.new connection
|
130
|
+
scp.upload! source_path, destination_path, recursive: options[:recursive], chunk_size: SCP_CHUNK_SIZE
|
131
|
+
rescue RuntimeError => error
|
132
|
+
raise "File transfer failed: '#{source_path}' => '#{destination_path}' (permission denied)." if error.message =~ /Permission denied/
|
133
|
+
raise error
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def connection_key(user, host, port)
|
141
|
+
"#{user}@#{host}:#{port}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'really/command'
|
2
|
+
require 'really/helpers/rendering_helper'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Really
|
6
|
+
class FileTransferCommand < Command
|
7
|
+
attr_accessor :destination_path
|
8
|
+
|
9
|
+
def initialize(source_path, destination_path, options = {})
|
10
|
+
super nil, options
|
11
|
+
@source_path = source_path
|
12
|
+
@destination_path = destination_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def source_path
|
16
|
+
return rendered_source_path if @options[:render]
|
17
|
+
@source_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"<#{self.class} source:'#{@source_path}' dest:'#{@destination_path}'>"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def rendered_source_path
|
27
|
+
return @rendered_file.path if @rendered_file
|
28
|
+
|
29
|
+
@rendered_file = Tempfile.new File.basename(@source_path)
|
30
|
+
|
31
|
+
args = [@source_path]
|
32
|
+
args << @options[:context] if @options[:context]
|
33
|
+
|
34
|
+
@rendered_file.print Really::Helpers::RenderingHelper.render_template(*args)
|
35
|
+
@rendered_file.close
|
36
|
+
|
37
|
+
logger.debug "Rendered eRuby template to tempfile '#{@rendered_file.path}'."
|
38
|
+
|
39
|
+
@rendered_file.path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
|
3
|
+
module Really
|
4
|
+
module Helpers
|
5
|
+
module RenderingHelper
|
6
|
+
class << self
|
7
|
+
def render_template(path, context = binding)
|
8
|
+
path = File.expand_path path
|
9
|
+
raise "File at '#{path}' is not readable. (Does it exist?)" unless File.readable?(path)
|
10
|
+
|
11
|
+
logger.debug "Rendering eRuby template at path '#{path}'..."
|
12
|
+
|
13
|
+
eruby = Erubis::Eruby.new File.read(path)
|
14
|
+
result = eruby.result context
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Really
|
2
|
+
class Logger
|
3
|
+
attr_accessor :verbose, :quiet
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
$stdout.sync = true
|
7
|
+
@verbose = false
|
8
|
+
@quiet = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def status(message, options = {})
|
12
|
+
log message, options
|
13
|
+
end
|
14
|
+
|
15
|
+
def error(message, options = {})
|
16
|
+
default_options = { header: 'error:', header_color: :red, override_quiet: true }
|
17
|
+
options = default_options.merge options
|
18
|
+
log message, options
|
19
|
+
end
|
20
|
+
|
21
|
+
def warning(message, options = {})
|
22
|
+
default_options = { header: 'warning:', header_color: :yellow }
|
23
|
+
options = default_options.merge options
|
24
|
+
log message, options
|
25
|
+
end
|
26
|
+
|
27
|
+
def debug(message, options = {})
|
28
|
+
default_options = { header: 'debug:', header_color: :pink }
|
29
|
+
options = default_options.merge options
|
30
|
+
log message, options if @verbose
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def log(message, options = {})
|
36
|
+
default_options = { header: nil, header_color: :yellow, override_quiet: false }
|
37
|
+
options = default_options.merge options
|
38
|
+
|
39
|
+
options[:header] = ' -->' if options[:header] == :arrow
|
40
|
+
options[:header] = self.send options[:header_color], options[:header] if options[:header_color]
|
41
|
+
|
42
|
+
puts "#{"#{options[:header]} " if options[:header]}#{message}" unless @quiet && !options[:override_quiet]
|
43
|
+
end
|
44
|
+
|
45
|
+
def red(str)
|
46
|
+
color_str(str, 31)
|
47
|
+
end
|
48
|
+
|
49
|
+
def green(str)
|
50
|
+
color_str(str, 32)
|
51
|
+
end
|
52
|
+
|
53
|
+
def yellow(str)
|
54
|
+
color_str(str, 33)
|
55
|
+
end
|
56
|
+
|
57
|
+
def pink(str)
|
58
|
+
color_str(str, 35)
|
59
|
+
end
|
60
|
+
|
61
|
+
def color_str(str, color_code)
|
62
|
+
return nil if str.nil? || str.empty?
|
63
|
+
"\e[#{color_code}m#{str}\e[0m"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Object
|
69
|
+
def logger
|
70
|
+
@@__really_logger__ ||= Really::Logger.new
|
71
|
+
end
|
72
|
+
end
|
data/lib/really/role.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Really
|
2
|
+
class Role
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :dependencies, :task_descriptors
|
5
|
+
|
6
|
+
def initialize(name, options = {}, &block)
|
7
|
+
@name = name
|
8
|
+
@options = options
|
9
|
+
@dependencies = []
|
10
|
+
@task_descriptors = []
|
11
|
+
|
12
|
+
dependencies = options[:depends_on] || []
|
13
|
+
@dependencies += [dependencies].flatten
|
14
|
+
|
15
|
+
instance_eval &block
|
16
|
+
end
|
17
|
+
|
18
|
+
def eql?(role)
|
19
|
+
@name == role.name
|
20
|
+
end
|
21
|
+
|
22
|
+
def hash
|
23
|
+
@name.hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"<#{self.class} #{@name}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def task(name, options = {})
|
31
|
+
@task_descriptors << { task_name: name, options: options }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'really/drivers'
|
2
|
+
require 'really/role'
|
3
|
+
require 'really/task'
|
4
|
+
|
5
|
+
require 'really/dependency_graph'
|
6
|
+
|
7
|
+
module Really
|
8
|
+
class Script
|
9
|
+
def initialize(path, options = {})
|
10
|
+
path = File.expand_path path
|
11
|
+
raise "Script at '#{path}' is not readable. :( (Does it exist?)" unless File.readable?(path)
|
12
|
+
|
13
|
+
@options = options
|
14
|
+
@tasks = {}
|
15
|
+
@roles = {}
|
16
|
+
@drivers = []
|
17
|
+
|
18
|
+
instance_eval File.read(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public API for use in really.rb scripts
|
22
|
+
|
23
|
+
def task(name, options = {}, &block)
|
24
|
+
name = name.to_sym
|
25
|
+
raise "Duplicate definition of group name '#{name}'." if @tasks.has_key?(name)
|
26
|
+
|
27
|
+
task = Task.new(name, options, &block)
|
28
|
+
@tasks[name] = task
|
29
|
+
task
|
30
|
+
end
|
31
|
+
|
32
|
+
def role(name, options = {}, &block)
|
33
|
+
name = name.to_sym
|
34
|
+
raise "Duplicate definition of role name '#{name}'." if @roles.has_key?(name)
|
35
|
+
|
36
|
+
role = Role.new(name, options, &block)
|
37
|
+
@roles[name] = role
|
38
|
+
role
|
39
|
+
end
|
40
|
+
|
41
|
+
def configure(&block)
|
42
|
+
instance_eval &block
|
43
|
+
end
|
44
|
+
|
45
|
+
def via(name, &block)
|
46
|
+
name = name.to_s.capitalize
|
47
|
+
name = "SSH" if name == "Ssh"
|
48
|
+
klass = eval "Really::Drivers::#{name}"
|
49
|
+
|
50
|
+
driver = klass.new &block
|
51
|
+
@drivers << driver
|
52
|
+
driver
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal public API
|
56
|
+
|
57
|
+
def execute(role_names = [])
|
58
|
+
@drivers.each do |driver|
|
59
|
+
driver.open
|
60
|
+
|
61
|
+
role_graph = DependencyGraph.new @roles
|
62
|
+
|
63
|
+
# All drivers implicitly get the magic :default role.
|
64
|
+
role_graph.add @roles[:default] if @roles.has_key?(:default)
|
65
|
+
|
66
|
+
driver.role_names.each do |role_name|
|
67
|
+
raise "Role '#{role_name}' does not exist." unless @roles.has_key?(role_name)
|
68
|
+
role_graph.add @roles[role_name]
|
69
|
+
end
|
70
|
+
|
71
|
+
all_tasks = Hash[@tasks.collect { |key, value| [key, value.dup] }]
|
72
|
+
task_graph = DependencyGraph.new all_tasks
|
73
|
+
|
74
|
+
roles = role_graph.ordered_nodes
|
75
|
+
|
76
|
+
roles.each do |role|
|
77
|
+
role.task_descriptors.each do |descriptor|
|
78
|
+
task_name = descriptor[:task_name]
|
79
|
+
raise "Task '#{task_name}' does not exist." unless all_tasks.has_key?(task_name)
|
80
|
+
|
81
|
+
task = all_tasks[task_name]
|
82
|
+
task.options.merge! descriptor[:options]
|
83
|
+
task_graph.add task
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
logger.status "[#{roles.length > 1 ? 'roles' : 'role'}: #{roles.collect(&:name).join ', '}]"
|
88
|
+
|
89
|
+
task_graph.ordered_nodes.each do |task|
|
90
|
+
logger.status "task:#{task.name}", header: '***', header_color: :green
|
91
|
+
driver.execute_task task, @options
|
92
|
+
end
|
93
|
+
|
94
|
+
driver.close
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# stuff
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
data/lib/really/task.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'really/command'
|
2
|
+
require 'really/file_transfer_command'
|
3
|
+
|
4
|
+
module Really
|
5
|
+
class Task
|
6
|
+
attr_reader :name
|
7
|
+
attr_accessor :options, :dependencies
|
8
|
+
|
9
|
+
def initialize(name, options = {}, &block)
|
10
|
+
@name = name
|
11
|
+
@options = options
|
12
|
+
@block = block
|
13
|
+
@dependencies = []
|
14
|
+
@commands = []
|
15
|
+
|
16
|
+
dependencies = options[:depends_on] || []
|
17
|
+
@dependencies += [dependencies].flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def eql?(task)
|
21
|
+
@name == task.name
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
@name.hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"<#{self.class} #{@name}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal public API
|
33
|
+
|
34
|
+
def commands
|
35
|
+
instance_eval &@block if @block && @commands.empty?
|
36
|
+
@commands
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# Internal API for modules that add methods to Really::Task
|
42
|
+
|
43
|
+
def _add_command(command, options = {})
|
44
|
+
@commands << Command.new(command, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def _add_file_transfer(source_path, destination_path, options = {})
|
48
|
+
filename = File.basename destination_path
|
49
|
+
tmp_path = "/tmp/_really_#{filename}"
|
50
|
+
|
51
|
+
@commands << FileTransferCommand.new(source_path, tmp_path, options)
|
52
|
+
|
53
|
+
_add_command "mv '#{tmp_path}' '#{destination_path}'", options
|
54
|
+
_add_command "chown #{'-R ' if options[:recursive]}#{options[:user]}:#{options[:user]} #{destination_path}", options if options[:user]
|
55
|
+
_add_command "chmod #{'-R ' if options[:recursive]}#{options[:mode]} #{destination_path}", options if options[:mode]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
require 'really/commands'
|
data/really.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'really/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "really"
|
8
|
+
spec.version = Really::VERSION
|
9
|
+
spec.authors = ["Cody Krieger"]
|
10
|
+
spec.email = ["cody@krieger.io"]
|
11
|
+
spec.description = %q{A really simple machine provisioning tool.}
|
12
|
+
spec.summary = %q{A really simple machine provisioning tool.}
|
13
|
+
spec.homepage = "https://github.com/codykrieger/really"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
|
25
|
+
spec.add_dependency "erubis", "~> 2.7.0"
|
26
|
+
spec.add_dependency "net-scp", "~> 1.1.2"
|
27
|
+
spec.add_dependency "net-ssh", "~> 2.6.8"
|
28
|
+
spec.add_dependency "net-ssh-gateway", "~> 1.2.0"
|
29
|
+
spec.add_dependency "thor", "~> 0.18.1"
|
30
|
+
end
|
data/really.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# tasks do not have verify blocks; this should help enforce the convention that commands are idempotent
|
2
|
+
|
3
|
+
task :install_vim do
|
4
|
+
install_package 'vim'
|
5
|
+
end
|
6
|
+
|
7
|
+
task :install_emacs do
|
8
|
+
install_package 'emacs' # ...ew
|
9
|
+
end
|
10
|
+
|
11
|
+
task :install_zsh do
|
12
|
+
install_package 'zsh'
|
13
|
+
end
|
14
|
+
|
15
|
+
task :install_git do
|
16
|
+
install_package 'git-core'
|
17
|
+
end
|
18
|
+
|
19
|
+
task :configure_git, depends_on: :install_git do
|
20
|
+
command "git config --global user.name 'Cody Krieger'"
|
21
|
+
command "git config --global user.email 'foo@example.com'"
|
22
|
+
end
|
23
|
+
|
24
|
+
task :install_editors, depends_on: [:install_vim, :install_emacs] # block optional! (this is a meta-task)
|
25
|
+
|
26
|
+
task :testing do
|
27
|
+
install_package 'vim'
|
28
|
+
install_package 'zsh'
|
29
|
+
install_package_dependencies 'ruby'
|
30
|
+
file '~/projects/sprinkle/app-server/iptables.up.rules.erb', '/etc/iptables.up.rules', sudo: true, render: true, context: { ssh_port: 2222 }
|
31
|
+
end
|
32
|
+
|
33
|
+
# the magic :default role runs tasks on *all* hosts (before other roles), no matter what (well, unless --no-default is specified)
|
34
|
+
role :default do
|
35
|
+
task :install_editors
|
36
|
+
end
|
37
|
+
|
38
|
+
role :git_server do
|
39
|
+
task :install_git
|
40
|
+
task :configure_git
|
41
|
+
task :testing
|
42
|
+
end
|
43
|
+
|
44
|
+
role :other_server, depends_on: [:git_server] do
|
45
|
+
task :install_zsh
|
46
|
+
end
|
47
|
+
|
48
|
+
role :testing do
|
49
|
+
task :testing
|
50
|
+
end
|
51
|
+
|
52
|
+
configure do
|
53
|
+
via :ssh do
|
54
|
+
host "127.0.0.1"
|
55
|
+
port 2222
|
56
|
+
user "vagrant"
|
57
|
+
# password "vagrant"
|
58
|
+
keys "~/.vagrant.d/insecure_private_key"
|
59
|
+
|
60
|
+
role :git_server
|
61
|
+
end
|
62
|
+
|
63
|
+
# via :ssh do
|
64
|
+
# hosts "example.com", "secretwebsite.com"
|
65
|
+
# user "deploy"
|
66
|
+
# keys "~/.ssh/id_rsa"
|
67
|
+
# gateway "gateway.example.com"
|
68
|
+
|
69
|
+
# roles :other_server, :some_other_role
|
70
|
+
# end
|
71
|
+
|
72
|
+
# via :local do
|
73
|
+
# role :other_server
|
74
|
+
# role :testing
|
75
|
+
# end
|
76
|
+
end
|
data/test/test_really.rb
ADDED
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: really
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cody Krieger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: erubis
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.7.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.7.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: net-scp
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.1.2
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.1.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: net-ssh
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.6.8
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.6.8
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: net-ssh-gateway
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.2.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.2.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: thor
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.18.1
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.18.1
|
125
|
+
description: A really simple machine provisioning tool.
|
126
|
+
email:
|
127
|
+
- cody@krieger.io
|
128
|
+
executables:
|
129
|
+
- really
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .travis.yml
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- bin/really
|
140
|
+
- lib/really.rb
|
141
|
+
- lib/really/cli.rb
|
142
|
+
- lib/really/command.rb
|
143
|
+
- lib/really/commands.rb
|
144
|
+
- lib/really/commands/basic_commands.rb
|
145
|
+
- lib/really/commands/file_transfer_commands.rb
|
146
|
+
- lib/really/commands/package_management.rb
|
147
|
+
- lib/really/commands/rendering_commands.rb
|
148
|
+
- lib/really/commands/text_file_commands.rb
|
149
|
+
- lib/really/dependency_graph.rb
|
150
|
+
- lib/really/drivers.rb
|
151
|
+
- lib/really/drivers/driver_base.rb
|
152
|
+
- lib/really/drivers/local.rb
|
153
|
+
- lib/really/drivers/ssh.rb
|
154
|
+
- lib/really/file_transfer_command.rb
|
155
|
+
- lib/really/helpers/rendering_helper.rb
|
156
|
+
- lib/really/logger.rb
|
157
|
+
- lib/really/role.rb
|
158
|
+
- lib/really/script.rb
|
159
|
+
- lib/really/task.rb
|
160
|
+
- lib/really/version.rb
|
161
|
+
- really.gemspec
|
162
|
+
- really.rb
|
163
|
+
- test/minitest_helper.rb
|
164
|
+
- test/test_really.rb
|
165
|
+
homepage: https://github.com/codykrieger/really
|
166
|
+
licenses:
|
167
|
+
- MIT
|
168
|
+
metadata: {}
|
169
|
+
post_install_message:
|
170
|
+
rdoc_options: []
|
171
|
+
require_paths:
|
172
|
+
- lib
|
173
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - '>='
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - '>='
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
requirements: []
|
184
|
+
rubyforge_project:
|
185
|
+
rubygems_version: 2.0.3
|
186
|
+
signing_key:
|
187
|
+
specification_version: 4
|
188
|
+
summary: A really simple machine provisioning tool.
|
189
|
+
test_files:
|
190
|
+
- test/minitest_helper.rb
|
191
|
+
- test/test_really.rb
|