stackadmin 0.1.0
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 +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +119 -0
- data/README.md +2 -0
- data/Rakefile +37 -0
- data/lib/stackadmin/audit.rb +145 -0
- data/lib/stackadmin/base.rb +42 -0
- data/lib/stackadmin/exceptions.rb +16 -0
- data/lib/stackadmin/net-ssh.rb +49 -0
- data/lib/stackadmin/patch.rb +232 -0
- data/lib/stackadmin/version.rb +5 -0
- data/lib/stackadmin/yaml.rb +32 -0
- data/lib/stackadmin.rb +7 -0
- data/test/Vagrantfile +14 -0
- data/test/stackadmin_test.rb +325 -0
- metadata +158 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 9356c67e44d2b41f466700e39b383e6ddf8599d9
|
|
4
|
+
data.tar.gz: 90080f91deaa13008ee34454051567d44c1ee291
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7cdf3b9b5f4f8157f00c5d3826ea8643fdae871c4105004e0ae11ec8910b14c1b4a448f1b1c17fdeb61a5c60b85c70837f27d082b6dca0416d7d85c836abb058
|
|
7
|
+
data.tar.gz: 4925bc36f4ab907e38ee5fc41b4aa2d553fe1b615685d7d26040ffa2e77e2b352bde2bd1604b7f887abf738e5d07e7b28473103e78d866aecfb3c27ec9143ba2
|
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test/.vagrant
|
data/Gemfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
gem 'net-ssh'
|
|
4
|
+
gem 'json'
|
|
5
|
+
|
|
6
|
+
group :development do
|
|
7
|
+
gem 'minitest'
|
|
8
|
+
gem 'minitest-reporters'
|
|
9
|
+
gem 'vagrant', git: 'https://github.com/mitchellh/vagrant.git'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
group :plugins do
|
|
13
|
+
gem 'vagrant-s3auth'
|
|
14
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
GEM
|
|
24
|
+
remote: https://rubygems.org/
|
|
25
|
+
specs:
|
|
26
|
+
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
|
+
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
|
+
hashie (3.4.0)
|
|
45
|
+
hitimes (1.2.2)
|
|
46
|
+
httpclient (2.6.0.1)
|
|
47
|
+
i18n (0.6.11)
|
|
48
|
+
json (1.8.2)
|
|
49
|
+
listen (2.8.5)
|
|
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)
|
|
61
|
+
minitest-reporters (0.14.24)
|
|
62
|
+
ansi
|
|
63
|
+
builder
|
|
64
|
+
minitest (>= 2.12, < 5.0)
|
|
65
|
+
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
|
+
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
|
+
powerbar (1.0.12)
|
|
77
|
+
ansi (~> 1.5.0)
|
|
78
|
+
hashie (>= 1.1.0)
|
|
79
|
+
rb-fsevent (0.9.4)
|
|
80
|
+
rb-inotify (0.9.5)
|
|
81
|
+
ffi (>= 0.5.0)
|
|
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)
|
|
109
|
+
|
|
110
|
+
PLATFORMS
|
|
111
|
+
ruby
|
|
112
|
+
|
|
113
|
+
DEPENDENCIES
|
|
114
|
+
json
|
|
115
|
+
minitest
|
|
116
|
+
minitest-reporters
|
|
117
|
+
net-ssh
|
|
118
|
+
vagrant!
|
|
119
|
+
vagrant-s3auth
|
data/README.md
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Encoding: utf-8
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
|
|
4
|
+
task :default => 'test'
|
|
5
|
+
|
|
6
|
+
namespace :test do
|
|
7
|
+
desc 'Spin up test env'
|
|
8
|
+
task :start_env do
|
|
9
|
+
Dir.chdir('test') { system('bundle exec vagrant up') }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc 'Setup test env'
|
|
13
|
+
task :setup, :pub_key_location do |t, args|
|
|
14
|
+
pub_key_location = args[:pub_key_location] || File.expand_path('~/.ssh/keys/id_rsa.pub')
|
|
15
|
+
pub_key = File.open(pub_key_location, 'r').read
|
|
16
|
+
Dir.chdir('test') { system("vagrant ssh --command 'echo \"#{pub_key}\" | sudo tee -a /home/stackato/.ssh/authorized_keys' > /dev/null") }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc 'Destroy test env'
|
|
20
|
+
task :destroy_env do
|
|
21
|
+
Dir.chdir('test') { system('bundle exec vagrant destroy --force') }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Rake::TestTask.new('test:run') do |t|
|
|
26
|
+
t.test_files = FileList['test/*_test.rb']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc 'Run all test tasks'
|
|
30
|
+
task :test do
|
|
31
|
+
%w( start_env setup run destroy_env ).each do |j|
|
|
32
|
+
job = "test:#{j}"
|
|
33
|
+
puts "\e[35mRunning #{job}...\e[0m"
|
|
34
|
+
Rake::Task[job].invoke
|
|
35
|
+
puts
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'exceptions'
|
|
5
|
+
require_relative 'net-ssh'
|
|
6
|
+
|
|
7
|
+
class Stackadmin
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def audit(target = @id)
|
|
11
|
+
@id = target
|
|
12
|
+
log "Auditing #{@id}"
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
Net::SSH.start(target, 'stackato', port: @port) do |ssh|
|
|
16
|
+
@version = parse_version(ssh)
|
|
17
|
+
@license = parse_license(ssh)
|
|
18
|
+
@nodes = map_nodes(ssh)
|
|
19
|
+
parse_patch_status(ssh)
|
|
20
|
+
@fresh = true
|
|
21
|
+
end
|
|
22
|
+
rescue Net::SSH::Exception => e
|
|
23
|
+
log "#{@id} audit failed! #{e}"
|
|
24
|
+
@fresh = false
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse_version(ssh)
|
|
29
|
+
cmd = ssh.exec_sc!('kato info | grep Version')
|
|
30
|
+
ver = cmd[:stdout].split.last
|
|
31
|
+
log "Retrieved version: #{ver}"
|
|
32
|
+
|
|
33
|
+
ver.gsub!(/v/, '') if ver =~ /^v\d.*$/
|
|
34
|
+
log "Parsed as version: #{ver}"
|
|
35
|
+
|
|
36
|
+
ver
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse_license(ssh)
|
|
40
|
+
license = Hash.new
|
|
41
|
+
|
|
42
|
+
ssh.exec_sc!('kato config get cluster license')[:stdout].split("\n").each do |line|
|
|
43
|
+
line = line.split(': ')
|
|
44
|
+
license[line[0]] = line[1]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
license
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def map_nodes(ssh)
|
|
51
|
+
cluster = Hash.new
|
|
52
|
+
|
|
53
|
+
ssh.exec_sc!('kato node list')[:stdout].split("\n").each do |node|
|
|
54
|
+
n = node.split
|
|
55
|
+
cluster[n[0]] = { roles: n[1..-1] }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
cluster
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# TODO: It never seems to hit "Marked #{patch} as patched for #{k}"
|
|
62
|
+
def parse_patch_status(ssh)
|
|
63
|
+
# Initialize 'patched' hash for each cluster node
|
|
64
|
+
@nodes.each_value do |node|
|
|
65
|
+
node[:patched] = Hash.new
|
|
66
|
+
manifest['patches'].keys.each { |patch| node[:patched][patch] = true }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
status = ssh.exec_sc!('kato patch status')[:stdout]
|
|
70
|
+
log "kato patch status:\n#{status}"
|
|
71
|
+
|
|
72
|
+
patches = hashify_patch_status(status)
|
|
73
|
+
patches.each do |patch, info|
|
|
74
|
+
if info.empty?
|
|
75
|
+
@nodes.each do |k, v|
|
|
76
|
+
v[:patched][patch] = false
|
|
77
|
+
log "Marked #{patch} as patched for #{k}."
|
|
78
|
+
end
|
|
79
|
+
elsif info.has_key?(:unpatched)
|
|
80
|
+
@nodes.each do |k, v|
|
|
81
|
+
v[:patched][patch] = false if info[:unpatched].include?(k)
|
|
82
|
+
log "Marked #{patch} as unpatched for #{k}."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def hashify_patch_status(status)
|
|
89
|
+
patches = Hash.new
|
|
90
|
+
|
|
91
|
+
unless status =~ /^No Stackato updates are available for #{@version}\.$/
|
|
92
|
+
status = strip_footer(strip_header(status)).split("\n\n")
|
|
93
|
+
|
|
94
|
+
status.each do |patch|
|
|
95
|
+
patch = patch.split("\n").map { |p| p.lstrip.split(': ') }
|
|
96
|
+
name = patch[0][0]
|
|
97
|
+
patches[name] = Hash.new
|
|
98
|
+
|
|
99
|
+
patch.each do |line|
|
|
100
|
+
if line[0] == 'to be installed on'
|
|
101
|
+
list = line[1].lstrip
|
|
102
|
+
patches[name][:unpatched] = (list == 'none') ? [] : list.split(', ')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
patches
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def strip_header(status)
|
|
112
|
+
log 'Stripping header'
|
|
113
|
+
|
|
114
|
+
s = status.split("\n")
|
|
115
|
+
major_ver = @version.to_i
|
|
116
|
+
header_end = case major_ver
|
|
117
|
+
when 2
|
|
118
|
+
/^Known updates for Stackato #{@version}:$/
|
|
119
|
+
when 3
|
|
120
|
+
/^\d+ updates are available for #{@version}.$/
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
break if s.shift.match header_end until s.empty?
|
|
124
|
+
raise InvalidPatchStatus, "kato patch status:\n\n#{status}" if s.empty?
|
|
125
|
+
|
|
126
|
+
s.shift if major_ver == 3 # Strip superfluous leading empty line
|
|
127
|
+
s.join("\n")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def strip_footer(status)
|
|
131
|
+
log 'Stripping footer'
|
|
132
|
+
|
|
133
|
+
s = status.split("\n")
|
|
134
|
+
until s.empty?
|
|
135
|
+
line = s.pop
|
|
136
|
+
if line =~ /^\t+.+: .+$/
|
|
137
|
+
s << line
|
|
138
|
+
break
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
raise InvalidPatchStatus, "kato patch status:\n\n#{status}" if s.empty?
|
|
143
|
+
s.join("\n")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'open-uri'
|
|
5
|
+
|
|
6
|
+
class Stackadmin
|
|
7
|
+
attr_reader :id, :version, :license, :nodes, :fresh
|
|
8
|
+
|
|
9
|
+
def initialize(target, port = 22, debug = false, yml_file = nil)
|
|
10
|
+
@port = port
|
|
11
|
+
@debug = debug
|
|
12
|
+
yml_file ? parse_yaml(target, yml_file) : audit(target)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def refresh(target = nil)
|
|
16
|
+
@id ||= target
|
|
17
|
+
audit
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.find_instances(ssh_config = '~/.ssh/config', filter = [])
|
|
21
|
+
instances = IO.read(File.expand_path(ssh_config))
|
|
22
|
+
.scan(/^\s*Host (.*stackato.*)/)
|
|
23
|
+
.flatten
|
|
24
|
+
filter.each { |f| instances.delete_if { |host| host =~ /#{f}/ } }
|
|
25
|
+
instances
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def manifest(uri = nil)
|
|
29
|
+
unless @manifest
|
|
30
|
+
uri ||= "https://get.stackato.com/kato-patch/#{@version}/manifest.json"
|
|
31
|
+
log "Retrieving latest manifest from: #{uri}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@manifest ||= JSON.load(open(uri)) rescue nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def log(msg)
|
|
40
|
+
$stderr.puts ">> #{msg}" if @debug
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Stackadmin
|
|
2
|
+
class Exception < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class YAMLTargetNotFound < Stackadmin::Exception
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class InvalidPatchStatus < Stackadmin::Exception
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class InvalidFlags < Stackadmin::Exception
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class InvalidCommand < Stackadmin::Exception
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'net/ssh'
|
|
4
|
+
|
|
5
|
+
# Grab exit codes from remote commands
|
|
6
|
+
# Yanked this from http://stackoverflow.com/a/13436242
|
|
7
|
+
class Net::SSH::Connection::Session
|
|
8
|
+
class CommandFailed < Net::SSH::Exception
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class CommandExecutionFailed < Net::SSH::Exception
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def exec_sc!(command)
|
|
15
|
+
stdout_data, stderr_data = '', ''
|
|
16
|
+
exit_code, exit_signal = nil, nil
|
|
17
|
+
self.open_channel do |channel|
|
|
18
|
+
channel.exec(command) do |_, success|
|
|
19
|
+
raise CommandExecutionFailed, "Command \"#{command}\" was unable to execute" unless success
|
|
20
|
+
|
|
21
|
+
channel.on_data do |_, data|
|
|
22
|
+
stdout_data += data
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
channel.on_extended_data do |_, _, data|
|
|
26
|
+
stderr_data += data
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
channel.on_request('exit-status') do |_, data|
|
|
30
|
+
exit_code = data.read_long
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
channel.on_request('exit-signal') do |_, data|
|
|
34
|
+
exit_signal = data.read_long
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
self.loop
|
|
39
|
+
|
|
40
|
+
raise CommandFailed, "Command \"#{command}\" returned exit code #{exit_code}" unless exit_code == 0
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
stdout: stdout_data,
|
|
44
|
+
stderr: stderr_data,
|
|
45
|
+
exit_code: exit_code,
|
|
46
|
+
exit_signal: exit_signal
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'net-ssh'
|
|
5
|
+
require_relative 'exceptions'
|
|
6
|
+
require_relative 'audit'
|
|
7
|
+
|
|
8
|
+
class Stackadmin
|
|
9
|
+
# Returns hash of hashes: { node1: { installed: [patch1], not_installed: [patch2] }, etc }
|
|
10
|
+
# TODO: DRY this with ::mark! & ::reinstall! & ::revert!
|
|
11
|
+
def install!(patches = [], node = nil, local = false, restart = true, force_update = false, manifest_file = nil)
|
|
12
|
+
status = Hash.new
|
|
13
|
+
output = patch('install', patches, node: node, local: local, no_restart: !restart, force_update: force_update, manifest: manifest_file)
|
|
14
|
+
|
|
15
|
+
output.each do |p|
|
|
16
|
+
p = p[:stdout]
|
|
17
|
+
log "Job output:\n#{p}"
|
|
18
|
+
p.gsub!(/\e\[\d+m/, '') # Strip ASCII color
|
|
19
|
+
|
|
20
|
+
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
|
+
status[fp[1]] = {
|
|
24
|
+
installed: [],
|
|
25
|
+
not_installed: []
|
|
26
|
+
} unless status.has_key?(fp[1])
|
|
27
|
+
|
|
28
|
+
status[fp[1]][:not_installed] << fp[0]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
p.scan(/^Successfully installed update (.+?) on (.+?)\.$/).each do |sp|
|
|
32
|
+
# sp = Array of successful patches. See fp example above
|
|
33
|
+
status[sp[1]] = {
|
|
34
|
+
installed: [],
|
|
35
|
+
not_installed: []
|
|
36
|
+
} unless status.has_key?(sp[1])
|
|
37
|
+
|
|
38
|
+
status[sp[1]][:installed] << sp[0]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
status
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset!(node = nil, local = false)
|
|
46
|
+
output = patch('reset', [], node: node, local: local)
|
|
47
|
+
output[0][:stdout] =~ /^Updates state reset.$/ ? true : false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update!(node = nil, local = false, manifest_file = nil)
|
|
51
|
+
output = v3_action_check('update') do
|
|
52
|
+
patch('update', [], node: node, local: local, manifest: manifest_file)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
output[0][:stdout] =~ /^done!$/ ? true : false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns hash: { node1: [patch1, patch2], etc }
|
|
59
|
+
# TODO: DRY this with ::install! & ::reinstall! & ::revert!
|
|
60
|
+
def mark!(patches = [], mark_installed = false, node = nil, local = false)
|
|
61
|
+
status = Hash.new
|
|
62
|
+
output = v3_action_check('mark') do
|
|
63
|
+
patch('mark', patches, node: node, local: local, mark_installed: mark_installed)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
output.each do |p|
|
|
67
|
+
p = p[:stdout]
|
|
68
|
+
log "Job output:\n#{p}"
|
|
69
|
+
p.gsub!(/\e\[\d+m/, '') # Strip ASCII color
|
|
70
|
+
|
|
71
|
+
p.scan(/^Successfully marked patch (.+?) as #{'not ' unless mark_installed}installed on (.+?)\.$/).each do |sp|
|
|
72
|
+
# sp = Array of successfully marked patches
|
|
73
|
+
# e.g. [["patch1-name", "node1-ip"], ["patch2-name", "node2-ip"]]
|
|
74
|
+
status[sp[1]] = Array.new unless status.has_key?(sp[1])
|
|
75
|
+
status[sp[1]] << sp[0]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
status
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns hash of hashes: { node1: { installed: [patch1], not_installed: [patch2] }, etc }
|
|
83
|
+
# TODO: DRY this with ::install! & ::mark! & ::revert!
|
|
84
|
+
def reinstall!(patches = [], node = nil, local = false, restart = true, force_update = false, manifest_file = nil)
|
|
85
|
+
if patches.empty?
|
|
86
|
+
raise InvalidCommand, "kato patch reinstall requires a specific patchname!"
|
|
87
|
+
else
|
|
88
|
+
status = Hash.new
|
|
89
|
+
output = patch('reinstall', patches, node: node, local: local, no_restart: !restart, force_update: force_update, manifest: manifest_file)
|
|
90
|
+
|
|
91
|
+
output.each do |p|
|
|
92
|
+
p = p[:stdout]
|
|
93
|
+
log "Job output:\n#{p}"
|
|
94
|
+
p.gsub!(/\e\[\d+m/, '') # Strip ASCII color
|
|
95
|
+
|
|
96
|
+
p.scan(/^Failed installing update (.+?) on (.+?)\.$/).each do |fp|
|
|
97
|
+
# fp = Array of failed patches
|
|
98
|
+
# e.g. [["patch1-name", "node1-ip"], ["patch2-name", "node2-ip"]]
|
|
99
|
+
status[fp[1]] = {
|
|
100
|
+
installed: [],
|
|
101
|
+
not_installed: []
|
|
102
|
+
} unless status.has_key?(fp[1])
|
|
103
|
+
|
|
104
|
+
status[fp[1]][:not_installed] << fp[0]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
p.scan(/^Successfully installed update (.+?) on (.+?)\.$/).each do |sp|
|
|
108
|
+
# sp = Array of successful patches. See fp example above
|
|
109
|
+
status[sp[1]] = {
|
|
110
|
+
installed: [],
|
|
111
|
+
not_installed: []
|
|
112
|
+
} unless status.has_key?(sp[1])
|
|
113
|
+
|
|
114
|
+
status[sp[1]][:installed] << sp[0]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
status
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns hash of hashes: { node1: { reverted: [patch1], not_reverted: [patch2], unable_to_revert: [patch3] }, etc }
|
|
123
|
+
# TODO: DRY this with ::install! & ::mark!
|
|
124
|
+
def revert!(patches = [], node = nil, local = false, restart = true, force_update = false, manifest_file = nil)
|
|
125
|
+
status = Hash.new
|
|
126
|
+
output = v3_action_check('revert') do
|
|
127
|
+
patch('revert', patches, node: node, local: local, no_restart: !restart, force_update: force_update, manifest: manifest_file)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
output.each do |p|
|
|
131
|
+
p = p[:stdout]
|
|
132
|
+
log "Job output:\n#{p}"
|
|
133
|
+
p.gsub!(/\e\[\d+m/, '') # Strip ASCII color
|
|
134
|
+
|
|
135
|
+
p.scan(/^Failed reverting update (.+?) on (.+?)\.$/).each do |fp|
|
|
136
|
+
# fp = Array of failed patches
|
|
137
|
+
# e.g. [["patch1-name", "node1-ip"], ["patch2-name", "node2-ip"]]
|
|
138
|
+
status[fp[1]] = {
|
|
139
|
+
reverted: [],
|
|
140
|
+
not_reverted: [],
|
|
141
|
+
unable_to_revert: []
|
|
142
|
+
} unless status.has_key?(fp[1])
|
|
143
|
+
|
|
144
|
+
status[fp[1]][:not_installed] << fp[0]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
p.scan(/^Successfully reverted update (.+?) on (.+?)\.$/).each do |sp|
|
|
148
|
+
# sp = Array of successful patches. See fp example above
|
|
149
|
+
status[sp[1]] = {
|
|
150
|
+
reverted: [],
|
|
151
|
+
not_reverted: [],
|
|
152
|
+
unable_to_revert: []
|
|
153
|
+
} unless status.has_key?(sp[1])
|
|
154
|
+
|
|
155
|
+
status[sp[1]][:installed] << sp[0]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
status
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def v3_action_check(action)
|
|
165
|
+
if @version.to_i == 3
|
|
166
|
+
yield
|
|
167
|
+
else
|
|
168
|
+
raise InvalidCommand, "kato patch #{action} unavailable on Stackato v#{@version}!"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Args:
|
|
173
|
+
# - action: string, required
|
|
174
|
+
# -- subcommand to use for 'kato patch'
|
|
175
|
+
# - patches: array
|
|
176
|
+
# -- list of patches to apply with action
|
|
177
|
+
# -- string accepted, but assumed to denote a single patch
|
|
178
|
+
# - opts: hash, optional
|
|
179
|
+
# -- flags/arguments to apply to the kato patch action
|
|
180
|
+
def patch(action, patches = [], opts = {})
|
|
181
|
+
raise InvalidFlags, "Can't target both #{opts[:node]} & \"local\"!" if (opts[:node] && opts[:local])
|
|
182
|
+
|
|
183
|
+
cmd = "kato patch #{action}"
|
|
184
|
+
|
|
185
|
+
if @version.to_i == 3
|
|
186
|
+
cmd += " --node #{opts[:node]}" if opts[:node]
|
|
187
|
+
cmd += " --manifest #{opts[:manifest]}" if opts[:manifest]
|
|
188
|
+
cmd += ' --force-update' if opts[:force_update]
|
|
189
|
+
|
|
190
|
+
if action == 'mark'
|
|
191
|
+
cmd += opts[:mark_installed] ? ' --installed' : ' --notinstalled'
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if opts[:manifest] || opts[:force_update]
|
|
195
|
+
@manifest = nil
|
|
196
|
+
manifest(opts[:manifest])
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
cmd += ' --no-restart' if opts[:no_restart]
|
|
201
|
+
cmd += case @version.to_i
|
|
202
|
+
when 2
|
|
203
|
+
' --only-this-node'
|
|
204
|
+
when 3
|
|
205
|
+
' --local'
|
|
206
|
+
end if opts[:local]
|
|
207
|
+
|
|
208
|
+
patches = [patches] if patches.class == String
|
|
209
|
+
output = Array.new
|
|
210
|
+
|
|
211
|
+
begin
|
|
212
|
+
Net::SSH.start(@id, 'stackato', port: @port) do |ssh|
|
|
213
|
+
if patches.empty?
|
|
214
|
+
log "Starting job: #{cmd}"
|
|
215
|
+
output << ssh.exec_sc!(cmd)
|
|
216
|
+
else
|
|
217
|
+
patches.each do |patch|
|
|
218
|
+
log "Starting job: #{cmd} #{patch}"
|
|
219
|
+
output << ssh.exec_sc!("#{cmd} #{patch}")
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
parse_patch_status(ssh)
|
|
224
|
+
end
|
|
225
|
+
rescue Net::SSH::Exception => e
|
|
226
|
+
patches = patches.empty? ? '[all]' : patches.to_s
|
|
227
|
+
log "kato patch failed! \"#{cmd} #{patches}\" -- #{e}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
output
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require_relative 'exceptions'
|
|
5
|
+
|
|
6
|
+
class Stackadmin
|
|
7
|
+
def to_yaml
|
|
8
|
+
{ 'id' => @id,
|
|
9
|
+
'version' => @version,
|
|
10
|
+
'license' => @license,
|
|
11
|
+
'nodes' => @nodes }.to_yaml
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def parse_yaml(target, yml_file)
|
|
17
|
+
instances = YAML.load(File.read(yml_file))
|
|
18
|
+
|
|
19
|
+
instances.each do |stk|
|
|
20
|
+
if stk['id'] == target
|
|
21
|
+
@id = stk['id']
|
|
22
|
+
@version = stk['version']
|
|
23
|
+
@license = stk['license']
|
|
24
|
+
@nodes = stk['nodes']
|
|
25
|
+
@fresh = false
|
|
26
|
+
return true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
raise YAMLTargetNotFound, "Could not find '#{target}' in #{yml_file}!"
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/stackadmin.rb
ADDED
data/test/Vagrantfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- mode: ruby -*-
|
|
2
|
+
# vi: set ft=ruby :
|
|
3
|
+
|
|
4
|
+
SSH_CONFIG = <<-EOS
|
|
5
|
+
Host *
|
|
6
|
+
StrictHostKeyChecking no
|
|
7
|
+
UserKnownHostsFile=/dev/null
|
|
8
|
+
EOS
|
|
9
|
+
|
|
10
|
+
Vagrant.configure(2) do |config|
|
|
11
|
+
config.vm.box = 'stackato-v3.4.2'
|
|
12
|
+
config.vm.box_url = 's3://deployination/stackato-v3.4.2.box'
|
|
13
|
+
config.vm.provision 'shell', inline: "echo '#{SSH_CONFIG}' > ~stackato/.ssh/config"
|
|
14
|
+
end
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'minitest/reporters'
|
|
3
|
+
require_relative '../lib/stackadmin'
|
|
4
|
+
|
|
5
|
+
MiniTest::Reporters.use! [MiniTest::Reporters::SpecReporter.new]
|
|
6
|
+
|
|
7
|
+
TEST_ENV = '127.0.0.1'
|
|
8
|
+
|
|
9
|
+
describe Stackadmin do
|
|
10
|
+
subject { Stackadmin.new(TEST_ENV, 2222) }
|
|
11
|
+
|
|
12
|
+
describe 'instantiation' do
|
|
13
|
+
it 'requires a target argument' do
|
|
14
|
+
lambda { Stackadmin.new }.must_raise ArgumentError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'requires a valid target' do
|
|
18
|
+
lambda { Stackadmin.new('foobaz') }.must_raise SocketError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'can connect to a valid Stackato instance' do
|
|
22
|
+
subject.must_be_instance_of Stackadmin
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'defaults to auditing a live deployment' do
|
|
26
|
+
assert subject.fresh
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe 'auditing' do
|
|
31
|
+
it 'retrieves the Stackato version' do
|
|
32
|
+
subject.version.must_equal '3.4.2'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'retrieves the active license info' do
|
|
36
|
+
subject.license.must_be_instance_of Hash
|
|
37
|
+
subject.license.must_be_empty
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'retrieves the latest manifest from ActiveState' do
|
|
41
|
+
subject.manifest.must_be_instance_of Hash
|
|
42
|
+
subject.manifest['stackato_version'].must_equal subject.version
|
|
43
|
+
%w( kato_patch patches ).each do |k|
|
|
44
|
+
subject.manifest(k).must_be_instance_of Hash
|
|
45
|
+
subject.manifest(k).wont_be_empty
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# TODO: We should be testing more than a single-node microcloud
|
|
50
|
+
describe 'nodes' do
|
|
51
|
+
it 'maps cluster nodes to a hash' do
|
|
52
|
+
subject.nodes.must_be_instance_of Hash
|
|
53
|
+
subject.nodes.size.must_equal 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
let(:local_node) { subject.nodes[TEST_ENV] }
|
|
57
|
+
|
|
58
|
+
it 'retrieves role info for each node' do
|
|
59
|
+
subject.nodes.each_value do |node|
|
|
60
|
+
node[:roles].must_be_instance_of Array
|
|
61
|
+
node[:roles].wont_be_empty
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local_node[:roles].must_include 'base'
|
|
65
|
+
local_node[:roles].must_include 'primary'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'retrieves patch info for each node' do
|
|
69
|
+
subject.nodes.each_value do |node|
|
|
70
|
+
node[:patched].must_be_instance_of Hash
|
|
71
|
+
node[:patched].wont_be_empty
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe 'patching' do
|
|
78
|
+
describe 'install!' do
|
|
79
|
+
it 'all patches to all nodes by default' do
|
|
80
|
+
skip 'need to isolate environments for testing'
|
|
81
|
+
|
|
82
|
+
subject.nodes.each_value do |node|
|
|
83
|
+
node[:patched].each_value { |patch| refute patch }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
subject.install!
|
|
87
|
+
|
|
88
|
+
subject.nodes.each_value do |node|
|
|
89
|
+
node[:patched].each_value { |patch| assert patch }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
let(:local_node) { subject.nodes[TEST_ENV] }
|
|
94
|
+
|
|
95
|
+
it 'install one patch' do
|
|
96
|
+
skip 'need to isolate environments for testing'
|
|
97
|
+
|
|
98
|
+
patch = local_node[:patched].keys[0]
|
|
99
|
+
subject.nodes.each_value { |node| refute node[:patched][patch] }
|
|
100
|
+
subject.install! patch
|
|
101
|
+
subject.nodes.each_value { |node| assert node[:patched][patch] }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'install multiple patches' do
|
|
105
|
+
skip 'need to isolate environments for testing'
|
|
106
|
+
|
|
107
|
+
patches = local_node[:patched].keys[0..1]
|
|
108
|
+
patches.each do |patch|
|
|
109
|
+
subject.nodes.each_value { |node| refute node[:patched][patch] }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
subject.install! patches
|
|
113
|
+
|
|
114
|
+
patches.each do |patch|
|
|
115
|
+
subject.nodes.each_value { |node| assert node[:patched][patch] }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'only to the local node' do
|
|
120
|
+
skip 'need to test multi-node clusters'
|
|
121
|
+
|
|
122
|
+
local_node[:patched].each_value { |patch| refute patch }
|
|
123
|
+
subject.install!([], nil, true)
|
|
124
|
+
local_node[:patched].each_value { |patch| assert patch }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'only to a specified node' do
|
|
128
|
+
skip 'need to test multi-node clusters'
|
|
129
|
+
|
|
130
|
+
local_node[:patched].each_value { |patch| refute patch }
|
|
131
|
+
subject.install!([], TEST_ENV)
|
|
132
|
+
local_node[:patched].each_value { |patch| assert patch }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'only to multiple specified nodes' do
|
|
136
|
+
skip 'Not implemented yet'
|
|
137
|
+
|
|
138
|
+
nodes = subject.nodes.keys[0..1]
|
|
139
|
+
nodes.each do |node|
|
|
140
|
+
node[:patched].each_value { |patch| refute patch }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
subject.install!([], nodes)
|
|
144
|
+
|
|
145
|
+
nodes.each do |node|
|
|
146
|
+
node[:patched].each_value { |patch| assert patch }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'without restarting the node' do
|
|
151
|
+
skip 'need to isolate environments for testing'
|
|
152
|
+
|
|
153
|
+
subject.nodes.each_value do |node|
|
|
154
|
+
node[:patched].each_value { |patch| refute patch }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
subject.install!([], nil, false, false)
|
|
158
|
+
|
|
159
|
+
subject.nodes.each_value do |node|
|
|
160
|
+
node[:patched].each_value { |patch| assert patch }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'detects a successful patch installation' do
|
|
165
|
+
skip
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'detects a failed patch installation' do
|
|
169
|
+
skip
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe 'reset! marks all patches as uninstalled' do
|
|
174
|
+
it 'on all nodes by default' do
|
|
175
|
+
skip
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'only on the local node' do
|
|
179
|
+
skip
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'only on a specified node' do
|
|
183
|
+
skip
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'only on multiple specified nodes' do
|
|
187
|
+
skip 'Not implemented yet'
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'detects a successful reset' do
|
|
191
|
+
skip
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
describe 'update! refreshes the manifest' do
|
|
196
|
+
it 'on all nodes by default' do
|
|
197
|
+
skip
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'only on the local node' do
|
|
201
|
+
skip
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'only on a specified node' do
|
|
205
|
+
skip
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'only on multiple specified nodes' do
|
|
209
|
+
skip 'Not implemented yet'
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'detects a successful update' do
|
|
213
|
+
skip
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
describe 'mark!' do
|
|
218
|
+
describe 'uninstalled' do
|
|
219
|
+
it 'single patch on all nodes by default' do
|
|
220
|
+
skip
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'multiple patches' do
|
|
224
|
+
skip
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'only on the local node' do
|
|
228
|
+
skip
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'only on a specified node' do
|
|
232
|
+
skip
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'only on multiple specified nodes' do
|
|
236
|
+
skip 'Not implemented yet'
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'installed on all nodes' do
|
|
241
|
+
skip
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'detects a successful mark' do
|
|
245
|
+
skip
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
describe 'reinstall!' do
|
|
250
|
+
it 'single patch to all nodes by default' do
|
|
251
|
+
skip
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'only multiple specified patches' do
|
|
255
|
+
skip
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it 'only to the local node' do
|
|
259
|
+
skip
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it 'only to a specified node' do
|
|
263
|
+
skip
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'only to multiple specified nodes' do
|
|
267
|
+
skip 'Not implemented yet'
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'without restarting the node' do
|
|
271
|
+
skip
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it 'detects a successful reinstallation' do
|
|
275
|
+
skip
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it 'detects a failed reinstallation' do
|
|
279
|
+
skip
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
describe 'revert!' do
|
|
284
|
+
it 'all patches on all nodes by default' do
|
|
285
|
+
skip
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it 'only one patch' do
|
|
289
|
+
skip
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it 'only multiple specified patches' do
|
|
293
|
+
skip
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'only to the local node' do
|
|
297
|
+
skip
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it 'only to a specified node' do
|
|
301
|
+
skip
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'only to multiple specified nodes' do
|
|
305
|
+
skip 'Not implemented yet'
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it 'without restarting the node' do
|
|
309
|
+
skip
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it 'detects a successful revert' do
|
|
313
|
+
skip
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it 'detects a failed revert' do
|
|
317
|
+
skip
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it 'detects a patch this is unable to be reverted' do
|
|
321
|
+
skip 'Not implemented yet'
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stackadmin
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andres Rojas
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-02-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: net-ssh
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '>='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '>='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: json
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '>='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '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'
|
|
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: minitest-reporters
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - '>='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: vagrant
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - '>='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - '>='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: vagrant-s3auth
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - '>='
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - '>='
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rake
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - '>='
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - '>='
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description:
|
|
112
|
+
email:
|
|
113
|
+
- andres.rojas@mtnsat.com
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- .gitignore
|
|
119
|
+
- Gemfile
|
|
120
|
+
- Gemfile.lock
|
|
121
|
+
- README.md
|
|
122
|
+
- Rakefile
|
|
123
|
+
- lib/stackadmin.rb
|
|
124
|
+
- lib/stackadmin/audit.rb
|
|
125
|
+
- lib/stackadmin/base.rb
|
|
126
|
+
- lib/stackadmin/exceptions.rb
|
|
127
|
+
- lib/stackadmin/net-ssh.rb
|
|
128
|
+
- lib/stackadmin/patch.rb
|
|
129
|
+
- lib/stackadmin/version.rb
|
|
130
|
+
- lib/stackadmin/yaml.rb
|
|
131
|
+
- test/Vagrantfile
|
|
132
|
+
- test/stackadmin_test.rb
|
|
133
|
+
homepage: https://github.com/mtnsat/stackadmin
|
|
134
|
+
licenses: []
|
|
135
|
+
metadata: {}
|
|
136
|
+
post_install_message:
|
|
137
|
+
rdoc_options: []
|
|
138
|
+
require_paths:
|
|
139
|
+
- lib
|
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - '>='
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
|
+
requirements:
|
|
147
|
+
- - '>='
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: '0'
|
|
150
|
+
requirements: []
|
|
151
|
+
rubyforge_project:
|
|
152
|
+
rubygems_version: 2.0.14
|
|
153
|
+
signing_key:
|
|
154
|
+
specification_version: 4
|
|
155
|
+
summary: A helper class for administering Stackato deployments
|
|
156
|
+
test_files:
|
|
157
|
+
- test/Vagrantfile
|
|
158
|
+
- test/stackadmin_test.rb
|