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 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,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
4
+ require 'shelter'
5
+
6
+ Shelter::CLI::App.start(ARGV)
7
+
8
+ # vim: set filetype=ruby:
@@ -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
@@ -0,0 +1,5 @@
1
+ module Shelter
2
+ # Current version to publish
3
+ # Gemspec also uses this constant
4
+ VERSION = '0.0.3'.freeze
5
+ end
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: []