shelter 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: []