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 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: