stackadmin 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/CHANGELOG +14 -0
- data/Gemfile +8 -9
- data/Gemfile.lock +11 -98
- data/README.md +71 -0
- data/Rakefile +35 -9
- data/lib/stackadmin/audit.rb +41 -1
- data/lib/stackadmin/base.rb +48 -0
- data/lib/stackadmin/exceptions.rb +13 -0
- data/lib/stackadmin/net-ssh.rb +19 -2
- data/lib/stackadmin/patch.rb +91 -18
- data/lib/stackadmin/version.rb +2 -1
- data/lib/stackadmin/yaml.rb +5 -0
- data/stackadmin.gemspec +26 -0
- data/test/helpers/vagrant.rb +31 -0
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e2ddf654357669ce50978cf4616b55f25244cbf
|
4
|
+
data.tar.gz: 483e8a3ba68b7995367b40af91791b1aca24e5e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb485c2bb8ea145d9247945efc5d2a7f8d520e7f9b83c3e810a3508e1fc86a24c10541bcf16cb919c11544a68a216df02ce4fd39b3fc9d01819e6d501726335b
|
7
|
+
data.tar.gz: 33fa85e6175d55f656aacfd9d4eb685bba74eb798663972fef9684f337831f8591c6d211b87afcd16eec659786ac83621eaf57d759d7c690380cc71894ae1480
|
data/.gitignore
CHANGED
data/CHANGELOG
ADDED
data/Gemfile
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
|
+
ruby '2.0.0'
|
2
3
|
|
3
|
-
gem 'net-ssh'
|
4
|
-
gem 'json'
|
4
|
+
gem 'net-ssh', '~> 2.9.2'
|
5
|
+
gem 'json', '~> 1.8.2'
|
5
6
|
|
6
7
|
group :development do
|
7
|
-
gem 'minitest'
|
8
|
-
gem 'minitest-reporters'
|
9
|
-
gem '
|
10
|
-
|
11
|
-
|
12
|
-
group :plugins do
|
13
|
-
gem 'vagrant-s3auth'
|
8
|
+
gem 'minitest', '~> 4.3.2'
|
9
|
+
gem 'minitest-reporters', '~> 0.14.24'
|
10
|
+
gem 'rake', '~> 10.4.2'
|
11
|
+
gem 'vagrant-wrapper', '~> 2.0.2'
|
12
|
+
gem 'yard', '~> 0.8.7.6'
|
14
13
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,119 +1,32 @@
|
|
1
|
-
GIT
|
2
|
-
remote: https://github.com/mitchellh/vagrant.git
|
3
|
-
revision: b2c722ef54e57c9de5cdbe712b27e52faacec5da
|
4
|
-
specs:
|
5
|
-
vagrant (1.7.2)
|
6
|
-
bundler (>= 1.5.2, < 1.8.0)
|
7
|
-
childprocess (~> 0.5.0)
|
8
|
-
erubis (~> 2.7.0)
|
9
|
-
hashicorp-checkpoint (~> 0.1.1)
|
10
|
-
i18n (~> 0.6.0)
|
11
|
-
listen (~> 2.8.0)
|
12
|
-
log4r (~> 1.1.9, < 1.1.11)
|
13
|
-
net-scp (~> 1.1.0)
|
14
|
-
net-sftp (~> 2.1)
|
15
|
-
net-ssh (>= 2.6.6, < 2.10.0)
|
16
|
-
nokogiri (= 1.6.3.1)
|
17
|
-
rb-kqueue (~> 0.2.0)
|
18
|
-
rest-client (>= 1.6.0, < 2.0)
|
19
|
-
wdm (~> 0.1.0)
|
20
|
-
winrm (~> 1.3.0)
|
21
|
-
winrm-fs (~> 0.1.0)
|
22
|
-
|
23
1
|
GEM
|
24
2
|
remote: https://rubygems.org/
|
25
3
|
specs:
|
26
4
|
ansi (1.5.0)
|
27
|
-
aws-sdk (1.59.1)
|
28
|
-
aws-sdk-v1 (= 1.59.1)
|
29
|
-
aws-sdk-v1 (1.59.1)
|
30
|
-
json (~> 1.4)
|
31
|
-
nokogiri (>= 1.4.4)
|
32
5
|
builder (3.2.2)
|
33
|
-
celluloid (0.16.0)
|
34
|
-
timers (~> 4.0.0)
|
35
|
-
childprocess (0.5.5)
|
36
|
-
ffi (~> 1.0, >= 1.0.11)
|
37
|
-
erubis (2.7.0)
|
38
|
-
ffi (1.9.6)
|
39
|
-
gssapi (1.2.0)
|
40
|
-
ffi (>= 1.0.1)
|
41
|
-
gyoku (1.2.2)
|
42
|
-
builder (>= 2.1.2)
|
43
|
-
hashicorp-checkpoint (0.1.4)
|
44
6
|
hashie (3.4.0)
|
45
|
-
hitimes (1.2.2)
|
46
|
-
httpclient (2.6.0.1)
|
47
|
-
i18n (0.6.11)
|
48
7
|
json (1.8.2)
|
49
|
-
|
50
|
-
celluloid (>= 0.15.2)
|
51
|
-
rb-fsevent (>= 0.9.3)
|
52
|
-
rb-inotify (>= 0.9)
|
53
|
-
little-plugger (1.1.3)
|
54
|
-
log4r (1.1.10)
|
55
|
-
logging (1.8.2)
|
56
|
-
little-plugger (>= 1.1.3)
|
57
|
-
multi_json (>= 1.8.4)
|
58
|
-
mime-types (2.4.3)
|
59
|
-
mini_portile (0.6.0)
|
60
|
-
minitest (4.3.2)
|
8
|
+
minitest (4.3.3)
|
61
9
|
minitest-reporters (0.14.24)
|
62
10
|
ansi
|
63
11
|
builder
|
64
12
|
minitest (>= 2.12, < 5.0)
|
65
13
|
powerbar
|
66
|
-
multi_json (1.10.1)
|
67
|
-
net-scp (1.1.2)
|
68
|
-
net-ssh (>= 2.6.5)
|
69
|
-
net-sftp (2.1.2)
|
70
|
-
net-ssh (>= 2.6.5)
|
71
14
|
net-ssh (2.9.2)
|
72
|
-
netrc (0.10.2)
|
73
|
-
nokogiri (1.6.3.1)
|
74
|
-
mini_portile (= 0.6.0)
|
75
|
-
nori (2.4.0)
|
76
15
|
powerbar (1.0.12)
|
77
16
|
ansi (~> 1.5.0)
|
78
17
|
hashie (>= 1.1.0)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
rb-kqueue (0.2.3)
|
83
|
-
ffi (>= 0.5.0)
|
84
|
-
rest-client (1.7.2)
|
85
|
-
mime-types (>= 1.16, < 3.0)
|
86
|
-
netrc (~> 0.7)
|
87
|
-
rubyntlm (0.4.0)
|
88
|
-
rubyzip (1.1.7)
|
89
|
-
timers (4.0.1)
|
90
|
-
hitimes
|
91
|
-
uuidtools (2.1.5)
|
92
|
-
vagrant-s3auth (1.0.2)
|
93
|
-
aws-sdk (~> 1.59.1)
|
94
|
-
wdm (0.1.0)
|
95
|
-
winrm (1.3.0)
|
96
|
-
builder (>= 2.1.2)
|
97
|
-
gssapi (~> 1.2)
|
98
|
-
gyoku (~> 1.0)
|
99
|
-
httpclient (~> 2.2, >= 2.2.0.2)
|
100
|
-
logging (~> 1.6, >= 1.6.1)
|
101
|
-
nori (~> 2.0)
|
102
|
-
rubyntlm (~> 0.4.0)
|
103
|
-
uuidtools (~> 2.1.2)
|
104
|
-
winrm-fs (0.1.0)
|
105
|
-
erubis (~> 2.7)
|
106
|
-
logging (~> 1.6, >= 1.6.1)
|
107
|
-
rubyzip (~> 1.1)
|
108
|
-
winrm (~> 1.3.0)
|
18
|
+
rake (10.4.2)
|
19
|
+
vagrant-wrapper (2.0.2)
|
20
|
+
yard (0.8.7.6)
|
109
21
|
|
110
22
|
PLATFORMS
|
111
23
|
ruby
|
112
24
|
|
113
25
|
DEPENDENCIES
|
114
|
-
json
|
115
|
-
minitest
|
116
|
-
minitest-reporters
|
117
|
-
net-ssh
|
118
|
-
|
119
|
-
vagrant-
|
26
|
+
json (~> 1.8.2)
|
27
|
+
minitest (~> 4.3.2)
|
28
|
+
minitest-reporters (~> 0.14.24)
|
29
|
+
net-ssh (~> 2.9.2)
|
30
|
+
rake (~> 10.4.2)
|
31
|
+
vagrant-wrapper (~> 2.0.2)
|
32
|
+
yard (~> 0.8.7.6)
|
data/README.md
CHANGED
@@ -1,2 +1,73 @@
|
|
1
1
|
# stackadmin
|
2
2
|
Gem to facilitate administration of Stackato deployments
|
3
|
+
|
4
|
+
## Install
|
5
|
+
```
|
6
|
+
gem install stackadmin
|
7
|
+
```
|
8
|
+
|
9
|
+
Then, `require 'stackadmin'` in your application.
|
10
|
+
|
11
|
+
## Requirements
|
12
|
+
- Ruby 2.0.0
|
13
|
+
- Target Stackato nodes must be set up for [Passwordless SSH access](http://www.linuxproblem.org/art_9.html).
|
14
|
+
|
15
|
+
## Testing
|
16
|
+
### The happy path
|
17
|
+
```
|
18
|
+
bundle install
|
19
|
+
bundle exec rake test
|
20
|
+
```
|
21
|
+
|
22
|
+
That should get everything set up for you. It will spin up a vagrant VM, run all tests, and then destroy the vagrant VM.
|
23
|
+
|
24
|
+
Problems? Read on.
|
25
|
+
|
26
|
+
### Setting up the test env
|
27
|
+
Install your dependencies:
|
28
|
+
```
|
29
|
+
bundle install
|
30
|
+
```
|
31
|
+
|
32
|
+
The test environment depends on [Vagrant](http://www.vagrantup.com/) (>= 1.5.1) and [vagrant-s3auth](https://github.com/WhoopInc/vagrant-s3auth).
|
33
|
+
|
34
|
+
The Vagrant VM depends on a custom basebox stored in our private S3 bucket.
|
35
|
+
|
36
|
+
You should have access credentials stored in the standard environment variables (i.e. `ENV['AWS_ACCESS_KEY_ID']` and `ENV['AWS_SECRET_ACCESS_KEY']`) in order to have Vagrant download the basebox automatically.
|
37
|
+
|
38
|
+
To verify that these requirements are met:
|
39
|
+
```
|
40
|
+
bundle exec rake test:setup_vagrant
|
41
|
+
```
|
42
|
+
|
43
|
+
To spin up the Vagrant box:
|
44
|
+
```
|
45
|
+
bundle exec rake test:start_env
|
46
|
+
```
|
47
|
+
|
48
|
+
To fulfill the passwordless SSH requirement to the `stackato` user in the test env:
|
49
|
+
```
|
50
|
+
bundle exec rake test:setup_env
|
51
|
+
```
|
52
|
+
|
53
|
+
That last command assumes that your personal public key is located in `~/.ssh/keys/id_rsa.pub`. If your key is located elsewhere, you can specify the location as follows:
|
54
|
+
```
|
55
|
+
bundle exec rake test:setup_env['~/.ssh/certs/me.pub']
|
56
|
+
```
|
57
|
+
where `<pub_key_location>` is the path to your public key.
|
58
|
+
|
59
|
+
You can also provide the public key location when using `rake test`:
|
60
|
+
```
|
61
|
+
bundle exec rake test['~/.ssh/certs/me.pub']
|
62
|
+
```
|
63
|
+
|
64
|
+
### Run tests
|
65
|
+
Currently, all tests are in a single suite:
|
66
|
+
```
|
67
|
+
bundle exec rake test:run
|
68
|
+
```
|
69
|
+
|
70
|
+
### Destroy the test env
|
71
|
+
```
|
72
|
+
bundle exec rake test:destroy_env
|
73
|
+
```
|
data/Rakefile
CHANGED
@@ -1,24 +1,42 @@
|
|
1
1
|
# Encoding: utf-8
|
2
2
|
require 'rake/testtask'
|
3
|
+
require_relative 'test/helpers/vagrant'
|
3
4
|
|
4
5
|
task :default => 'test'
|
5
6
|
|
6
7
|
namespace :test do
|
8
|
+
desc 'Setup vagrant'
|
9
|
+
task :setup_vagrant do
|
10
|
+
vagrant = VagrantWrapper.require_or_help_install(">= #{MIN_VAGRANT}")
|
11
|
+
puts "Vagrant v#{vagrant.vagrant_version} found!"
|
12
|
+
raise 'Unable to install "vagrant-s3auth" plugin!' unless vagrant.install_plugin('vagrant-s3auth')
|
13
|
+
|
14
|
+
missing = missing_aws_creds
|
15
|
+
if missing.empty?
|
16
|
+
puts 'S3 credentials found!'
|
17
|
+
else
|
18
|
+
raise "The following environment variables must be defined: #{missing.join(', ')}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
7
22
|
desc 'Spin up test env'
|
8
23
|
task :start_env do
|
9
|
-
Dir.chdir('test') { system(
|
24
|
+
Dir.chdir('test') { system("#{VAGRANT} up") }
|
10
25
|
end
|
11
26
|
|
12
27
|
desc 'Setup test env'
|
13
|
-
task :
|
14
|
-
pub_key_location = args[:pub_key_location] ||
|
28
|
+
task :setup_env, :pub_key_location do |_, args|
|
29
|
+
pub_key_location = args[:pub_key_location] || '~/.ssh/keys/id_rsa.pub'
|
30
|
+
pub_key_location = File.expand_path(pub_key_location)
|
15
31
|
pub_key = File.open(pub_key_location, 'r').read
|
16
|
-
Dir.chdir('test')
|
32
|
+
Dir.chdir('test') do
|
33
|
+
system("#{VAGRANT} ssh --command 'echo \"#{pub_key}\" | sudo tee -a /home/stackato/.ssh/authorized_keys' > /dev/null")
|
34
|
+
end
|
17
35
|
end
|
18
36
|
|
19
37
|
desc 'Destroy test env'
|
20
38
|
task :destroy_env do
|
21
|
-
Dir.chdir('test') { system(
|
39
|
+
Dir.chdir('test') { system("#{VAGRANT} destroy --force") }
|
22
40
|
end
|
23
41
|
end
|
24
42
|
|
@@ -27,11 +45,19 @@ Rake::TestTask.new('test:run') do |t|
|
|
27
45
|
end
|
28
46
|
|
29
47
|
desc 'Run all test tasks'
|
30
|
-
task :test do
|
31
|
-
%w( start_env
|
48
|
+
task :test, :pub_key_location do |_, args|
|
49
|
+
%w( setup_vagrant start_env setup_env run destroy_env ).each do |j|
|
32
50
|
job = "test:#{j}"
|
33
|
-
|
34
|
-
|
51
|
+
print "\e[35mRunning #{job}"
|
52
|
+
|
53
|
+
if j == 'setup_env' && !args[:pub_key_location].nil?
|
54
|
+
puts "['#{args[:pub_key_location]}']...\e[0m"
|
55
|
+
Rake::Task[job].invoke(args[:pub_key_location])
|
56
|
+
else
|
57
|
+
puts "...\e[0m"
|
58
|
+
Rake::Task[job].invoke
|
59
|
+
end
|
60
|
+
|
35
61
|
puts
|
36
62
|
end
|
37
63
|
end
|
data/lib/stackadmin/audit.rb
CHANGED
@@ -7,6 +7,11 @@ require_relative 'net-ssh'
|
|
7
7
|
class Stackadmin
|
8
8
|
private
|
9
9
|
|
10
|
+
# SSHes into the target node and populates the instance variables with the
|
11
|
+
# results of the audit
|
12
|
+
#
|
13
|
+
# @param target [String] the hostname, FQDN, or IP address of the target node
|
14
|
+
# @raise [Net::SSH::Exception] if unable to SSH to target
|
10
15
|
def audit(target = @id)
|
11
16
|
@id = target
|
12
17
|
log "Auditing #{@id}"
|
@@ -25,6 +30,10 @@ class Stackadmin
|
|
25
30
|
end
|
26
31
|
end
|
27
32
|
|
33
|
+
# Retrieves the current version designation of the target Stackato node
|
34
|
+
#
|
35
|
+
# @param ssh [Net::SSH::Connection::Session] an ssh session to the target node
|
36
|
+
# @return [String] the target node's installed Stackato version
|
28
37
|
def parse_version(ssh)
|
29
38
|
cmd = ssh.exec_sc!('kato info | grep Version')
|
30
39
|
ver = cmd[:stdout].split.last
|
@@ -36,6 +45,10 @@ class Stackadmin
|
|
36
45
|
ver
|
37
46
|
end
|
38
47
|
|
48
|
+
# Retrieves the current license assigned to the target Stackato node
|
49
|
+
#
|
50
|
+
# @param (see #parse_version)
|
51
|
+
# @return [Hash] the details of the installed license
|
39
52
|
def parse_license(ssh)
|
40
53
|
license = Hash.new
|
41
54
|
|
@@ -47,6 +60,10 @@ class Stackadmin
|
|
47
60
|
license
|
48
61
|
end
|
49
62
|
|
63
|
+
# Maps the target cluster's nodes into a hash
|
64
|
+
#
|
65
|
+
# @param (see #parse_version)
|
66
|
+
# @return [Hash] the details of each node in the cluster
|
50
67
|
def map_nodes(ssh)
|
51
68
|
cluster = Hash.new
|
52
69
|
|
@@ -58,7 +75,11 @@ class Stackadmin
|
|
58
75
|
cluster
|
59
76
|
end
|
60
77
|
|
61
|
-
#
|
78
|
+
# Audits the installation status of each patch listed in the manifest
|
79
|
+
#
|
80
|
+
# @param (see #parse_version)
|
81
|
+
# @todo It never seems to hit "Marked #{patch} as patched for #{k}"
|
82
|
+
# @todo I don't see how this catches a 'no patches available' state
|
62
83
|
def parse_patch_status(ssh)
|
63
84
|
# Initialize 'patched' hash for each cluster node
|
64
85
|
@nodes.each_value do |node|
|
@@ -85,6 +106,15 @@ class Stackadmin
|
|
85
106
|
end
|
86
107
|
end
|
87
108
|
|
109
|
+
# Parses `kato patch status` into a hash
|
110
|
+
#
|
111
|
+
# @param status [String] the output of `kato patch status`
|
112
|
+
# @return [Hash] list of uninstalled patches with list of nodes
|
113
|
+
# @example Returned Hash
|
114
|
+
# {
|
115
|
+
# 'patch1' => ['127.0.0.1', '171.234.56.78'],
|
116
|
+
# 'patch2' => ['127.0.0.1']
|
117
|
+
# }
|
88
118
|
def hashify_patch_status(status)
|
89
119
|
patches = Hash.new
|
90
120
|
|
@@ -108,6 +138,11 @@ class Stackadmin
|
|
108
138
|
patches
|
109
139
|
end
|
110
140
|
|
141
|
+
# Strips the header info from the output of `kato patch status`
|
142
|
+
#
|
143
|
+
# @param (see #hashify_patch_status)
|
144
|
+
# @return [String] status sans header info
|
145
|
+
# @raise [InvalidPatchStatus] if the given status is invalid
|
111
146
|
def strip_header(status)
|
112
147
|
log 'Stripping header'
|
113
148
|
|
@@ -127,6 +162,11 @@ class Stackadmin
|
|
127
162
|
s.join("\n")
|
128
163
|
end
|
129
164
|
|
165
|
+
# Strips the footer info from the output of `kato patch status`
|
166
|
+
#
|
167
|
+
# @param (see #hashify_patch_status)
|
168
|
+
# @return [String] status sans footer info
|
169
|
+
# @raise (see #strip_header)
|
130
170
|
def strip_footer(status)
|
131
171
|
log 'Stripping footer'
|
132
172
|
|
data/lib/stackadmin/base.rb
CHANGED
@@ -3,20 +3,62 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'open-uri'
|
5
5
|
|
6
|
+
# Core class of the Stackadmin gem
|
7
|
+
# @!attribute [r] id
|
8
|
+
# @return [String] the hostname/FQDN/IP address of the core Stackato node
|
9
|
+
# @!attribute [r] version
|
10
|
+
# @return [String] the version of Stackato installed on the node
|
11
|
+
# @!attribute [r] license
|
12
|
+
# @return [Hash] the details of the Stackato license installed on the node
|
13
|
+
# @example Returned Hash
|
14
|
+
# {
|
15
|
+
# 'organization' => 'org',
|
16
|
+
# 'serial' => 'S0F1E2D3C4D5',
|
17
|
+
# 'type' => 'paid',
|
18
|
+
# 'memory_limit' => 10,
|
19
|
+
# 'expiration' => '2014-12-31'
|
20
|
+
# }
|
21
|
+
# @!attribute [r] nodes
|
22
|
+
# @return [Hash] the details of each node in the Stackato cluster
|
23
|
+
# @example Returned Hash
|
24
|
+
# {
|
25
|
+
# '127.0.0.1' => {
|
26
|
+
# roles: ['base', 'core']
|
27
|
+
# },
|
28
|
+
# '171.234.56.78' => {
|
29
|
+
# roles: ['base', 'dea']
|
30
|
+
# }
|
31
|
+
# }
|
32
|
+
# @!attribute [r] fresh
|
33
|
+
# @return [Boolean] whether the available node data is from a fresh audit
|
6
34
|
class Stackadmin
|
7
35
|
attr_reader :id, :version, :license, :nodes, :fresh
|
8
36
|
|
37
|
+
# Initializes a new Stackadmin object
|
38
|
+
# @param target [String] hostname/FQDN/IP address of the target node
|
39
|
+
# @param port [Fixnum] port used to SSH into the target node
|
40
|
+
# @param debug [Boolean] toggles display of debug information
|
41
|
+
# @param yml_file [String, nil] pre-populated YAML file to pull node information from
|
42
|
+
# @return [Stackadmin] new, populated object
|
43
|
+
# @todo All paramaters, -target, should be in an opt[] hash
|
9
44
|
def initialize(target, port = 22, debug = false, yml_file = nil)
|
10
45
|
@port = port
|
11
46
|
@debug = debug
|
12
47
|
yml_file ? parse_yaml(target, yml_file) : audit(target)
|
13
48
|
end
|
14
49
|
|
50
|
+
# Refresh object data with a fresh audit
|
51
|
+
# @param target [String, nil] optional hostname/FQDN/IP address of the target node
|
52
|
+
# @todo Probably unneccessary? Deprecate?
|
15
53
|
def refresh(target = nil)
|
16
54
|
@id ||= target
|
17
55
|
audit
|
18
56
|
end
|
19
57
|
|
58
|
+
# Parse through an SSH config to find possible Stackato hostnames
|
59
|
+
# @param ssh_config [String] path to the SSH config file
|
60
|
+
# @param filter [Array<String>] list of regex strings to filter out of found hostnames
|
61
|
+
# @return [Array<String>] (optionally filtered) list of Stackato hostnames found
|
20
62
|
def self.find_instances(ssh_config = '~/.ssh/config', filter = [])
|
21
63
|
instances = IO.read(File.expand_path(ssh_config))
|
22
64
|
.scan(/^\s*Host (.*stackato.*)/)
|
@@ -25,6 +67,9 @@ class Stackadmin
|
|
25
67
|
instances
|
26
68
|
end
|
27
69
|
|
70
|
+
# Retrieves patch manifest
|
71
|
+
# @param uri [String, nil] path to the manifest.json
|
72
|
+
# @return [Hash] the patch manifest
|
28
73
|
def manifest(uri = nil)
|
29
74
|
unless @manifest
|
30
75
|
uri ||= "https://get.stackato.com/kato-patch/#{@version}/manifest.json"
|
@@ -36,6 +81,9 @@ class Stackadmin
|
|
36
81
|
|
37
82
|
private
|
38
83
|
|
84
|
+
# Outputs formatted debug messages to stderr
|
85
|
+
# @param msg [String] debug message to output
|
86
|
+
# @return [String] formatted debug message
|
39
87
|
def log(msg)
|
40
88
|
$stderr.puts ">> #{msg}" if @debug
|
41
89
|
end
|
@@ -1,16 +1,29 @@
|
|
1
1
|
class Stackadmin
|
2
|
+
# A general exception class, to act as the ancestor to all other
|
3
|
+
# Stackadmin exception classes.
|
2
4
|
class Exception < StandardError
|
3
5
|
end
|
4
6
|
|
7
|
+
# This exception is raised when the target node
|
8
|
+
# is not found in the provided YAML file
|
5
9
|
class YAMLTargetNotFound < Stackadmin::Exception
|
6
10
|
end
|
7
11
|
|
12
|
+
# This exception is raised when the output of a
|
13
|
+
# `kato patch status` call does not follow the
|
14
|
+
# expected template
|
8
15
|
class InvalidPatchStatus < Stackadmin::Exception
|
9
16
|
end
|
10
17
|
|
18
|
+
# This exception is raised when an invalid flag/option
|
19
|
+
# is used (or an invalid combination of flags/options)
|
20
|
+
# in a `kato` subcommand
|
11
21
|
class InvalidFlags < Stackadmin::Exception
|
12
22
|
end
|
13
23
|
|
24
|
+
# This exception is raised when a `kato` subcommand
|
25
|
+
# is called that does not follow the subcommand's
|
26
|
+
# prototype
|
14
27
|
class InvalidCommand < Stackadmin::Exception
|
15
28
|
end
|
16
29
|
end
|
data/lib/stackadmin/net-ssh.rb
CHANGED
@@ -2,15 +2,32 @@
|
|
2
2
|
|
3
3
|
require 'net/ssh'
|
4
4
|
|
5
|
-
#
|
6
|
-
# Yanked this from http://stackoverflow.com/a/13436242
|
5
|
+
# @see http://net-ssh.github.io/net-ssh/classes/Net/SSH/Connection/Session.html Class Net::SSH::Connection::Session docs
|
7
6
|
class Net::SSH::Connection::Session
|
7
|
+
# This exception is raised when the ssh call
|
8
|
+
# returns a non-zero exit code
|
8
9
|
class CommandFailed < Net::SSH::Exception
|
9
10
|
end
|
10
11
|
|
12
|
+
# This exception is raised when the ssh call
|
13
|
+
# is unable to execute
|
11
14
|
class CommandExecutionFailed < Net::SSH::Exception
|
12
15
|
end
|
13
16
|
|
17
|
+
# A convenience method for executing a command and interacting with it.
|
18
|
+
# @see http://net-ssh.github.io/net-ssh/classes/Net/SSH/Connection/Session.html#method-i-exec-21 Net::SSH::Connection::Session::exec! docs
|
19
|
+
# @see http://stackoverflow.com/a/13436242 Yanked from Stack Overflow
|
20
|
+
# @param command [String] command to execute over the SSH connection
|
21
|
+
# @raise [CommandExecutionFailed] if the command was unable to execute
|
22
|
+
# @raise [CommandFailed] if the command returns a non-zero exit code
|
23
|
+
# @return [Hash] A hash containing the STDOUT, STDERR, exit code & exit signal returned by the command
|
24
|
+
# @example Returned Hash
|
25
|
+
# {
|
26
|
+
# stdout: "Distributor ID:\tUbuntu\nDescription:\tUbuntu 12.04.5 LTS\nRelease:\t12.04\nCodename:\tprecise\n",
|
27
|
+
# stderr: "No LSB modules are available.\n",
|
28
|
+
# exit_code: 0,
|
29
|
+
# exit_signal: nil
|
30
|
+
# }
|
14
31
|
def exec_sc!(command)
|
15
32
|
stdout_data, stderr_data = '', ''
|
16
33
|
exit_code, exit_signal = nil, nil
|
data/lib/stackadmin/patch.rb
CHANGED
@@ -6,8 +6,27 @@ require_relative 'exceptions'
|
|
6
6
|
require_relative 'audit'
|
7
7
|
|
8
8
|
class Stackadmin
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# Installs patch(es) to the targeted Stackato cluster
|
10
|
+
# @param patches [String, Array<String>] the name of the patch (or list of patch names) to install
|
11
|
+
# @param node [nil, String] limit command to this node (given as an IP address)
|
12
|
+
# @param local [Boolean] limit command to the currently targeted Stackadmin node
|
13
|
+
# @param restart [Boolean] set to allow the target node's services to restart after a successful patch install
|
14
|
+
# @param force_update [Boolean] force an update to the patch manifest before installing any patches
|
15
|
+
# @param manifest_file [nil, String] execute the command with this manifest file
|
16
|
+
# @return [Hash] hash of hashes indicating the success status of each patch install to each node
|
17
|
+
# @example Returned Hash
|
18
|
+
# {
|
19
|
+
# '127.0.0.1' => {
|
20
|
+
# installed: ['patch1', 'patch3'],
|
21
|
+
# not_installed: ['patch2']
|
22
|
+
# },
|
23
|
+
# '171.234.56.78' => {
|
24
|
+
# installed: ['patch1', 'patch2', 'patch3'],
|
25
|
+
# not_installed: []
|
26
|
+
# }
|
27
|
+
# }
|
28
|
+
# @todo DRY this with ::mark! & ::reinstall! & ::revert!
|
29
|
+
# @todo (see #reset!)
|
11
30
|
def install!(patches = [], node = nil, local = false, restart = true, force_update = false, manifest_file = nil)
|
12
31
|
status = Hash.new
|
13
32
|
output = patch('install', patches, node: node, local: local, no_restart: !restart, force_update: force_update, manifest: manifest_file)
|
@@ -18,8 +37,6 @@ class Stackadmin
|
|
18
37
|
p.gsub!(/\e\[\d+m/, '') # Strip ASCII color
|
19
38
|
|
20
39
|
p.scan(/^Failed installing update (.+?) on (.+?)\.$/).each do |fp|
|
21
|
-
# fp = Array of failed patches
|
22
|
-
# e.g. [["patch1-name", "node1-ip"], ["patch2-name", "node2-ip"]]
|
23
40
|
status[fp[1]] = {
|
24
41
|
installed: [],
|
25
42
|
not_installed: []
|
@@ -29,7 +46,6 @@ class Stackadmin
|
|
29
46
|
end
|
30
47
|
|
31
48
|
p.scan(/^Successfully installed update (.+?) on (.+?)\.$/).each do |sp|
|
32
|
-
# sp = Array of successful patches. See fp example above
|
33
49
|
status[sp[1]] = {
|
34
50
|
installed: [],
|
35
51
|
not_installed: []
|
@@ -42,11 +58,22 @@ class Stackadmin
|
|
42
58
|
status
|
43
59
|
end
|
44
60
|
|
61
|
+
# Marks all of the targeted Stackato cluster's patch updates as uninstalled
|
62
|
+
# @param node [nil, String] limit command to this node (given as an IP address)
|
63
|
+
# @param local [Boolean] limit command to the currently targeted Stackadmin node
|
64
|
+
# @return [Boolean] whether or not the reset was successfully executed
|
65
|
+
# @todo collapse args into opt{}
|
45
66
|
def reset!(node = nil, local = false)
|
46
67
|
output = patch('reset', [], node: node, local: local)
|
47
68
|
output[0][:stdout] =~ /^Updates state reset.$/ ? true : false
|
48
69
|
end
|
49
70
|
|
71
|
+
# Updates the patch manifest for the targeted Stackato cluster
|
72
|
+
# @param node [nil, String] limit command to this node (given as an IP address)
|
73
|
+
# @param local [Boolean] limit command to the currently targeted Stackadmin node
|
74
|
+
# @param manifest_file [nil, String] execute the command with this manifest file
|
75
|
+
# @return [Boolean] whether or not the manifest was successfully updated
|
76
|
+
# @todo collapse args into opt{}
|
50
77
|
def update!(node = nil, local = false, manifest_file = nil)
|
51
78
|
output = v3_action_check('update') do
|
52
79
|
patch('update', [], node: node, local: local, manifest: manifest_file)
|
@@ -55,8 +82,20 @@ class Stackadmin
|
|
55
82
|
output[0][:stdout] =~ /^done!$/ ? true : false
|
56
83
|
end
|
57
84
|
|
85
|
+
# Marks patch(es) as installed or uninstalled on the targeted Stackato cluster
|
86
|
+
# @param patches [String, Array<String>] the name of the patch (or list of patch names) to mark
|
87
|
+
# @param mark_installed [Boolean] toggle marking as installed/uninstalled (true/false)
|
88
|
+
# @param node [nil, String] limit command to this node (given as an IP address)
|
89
|
+
# @param local [Boolean] limit command to the currently targeted Stackadmin node
|
90
|
+
# @return [Hash] hash of hashes listing which nodes had which patches successfully marked
|
91
|
+
# @example Returned Hash
|
92
|
+
# {
|
93
|
+
# '127.0.0.1' => ['patch1', 'patch2'],
|
94
|
+
# '171.234.56.78' => ['patch1', 'patch3']
|
95
|
+
# }
|
58
96
|
# Returns hash: { node1: [patch1, patch2], etc }
|
59
|
-
#
|
97
|
+
# @todo DRY this with ::install! & ::reinstall! & ::revert!
|
98
|
+
# @todo (see #reset!)
|
60
99
|
def mark!(patches = [], mark_installed = false, node = nil, local = false)
|
61
100
|
status = Hash.new
|
62
101
|
output = v3_action_check('mark') do
|
@@ -79,8 +118,12 @@ class Stackadmin
|
|
79
118
|
status
|
80
119
|
end
|
81
120
|
|
82
|
-
#
|
83
|
-
#
|
121
|
+
# Reinstalls patch(es) to the targeted Stackato cluster
|
122
|
+
# @param (see #install!)
|
123
|
+
# @return (see #install!)
|
124
|
+
# @example (see #install!)
|
125
|
+
# @todo DRY this with ::install! & ::mark! & ::revert!
|
126
|
+
# @todo (see #reset!)
|
84
127
|
def reinstall!(patches = [], node = nil, local = false, restart = true, force_update = false, manifest_file = nil)
|
85
128
|
if patches.empty?
|
86
129
|
raise InvalidCommand, "kato patch reinstall requires a specific patchname!"
|
@@ -119,8 +162,24 @@ class Stackadmin
|
|
119
162
|
end
|
120
163
|
end
|
121
164
|
|
122
|
-
#
|
123
|
-
#
|
165
|
+
# Revert patch(es) on the targeted Stackato cluster
|
166
|
+
# @param (see #install!)
|
167
|
+
# @return [Hash] hash of hashes indicating whether patches were successfully reverted on each node
|
168
|
+
# @example Returned Hash
|
169
|
+
# {
|
170
|
+
# '127.0.0.1' => {
|
171
|
+
# reverted: [patch1, patch4],
|
172
|
+
# not_reverted: [patch5],
|
173
|
+
# unable_to_revert: [patch3]
|
174
|
+
# },
|
175
|
+
# '171.234.56.78' => {
|
176
|
+
# reverted: [patch1, patch2],
|
177
|
+
# not_reverted: [],
|
178
|
+
# unable_to_revert: [patch3, patch4, patch5]
|
179
|
+
# }
|
180
|
+
# }
|
181
|
+
# @todo DRY this with ::install! & ::mark!
|
182
|
+
# @todo (see #reset!)
|
124
183
|
def revert!(patches = [], node = nil, local = false, restart = true, force_update = false, manifest_file = nil)
|
125
184
|
status = Hash.new
|
126
185
|
output = v3_action_check('revert') do
|
@@ -161,6 +220,9 @@ class Stackadmin
|
|
161
220
|
|
162
221
|
private
|
163
222
|
|
223
|
+
# Wrapper to throw an InvalidCommand exception on v3-only actions
|
224
|
+
# @param action [String] v3-only action
|
225
|
+
# @raise [InvalidCommand] if the targeted Stackato is not version 3
|
164
226
|
def v3_action_check(action)
|
165
227
|
if @version.to_i == 3
|
166
228
|
yield
|
@@ -169,14 +231,25 @@ class Stackadmin
|
|
169
231
|
end
|
170
232
|
end
|
171
233
|
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
#
|
234
|
+
# Helper method to parse and execute `kato patch` commands
|
235
|
+
# @param action [String] kato patch subcommand to execute
|
236
|
+
# @param patches [String, Array<String>] patch(es) to apply with action
|
237
|
+
# @param opts [Hash] flags/arguments to apply with the action
|
238
|
+
# @option opts [String] :node limit action to this node (given as an IP address)
|
239
|
+
# @option opts [Boolean] :local limit command to the currently targeted Stackadmin node
|
240
|
+
# @option opts [String] :manifest manifest file to use with the action
|
241
|
+
# @option opts [Boolean] :force_update force an update to the patch manifest before executing the action
|
242
|
+
# @option opts [Boolean] :mark_installed true marks the patch(es) as installed, false marks the patch(es) as uninstalled
|
243
|
+
# @option opts [Boolean] :no_restart set to prevent the action from restarting any associated services
|
244
|
+
# @raise [InvalidFlags] if both opts[:local] and opts[:node] are set
|
245
|
+
# @return [Hash] A hash containing the STDOUT, STDERR, exit code & exit signal returned by the action
|
246
|
+
# @example Returned Hash
|
247
|
+
# {
|
248
|
+
# stdout: "Updates state reset.\nWARNING: Stackato - no license installed\nUsing 31GB of 4GB (11GB over licensed limit)\nBuy a license: http://www.activestate.com/contact-stackato\n",
|
249
|
+
# stderr: nil,
|
250
|
+
# exit_code: 0,
|
251
|
+
# exit_signal: nil
|
252
|
+
# }
|
180
253
|
def patch(action, patches = [], opts = {})
|
181
254
|
raise InvalidFlags, "Can't target both #{opts[:node]} & \"local\"!" if (opts[:node] && opts[:local])
|
182
255
|
|
data/lib/stackadmin/version.rb
CHANGED
data/lib/stackadmin/yaml.rb
CHANGED
@@ -4,6 +4,7 @@ require 'yaml'
|
|
4
4
|
require_relative 'exceptions'
|
5
5
|
|
6
6
|
class Stackadmin
|
7
|
+
# @return [String] YAML-formatted target Stackato cluster info
|
7
8
|
def to_yaml
|
8
9
|
{ 'id' => @id,
|
9
10
|
'version' => @version,
|
@@ -13,6 +14,10 @@ class Stackadmin
|
|
13
14
|
|
14
15
|
private
|
15
16
|
|
17
|
+
# Parses a given YAML file into the current Stackadmin object attributes
|
18
|
+
# @param target [String] the target cluster's 'id'
|
19
|
+
# @param yml_file [String] a path to the YAML file to be parsed
|
20
|
+
# @raise [YAMLTargetNotFound] if given target is not found in the given yml_file
|
16
21
|
def parse_yaml(target, yml_file)
|
17
22
|
instances = YAML.load(File.read(yml_file))
|
18
23
|
|
data/stackadmin.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'stackadmin/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'stackadmin'
|
9
|
+
spec.version = Stackadmin::VERSION
|
10
|
+
spec.authors = ['Andres Rojas']
|
11
|
+
spec.email = ['andres.rojas@mtnsat.com']
|
12
|
+
spec.summary = 'A helper class for administering Stackato deployments'
|
13
|
+
spec.homepage = "https://github.com/mtnsat/stackadmin"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ['lib']
|
18
|
+
|
19
|
+
spec.add_dependency 'net-ssh'
|
20
|
+
spec.add_dependency 'json'
|
21
|
+
spec.add_development_dependency 'minitest'
|
22
|
+
spec.add_development_dependency 'minitest-reporters'
|
23
|
+
spec.add_development_dependency 'vagrant-wrapper'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'yard'
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'vagrant-wrapper'
|
4
|
+
|
5
|
+
MIN_VAGRANT = '1.5.1'
|
6
|
+
VAGRANT = "vagrant --min-ver=#{MIN_VAGRANT}"
|
7
|
+
|
8
|
+
class VagrantWrapper
|
9
|
+
def plugin_installed?(plugin)
|
10
|
+
(get_output('plugin list') =~ /^#{plugin} \((\d|\.)+\)$/) ? true : false
|
11
|
+
end
|
12
|
+
|
13
|
+
def install_plugin(plugin)
|
14
|
+
installed = false
|
15
|
+
|
16
|
+
if plugin_installed?(plugin)
|
17
|
+
installed = true
|
18
|
+
else
|
19
|
+
puts "Installing #{plugin}..."
|
20
|
+
output = get_output("plugin install #{plugin}")
|
21
|
+
installed = true if output =~ /^Installed the plugin '#{plugin} \((\d|\.)+\)'!$/
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "#{plugin} installed!" if installed
|
25
|
+
installed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def missing_aws_creds
|
30
|
+
%w( AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY ) - ENV.keys
|
31
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stackadmin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andres Rojas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name: vagrant
|
70
|
+
name: vagrant-wrapper
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - '>='
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rake
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - '>='
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: yard
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - '>='
|
@@ -116,6 +116,7 @@ extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
118
|
- .gitignore
|
119
|
+
- CHANGELOG
|
119
120
|
- Gemfile
|
120
121
|
- Gemfile.lock
|
121
122
|
- README.md
|
@@ -128,7 +129,9 @@ files:
|
|
128
129
|
- lib/stackadmin/patch.rb
|
129
130
|
- lib/stackadmin/version.rb
|
130
131
|
- lib/stackadmin/yaml.rb
|
132
|
+
- stackadmin.gemspec
|
131
133
|
- test/Vagrantfile
|
134
|
+
- test/helpers/vagrant.rb
|
132
135
|
- test/stackadmin_test.rb
|
133
136
|
homepage: https://github.com/mtnsat/stackadmin
|
134
137
|
licenses: []
|
@@ -155,4 +158,6 @@ specification_version: 4
|
|
155
158
|
summary: A helper class for administering Stackato deployments
|
156
159
|
test_files:
|
157
160
|
- test/Vagrantfile
|
161
|
+
- test/helpers/vagrant.rb
|
158
162
|
- test/stackadmin_test.rb
|
163
|
+
has_rdoc:
|