shelter 0.0.3
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/README.md +92 -0
- data/bin/shelter +8 -0
- data/bin/shelter-inventory +24 -0
- data/lib/ansible/inventory.rb +26 -0
- data/lib/cli/ansible_helper.rb +52 -0
- data/lib/cli/app.rb +51 -0
- data/lib/cli/command/ansible.rb +39 -0
- data/lib/cli/stack/cloud_formation.rb +111 -0
- data/lib/configuration.rb +50 -0
- data/lib/shelter.rb +34 -0
- data/lib/version.rb +5 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c6e01fa6f3c4c557007039ec6a00801fe964e56e
|
4
|
+
data.tar.gz: e836545cd87009fcc5c14c4a2f5051df4430a244
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3623a12c17917629240e4afc16f1bbd5b6377346bfe3f34c1c0ae867a76d782c79b0e06cc81528016a88d1039f6ea9abc84cc02c5492efd33498acbabda19555
|
7
|
+
data.tar.gz: 6f935734fdabcc64abb92e1eac449cdcefc58e1a1e55cd3a6ba2fd4ae9e291f5e9cda0ab6937e431e8c99f05373e1372604861180cddd7b9a15fe08e65cc7e64
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Configuration
|
2
|
+
|
3
|
+
Create `Shelterfile.rb`:
|
4
|
+
|
5
|
+
```
|
6
|
+
Shelter::CLI::App.config do |c|
|
7
|
+
c.ansible_directory = 'ansible'
|
8
|
+
c.stack_directory = 'stack'
|
9
|
+
c.plugin_directory = 'plugin'
|
10
|
+
c.inventory_directory = 'inventory'
|
11
|
+
c.secure_root = ENV.fetch('CHEPPERS_SECURE')
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
# Inventory script
|
16
|
+
|
17
|
+
Create a ruby file under your inventory directory:
|
18
|
+
|
19
|
+
```
|
20
|
+
module Inventory
|
21
|
+
class MyInventory
|
22
|
+
def load(inventory)
|
23
|
+
inventory.add_group_vars 'groupname', ch_env: 'value'
|
24
|
+
inventory.add_server 'groupname', 'ip-address'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
# Define a stack
|
31
|
+
|
32
|
+
Create a directory under your `stack` directory (eg: `random`)
|
33
|
+
and create your template there. `cli.rb` will be loaded.
|
34
|
+
|
35
|
+
|
36
|
+
cli.rb:
|
37
|
+
```
|
38
|
+
module Stack
|
39
|
+
class RandomStack < Shelter::CLI::Stack::CloudFormation
|
40
|
+
set_attr :stack_name, 'random'
|
41
|
+
set_attr :template_file, File.expand_path('template.yaml', File.dirname(__FILE__))
|
42
|
+
set_attr :meta, {
|
43
|
+
type: 'development',
|
44
|
+
client: 'cheppers',
|
45
|
+
application: 'random'
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
# Create plugin
|
52
|
+
|
53
|
+
Create a directory under your `plugin` directory and place your code there.
|
54
|
+
`main.rb` will be loaded.
|
55
|
+
|
56
|
+
### Example #1: extra command
|
57
|
+
|
58
|
+
```
|
59
|
+
# plugin/check/main.rb
|
60
|
+
require 'thor'
|
61
|
+
|
62
|
+
module Plugin
|
63
|
+
class Check < Thor
|
64
|
+
desc 'environment', 'Check environment'
|
65
|
+
def environment
|
66
|
+
puts "check"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Shelter::CLI::App.register(Plugin::Check, 'check', 'check [COMMAND]', 'check plugin')
|
72
|
+
```
|
73
|
+
|
74
|
+
### Example #2: extra command under a specific namespace
|
75
|
+
|
76
|
+
```
|
77
|
+
# plugin/ansible_scanner/main.rb
|
78
|
+
require 'thor'
|
79
|
+
|
80
|
+
module Plugin
|
81
|
+
class Ansible < Thor
|
82
|
+
desc 'scanner', 'scanner'
|
83
|
+
def scanner
|
84
|
+
puts "scan"
|
85
|
+
end
|
86
|
+
|
87
|
+
default_task :scanner
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Shelter::CLI::Command::Ansible.register(Plugin::Ansible, 'scanner', 'scanner', 'Scan')
|
92
|
+
```
|
data/bin/shelter
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'shelter'
|
6
|
+
|
7
|
+
inventory = Shelter::Ansible::Inventory.new
|
8
|
+
|
9
|
+
Shelter::CLI::App.config.inventory.each do |script|
|
10
|
+
begin
|
11
|
+
require File.join(Shelter::CLI::App.config.project_root, script)
|
12
|
+
class_name = "Inventory::#{File.basename(script, File.extname(script)).camelize}"
|
13
|
+
inv = Object.const_get(class_name).new()
|
14
|
+
inv.load(inventory)
|
15
|
+
rescue Exception => e
|
16
|
+
$stderr.puts "[x] Error with #{script}"
|
17
|
+
$stderr.puts e.message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
puts inventory.to_json
|
22
|
+
|
23
|
+
# vim: set filetype=ruby:
|
24
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shelter
|
2
|
+
module Ansible
|
3
|
+
# Ansible Inventory representation
|
4
|
+
class Inventory
|
5
|
+
def initialize
|
6
|
+
@inv = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_group_vars(group, vars)
|
10
|
+
@inv[group] ||= { hosts: [], vars: {} }
|
11
|
+
@inv[group][:vars].merge! vars
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_server(group, ip, vars: {})
|
15
|
+
@inv[group] ||= { hosts: [], vars: {} }
|
16
|
+
|
17
|
+
@inv[group][:hosts] << ip
|
18
|
+
#@inv[group][:vars] = { ansible_host: ip }.merge(vars)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_json
|
22
|
+
@inv.to_json
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Shelter
|
2
|
+
module CLI
|
3
|
+
# Mixin module for Ansible
|
4
|
+
module AnsibleHelpers
|
5
|
+
def vault_password_file
|
6
|
+
[
|
7
|
+
'--vault-password-file',
|
8
|
+
"#{App.config.secure_root}/ansible_vault_password"
|
9
|
+
].join(' ')
|
10
|
+
end
|
11
|
+
|
12
|
+
def inventory_file(inventory)
|
13
|
+
inventory ||= App.config.inventory_script
|
14
|
+
"--inventory #{inventory}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def command_bin
|
18
|
+
'ansible-playbook'
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_server_params(server_user)
|
22
|
+
command = []
|
23
|
+
command << "--user #{server_user} --ask-pass" unless server_user.nil?
|
24
|
+
command
|
25
|
+
end
|
26
|
+
|
27
|
+
def optional_params(tags: [], skip: [], limit: nil)
|
28
|
+
command = []
|
29
|
+
command << "--tags '#{tags.join(',')}'" unless tags.empty?
|
30
|
+
command << "--skip-tags '#{skip.join(',')}'" unless skip.empty?
|
31
|
+
command << "--limit '#{limit}'" unless limit.nil?
|
32
|
+
command
|
33
|
+
end
|
34
|
+
|
35
|
+
def ansible_execute(
|
36
|
+
playbook,
|
37
|
+
server_user: nil, inventory: nil,
|
38
|
+
tags: [], skip: [], limit: nil
|
39
|
+
)
|
40
|
+
command = [command_bin, inventory_file(inventory), vault_password_file]
|
41
|
+
command += new_server_params(server_user)
|
42
|
+
command += optional_params(tags: tags, skip: skip, limit: limit)
|
43
|
+
command << "#{App.config.ansible_directory}/#{playbook}.yaml"
|
44
|
+
|
45
|
+
full_command = "#{command.join(' ')}"
|
46
|
+
$stdout.puts full_command
|
47
|
+
system full_command
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/lib/cli/app.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
require_all(File.join(File.dirname(__FILE__), 'command'))
|
4
|
+
require_all(File.join(File.dirname(__FILE__), 'stack'))
|
5
|
+
|
6
|
+
module Shelter
|
7
|
+
module CLI
|
8
|
+
# Main Shelter app
|
9
|
+
class App < Thor
|
10
|
+
class << self
|
11
|
+
attr_writer :config
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
@config ||= Shelter::Configuration.new
|
16
|
+
@config.load_shelterfile
|
17
|
+
block_given? ? yield(@config) : @config
|
18
|
+
end
|
19
|
+
|
20
|
+
register(
|
21
|
+
Shelter::CLI::Command::Ansible,
|
22
|
+
'ansible',
|
23
|
+
'ansible [COMMAND]',
|
24
|
+
'Ansible related commands'
|
25
|
+
)
|
26
|
+
|
27
|
+
# Register server managers
|
28
|
+
path = File.join(App.config.stack_directory, '*')
|
29
|
+
Dir.glob(path).each do |path|
|
30
|
+
next unless File.exists? "#{path}/cli.rb"
|
31
|
+
require File.join(App.config.project_root, path, 'cli.rb')
|
32
|
+
|
33
|
+
stack_name = File.basename(path)
|
34
|
+
register(
|
35
|
+
Object.const_get("Stack::#{stack_name.camelize}"),
|
36
|
+
stack_name,
|
37
|
+
"#{stack_name} [COMMAND]",
|
38
|
+
"Manage #{stack_name.camelize} stack"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Register server managers
|
43
|
+
path = File.join(App.config.plugin_directory, '*')
|
44
|
+
Dir.glob(path).each do |path|
|
45
|
+
next unless File.exists? "#{path}/main.rb"
|
46
|
+
require File.join(App.config.project_root, path, 'main.rb')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Shelter
|
4
|
+
module CLI
|
5
|
+
module Command
|
6
|
+
# Ansible subcommand for Shelter
|
7
|
+
class Ansible < Thor
|
8
|
+
desc 'update', 'update existing infrastructure'
|
9
|
+
method_option :only,
|
10
|
+
default: [], type: :array,
|
11
|
+
desc: 'Execute only these tags'
|
12
|
+
method_option :skip,
|
13
|
+
default: [], type: :array,
|
14
|
+
desc: 'Skip these tags'
|
15
|
+
method_option :limit,
|
16
|
+
type: :string,
|
17
|
+
desc: 'Limit to a specific group or host'
|
18
|
+
method_option :inventory,
|
19
|
+
type: :string,
|
20
|
+
default: nil,
|
21
|
+
desc: 'Specify inventory file/script'
|
22
|
+
def update
|
23
|
+
ansible_execute(
|
24
|
+
'configuration',
|
25
|
+
tags: options[:only],
|
26
|
+
skip: options[:skip],
|
27
|
+
limit: options[:limit],
|
28
|
+
inventory: options[:inventory]
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
no_commands do
|
33
|
+
include Shelter::CLI::AnsibleHelpers
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
module Shelter
|
5
|
+
module CLI
|
6
|
+
module Stack
|
7
|
+
class CloudFormation < Thor
|
8
|
+
desc 'status', 'Stack status'
|
9
|
+
def status
|
10
|
+
target_stack = cf_client.describe_stacks(stack_name: get_attr(:stack_name)).stacks.first
|
11
|
+
|
12
|
+
puts "#{target_stack.stack_name} exists..."
|
13
|
+
puts "Created: #{target_stack.creation_time}"
|
14
|
+
|
15
|
+
resources = cf_client.describe_stack_resources(
|
16
|
+
stack_name: target_stack.stack_name
|
17
|
+
)
|
18
|
+
resources.stack_resources.each do |r|
|
19
|
+
puts ' __________________________________________________________________________________________'
|
20
|
+
puts '| Stack Name | Resource ID | Resource Type | Resource Status |'
|
21
|
+
puts "| #{r.stack_name} | #{r.physical_resource_id} | #{r.resource_type} | #{r.resource_status} |"
|
22
|
+
puts ' ------------------------------------------------------------------------------------------'
|
23
|
+
end
|
24
|
+
rescue Exception => e
|
25
|
+
fail Thor::Error, e.message
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'create', 'Create stack'
|
29
|
+
def create
|
30
|
+
cf_client.create_stack(
|
31
|
+
stack_name: get_attr(:stack_name),
|
32
|
+
template_body: File.open(get_attr(:template_file)).read,
|
33
|
+
capabilities: ['CAPABILITY_IAM'],
|
34
|
+
tags: [
|
35
|
+
{ key: 'client', value: get_attr(:meta)[:client] },
|
36
|
+
{ key: 'type', value: get_attr(:meta)[:type] },
|
37
|
+
{ key: 'application', value: get_attr(:meta)[:application] }
|
38
|
+
]
|
39
|
+
)
|
40
|
+
i = 0
|
41
|
+
cf_client.wait_until(:stack_create_complete, stack_name: get_attr(:stack_name)) do |w|
|
42
|
+
w.before_attempt do
|
43
|
+
i = (i + 1) % SPINNER.size
|
44
|
+
print "\r#{SPINNER[i]}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue Exception => e
|
48
|
+
fail Thor::Error, e.message
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'update', 'Update stack'
|
52
|
+
def update
|
53
|
+
cf_client.update_stack(
|
54
|
+
stack_name: get_attr(:stack_name),
|
55
|
+
template_body: File.open(get_attr(:template_file)).read,
|
56
|
+
capabilities: ['CAPABILITY_IAM']
|
57
|
+
)
|
58
|
+
i = 0
|
59
|
+
cf_client.wait_until(:stack_update_complete, stack_name: get_attr(:stack_name)) do |w|
|
60
|
+
w.before_attempt do
|
61
|
+
i = (i + 1) % SPINNER.size
|
62
|
+
print "\r#{SPINNER[i]}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rescue Exception => e
|
66
|
+
fail Thor::Error, e.message
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
no_commands do
|
72
|
+
class << self
|
73
|
+
def set_attr(name, value)
|
74
|
+
@@_attr ||= {}
|
75
|
+
@@_attr[name] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_attr(name)
|
80
|
+
@@_attr[name] if @@_attr.key? name
|
81
|
+
end
|
82
|
+
|
83
|
+
def cf_client
|
84
|
+
@cf_client ||= Aws::CloudFormation::Client.new(
|
85
|
+
credentials: Aws::Credentials.new(
|
86
|
+
ENV.fetch('AWS_ACCESS_KEY_ID'),
|
87
|
+
ENV.fetch('AWS_SECRET_ACCESS_KEY')
|
88
|
+
)
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_public_ip
|
93
|
+
target_stack = cf_client.describe_stacks(stack_name: get_attr(:stack_name)).stacks.first
|
94
|
+
|
95
|
+
resources = cf_client.describe_stack_resources(
|
96
|
+
stack_name: target_stack.stack_name
|
97
|
+
)
|
98
|
+
|
99
|
+
eip = resources.stack_resources.select { |r|
|
100
|
+
r.resource_type == 'AWS::EC2::EIP'
|
101
|
+
}.first
|
102
|
+
|
103
|
+
eip.physical_resource_id
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Shelter
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :ansible_directory,
|
4
|
+
:stack_directory,
|
5
|
+
:secure_root,
|
6
|
+
:inventory_script,
|
7
|
+
:inventory_directory,
|
8
|
+
:plugin_directory
|
9
|
+
|
10
|
+
attr_reader :project_root
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@ansible_directory = 'ansible'
|
14
|
+
@stack_directory = 'stacks'
|
15
|
+
@secure_root = ENV.fetch('SECURE', 'secure')
|
16
|
+
@inventory_script = File.join(File.dirname($PROGRAM_NAME), 'shelter-inventory')
|
17
|
+
@inventory_directory = 'inventory'
|
18
|
+
@plugin_directory = 'plugin'
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_shelterfile
|
22
|
+
load shelterfile if @shelterfile.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def inventory
|
26
|
+
path = File.join(@inventory_directory, '*')
|
27
|
+
Dir.glob(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def shelterfile
|
32
|
+
@shelterfile ||= File.join(find_project_root, 'Shelterfile.rb')
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_project_root
|
36
|
+
@project_root unless @project_root.nil?
|
37
|
+
|
38
|
+
dir = Dir.pwd
|
39
|
+
loop do
|
40
|
+
break if File.exist?('Shelterfile.rb')
|
41
|
+
fail 'No Shelterfile.rb found' if Dir.pwd == '/'
|
42
|
+
Dir.chdir('..')
|
43
|
+
end
|
44
|
+
@project_root = Dir.pwd
|
45
|
+
Dir.chdir dir
|
46
|
+
@project_root
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
data/lib/shelter.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'version'
|
2
|
+
require 'configuration'
|
3
|
+
|
4
|
+
directories = %w(
|
5
|
+
ansible
|
6
|
+
cli
|
7
|
+
)
|
8
|
+
|
9
|
+
class String
|
10
|
+
def camelize(uppercase_first_letter = true)
|
11
|
+
string = self
|
12
|
+
if uppercase_first_letter
|
13
|
+
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
|
14
|
+
else
|
15
|
+
string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
|
16
|
+
end
|
17
|
+
string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
LIB_ROOT = File.expand_path('../', __FILE__)
|
22
|
+
|
23
|
+
def require_all(dir)
|
24
|
+
path = File.join(dir, '*')
|
25
|
+
Dir.glob(path).each do |file|
|
26
|
+
require file if File.file?(file)
|
27
|
+
require_all(file) if File.directory?(file)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
directories.each do |dir|
|
32
|
+
require_all(File.join(File.dirname(__FILE__), dir))
|
33
|
+
end
|
34
|
+
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shelter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Balazs Nadasdi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.20.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.20.0
|
41
|
+
description: Shelter tries to be a framework to manage internal infrastructure
|
42
|
+
email: balazs.nadasdi@cheppers.com
|
43
|
+
executables:
|
44
|
+
- shelter
|
45
|
+
- shelter-inventory
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- README.md
|
50
|
+
- bin/shelter
|
51
|
+
- bin/shelter-inventory
|
52
|
+
- lib/ansible/inventory.rb
|
53
|
+
- lib/cli/ansible_helper.rb
|
54
|
+
- lib/cli/app.rb
|
55
|
+
- lib/cli/command/ansible.rb
|
56
|
+
- lib/cli/stack/cloud_formation.rb
|
57
|
+
- lib/configuration.rb
|
58
|
+
- lib/shelter.rb
|
59
|
+
- lib/version.rb
|
60
|
+
homepage: https://github.com/yitsushi/shelter
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '2.3'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.6.11
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Shelter is a tool for you
|
84
|
+
test_files: []
|