stackadmin 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9356c67e44d2b41f466700e39b383e6ddf8599d9
4
- data.tar.gz: 90080f91deaa13008ee34454051567d44c1ee291
3
+ metadata.gz: 1e2ddf654357669ce50978cf4616b55f25244cbf
4
+ data.tar.gz: 483e8a3ba68b7995367b40af91791b1aca24e5e0
5
5
  SHA512:
6
- metadata.gz: 7cdf3b9b5f4f8157f00c5d3826ea8643fdae871c4105004e0ae11ec8910b14c1b4a448f1b1c17fdeb61a5c60b85c70837f27d082b6dca0416d7d85c836abb058
7
- data.tar.gz: 4925bc36f4ab907e38ee5fc41b4aa2d553fe1b615685d7d26040ffa2e77e2b352bde2bd1604b7f887abf738e5d07e7b28473103e78d866aecfb3c27ec9143ba2
6
+ metadata.gz: bb485c2bb8ea145d9247945efc5d2a7f8d520e7f9b83c3e810a3508e1fc86a24c10541bcf16cb919c11544a68a216df02ce4fd39b3fc9d01819e6d501726335b
7
+ data.tar.gz: 33fa85e6175d55f656aacfd9d4eb685bba74eb798663972fef9684f337831f8591c6d211b87afcd16eec659786ac83621eaf57d759d7c690380cc71894ae1480
data/.gitignore CHANGED
@@ -1 +1,6 @@
1
1
  test/.vagrant
2
+ *.gem
3
+ doc
4
+ .yardoc
5
+ .DS_Store
6
+ bin
data/CHANGELOG ADDED
@@ -0,0 +1,14 @@
1
+ 0.1.1 (2011-03-11)
2
+ ==================
3
+
4
+ New
5
+ ---
6
+ - 100% YARD doc coverage
7
+
8
+ Changes
9
+ -------
10
+ - Added basic README
11
+
12
+ Fix
13
+ ---
14
+ - Switched from vagrant to vagrant-wrapper gem
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 'vagrant', git: 'https://github.com/mitchellh/vagrant.git'
10
- end
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
- 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)
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
- 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)
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
- vagrant!
119
- vagrant-s3auth
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('bundle exec vagrant up') }
24
+ Dir.chdir('test') { system("#{VAGRANT} up") }
10
25
  end
11
26
 
12
27
  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')
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') { system("vagrant ssh --command 'echo \"#{pub_key}\" | sudo tee -a /home/stackato/.ssh/authorized_keys' > /dev/null") }
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('bundle exec vagrant destroy --force') }
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 setup run destroy_env ).each do |j|
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
- puts "\e[35mRunning #{job}...\e[0m"
34
- Rake::Task[job].invoke
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
@@ -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
- # TODO: It never seems to hit "Marked #{patch} as patched for #{k}"
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
 
@@ -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
@@ -2,15 +2,32 @@
2
2
 
3
3
  require 'net/ssh'
4
4
 
5
- # Grab exit codes from remote commands
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
@@ -6,8 +6,27 @@ require_relative 'exceptions'
6
6
  require_relative 'audit'
7
7
 
8
8
  class Stackadmin
9
- # Returns hash of hashes: { node1: { installed: [patch1], not_installed: [patch2] }, etc }
10
- # TODO: DRY this with ::mark! & ::reinstall! & ::revert!
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
- # TODO: DRY this with ::install! & ::reinstall! & ::revert!
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
- # Returns hash of hashes: { node1: { installed: [patch1], not_installed: [patch2] }, etc }
83
- # TODO: DRY this with ::install! & ::mark! & ::revert!
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
- # Returns hash of hashes: { node1: { reverted: [patch1], not_reverted: [patch2], unable_to_revert: [patch3] }, etc }
123
- # TODO: DRY this with ::install! & ::mark!
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
- # 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
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
 
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class Stackadmin
4
- VERSION = '0.1.0'
4
+ # Gem version
5
+ VERSION = '0.1.1'
5
6
  end
@@ -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
 
@@ -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.0
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-02-23 00:00:00.000000000 Z
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: vagrant-s3auth
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: rake
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: