configspec 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44c4f6e749e74381cd17819c7f6f058794c40ee5
4
+ data.tar.gz: bddf65aa2cb75473a800d71e814b4d315db62ac9
5
+ SHA512:
6
+ metadata.gz: 5968fa460ac868ff9a13bde3fa4c70bb3bb1df4167006534e8be1fe904ea1bb3d393a6c3b719785327a593b0cac6acebaddfbc7f98f6b05ad485c1c66d51a137
7
+ data.tar.gz: c211b27045aa4a1070dbc8edd9fa12dc1f4670573ab0ecb1772010fefaf4b45c836aa25f9908382842683c701c016cb6c64b4cb0a8b52f7d2fdb9a4e2703cffb
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in configspec.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Gosuke Miyashita
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Configspec
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'configspec'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install configspec
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :spec => 'spec:redhat'
5
+
6
+ namespace :spec do
7
+ oses = %w( redhat )
8
+
9
+ oses.each do |os|
10
+ RSpec::Core::RakeTask.new(os.to_sym) do |t|
11
+ t.pattern = "spec/#{os}/*_spec.rb"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'configspec'
6
+
7
+ Configspec::Setup.run
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'configspec/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "configspec"
8
+ spec.version = Configspec::VERSION
9
+ spec.authors = ["Gosuke Miyashita"]
10
+ spec.email = ["gosukenator@gmail.com"]
11
+ spec.description = %q{A simple configuration management tool powered by RSpec}
12
+ spec.summary = %q{A simple configuration management tool powered by RSpec}
13
+ spec.homepage = "https://github.com/mizzy/configspec"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "net-ssh"
22
+ spec.add_runtime_dependency "rspec", ">= 2.13.0"
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,31 @@
1
+ require 'singleton'
2
+
3
+ module Configspec
4
+ module Backend
5
+ class Base
6
+ include Singleton
7
+
8
+ def set_commands(c)
9
+ @commands = c
10
+ end
11
+
12
+ def set_example(e)
13
+ @example = e
14
+ end
15
+
16
+ def commands
17
+ @commands
18
+ end
19
+
20
+ def check_zero(cmd, *args)
21
+ ret = run_command(commands.send(cmd, *args))
22
+ ret[:exit_status] == 0
23
+ end
24
+
25
+ # Default action is to call check_zero with args
26
+ def method_missing(meth, *args, &block)
27
+ check_zero(meth, *args)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,206 @@
1
+ require 'singleton'
2
+
3
+ module Configspec
4
+ module Backend
5
+ class Exec < Base
6
+
7
+ def run_command(cmd, opts={})
8
+ cmd = build_command(cmd)
9
+ cmd = add_pre_command(cmd)
10
+ stdout = `#{build_command(cmd)} 2>&1`
11
+ # In ruby 1.9, it is possible to use Open3.capture3, but not in 1.8
12
+ #stdout, stderr, status = Open3.capture3(cmd)
13
+
14
+ if @example
15
+ @example.metadata[:command] = cmd
16
+ @example.metadata[:stdout] = stdout
17
+ end
18
+
19
+ { :stdout => stdout, :stderr => nil,
20
+ :exit_status => $?.exitstatus, :exit_signal => nil }
21
+ end
22
+
23
+ def build_command(cmd)
24
+ path = Configspec.configuration.path || RSpec.configuration.path
25
+ if path
26
+ cmd = "env PATH=#{path}:$PATH #{cmd}"
27
+ cmd.gsub!(/(\&\&\s*!?\(?\s*)/, "\\1env PATH=#{path}:$PATH ")
28
+ cmd.gsub!(/(\|\|\s*!?\(?\s*)/, "\\1env PATH=#{path}:$PATH ")
29
+ end
30
+ cmd
31
+ end
32
+
33
+ def add_pre_command(cmd)
34
+ path = Configspec.configuration.path || RSpec.configuration.path
35
+ if Configspec.configuration.pre_command
36
+ cmd = "#{Configspec.configuration.pre_command} && #{cmd}"
37
+ cmd = "env PATH=#{path}:$PATH #{cmd}" if path
38
+ end
39
+ cmd
40
+ end
41
+
42
+ def check_running(process)
43
+ ret = run_command(commands.check_running(process))
44
+
45
+ # In Ubuntu, some services are under upstart and "service foo status" returns
46
+ # exit status 0 even though they are stopped.
47
+ # So return false if stdout contains "stopped/waiting".
48
+ return false if ret[:stdout] =~ /stopped\/waiting/
49
+
50
+ # If the service is not registered, check by ps command
51
+ if ret[:exit_status] == 1
52
+ ret = run_command(commands.check_process(process))
53
+ end
54
+
55
+ ret[:exit_status] == 0
56
+ end
57
+
58
+ def check_monitored_by_monit(process)
59
+ ret = run_command(commands.check_monitored_by_monit(process))
60
+ return false unless ret[:stdout] != nil && ret[:exit_status] == 0
61
+
62
+ retlines = ret[:stdout].split(/[\r\n]+/).map(&:strip)
63
+ proc_index = retlines.index("Process '#{process}'")
64
+ return false unless proc_index
65
+
66
+ retlines[proc_index+2].match(/\Amonitoring status\s+monitored\Z/i) != nil
67
+ end
68
+
69
+ def check_readable(file, by_whom)
70
+ mode = sprintf('%04s',run_command(commands.get_mode(file))[:stdout].strip)
71
+ mode = mode.split('')
72
+ mode_octal = mode[0].to_i * 512 + mode[1].to_i * 64 + mode[2].to_i * 8 + mode[3].to_i * 1
73
+ case by_whom
74
+ when nil
75
+ mode_octal & 0444 != 0
76
+ when 'owner'
77
+ mode_octal & 0400 != 0
78
+ when 'group'
79
+ mode_octal & 0040 != 0
80
+ when 'others'
81
+ mode_octal & 0004 != 0
82
+ end
83
+ end
84
+
85
+ def check_writable(file, by_whom)
86
+ mode = sprintf('%04s',run_command(commands.get_mode(file))[:stdout].strip)
87
+ mode = mode.split('')
88
+ mode_octal = mode[0].to_i * 512 + mode[1].to_i * 64 + mode[2].to_i * 8 + mode[3].to_i * 1
89
+ case by_whom
90
+ when nil
91
+ mode_octal & 0222 != 0
92
+ when 'owner'
93
+ mode_octal & 0200 != 0
94
+ when 'group'
95
+ mode_octal & 0020 != 0
96
+ when 'others'
97
+ mode_octal & 0002 != 0
98
+ end
99
+ end
100
+
101
+ def check_executable(file, by_whom)
102
+ mode = sprintf('%04s',run_command(commands.get_mode(file))[:stdout].strip)
103
+ mode = mode.split('')
104
+ mode_octal = mode[0].to_i * 512 + mode[1].to_i * 64 + mode[2].to_i * 8 + mode[3].to_i * 1
105
+ case by_whom
106
+ when nil
107
+ mode_octal & 0111 != 0
108
+ when 'owner'
109
+ mode_octal & 0100 != 0
110
+ when 'group'
111
+ mode_octal & 0010 != 0
112
+ when 'others'
113
+ mode_octal & 0001 != 0
114
+ end
115
+ end
116
+
117
+ def check_mounted(path, expected_attr, only_with)
118
+ ret = run_command(commands.check_mounted(path))
119
+ if expected_attr.nil? || ret[:exit_status] != 0
120
+ return ret[:exit_status] == 0
121
+ end
122
+
123
+ mount = ret[:stdout].scan(/\S+/)
124
+ actual_attr = { :device => mount[0], :type => mount[4] }
125
+ mount[5].gsub(/\(|\)/, '').split(',').each do |option|
126
+ name, val = option.split('=')
127
+ if val.nil?
128
+ actual_attr[name.to_sym] = true
129
+ else
130
+ val = val.to_i if val.match(/^\d+$/)
131
+ actual_attr[name.to_sym] = val
132
+ end
133
+ end
134
+
135
+ if ! expected_attr[:options].nil?
136
+ expected_attr.merge!(expected_attr[:options])
137
+ expected_attr.delete(:options)
138
+ end
139
+
140
+ if only_with
141
+ actual_attr == expected_attr
142
+ else
143
+ expected_attr.each do |key, val|
144
+ return false if actual_attr[key] != val
145
+ end
146
+ true
147
+ end
148
+ end
149
+
150
+ def check_routing_table(expected_attr)
151
+ return false if ! expected_attr[:destination]
152
+ ret = run_command(commands.check_routing_table(expected_attr[:destination]))
153
+ return false if ret[:exit_status] != 0
154
+
155
+ ret[:stdout] =~ /^(\S+)(?: via (\S+))? dev (\S+).+\r\n(?:default via (\S+))?/
156
+ actual_attr = {
157
+ :destination => $1,
158
+ :gateway => $2 ? $2 : $4,
159
+ :interface => expected_attr[:interface] ? $3 : nil
160
+ }
161
+
162
+ expected_attr.each do |key, val|
163
+ return false if actual_attr[key] != val
164
+ end
165
+ true
166
+ end
167
+
168
+ def check_os
169
+ return RSpec.configuration.os if RSpec.configuration.os
170
+ if run_command('ls /etc/redhat-release')[:exit_status] == 0
171
+ line = run_command('cat /etc/redhat-release')[:stdout]
172
+ if line =~ /release (\d[\d.]*)/
173
+ release = $1
174
+ end
175
+ { :family => 'RedHat', :release => release }
176
+ elsif run_command('ls /etc/system-release')[:exit_status] == 0
177
+ { :family => 'RedHat', :release => nil } # Amazon Linux
178
+ elsif run_command('ls /etc/debian_version')[:exit_status] == 0
179
+ { :family => 'Debian', :release => nil }
180
+ elsif run_command('ls /etc/gentoo-release')[:exit_status] == 0
181
+ { :family => 'Gentoo', :release => nil }
182
+ elsif run_command('ls /usr/lib/setup/Plamo-*')[:exit_status] == 0
183
+ { :family => 'Plamo', :release => nil }
184
+ elsif run_command('uname -s')[:stdout] =~ /AIX/i
185
+ { :family => 'AIX', :release => nil }
186
+ elsif (os = run_command('uname -sr')[:stdout]) && os =~ /SunOS/i
187
+ if os =~ /5.10/
188
+ { :family => 'Solaris10', :release => nil }
189
+ elsif run_command('grep -q "Oracle Solaris 11" /etc/release')[:exit_status] == 0
190
+ { :family => 'Solaris11', :release => nil }
191
+ elsif run_command('grep -q SmartOS /etc/release')[:exit_status] == 0
192
+ { :family => 'SmartOS', :release => nil }
193
+ else
194
+ { :family => 'Solaris', :release => nil }
195
+ end
196
+ elsif run_command('uname -s')[:stdout] =~ /Darwin/i
197
+ { :family => 'Darwin', :release => nil }
198
+ elsif run_command('uname -s')[:stdout] =~ /FreeBSD/i
199
+ { :family => 'FreeBSD', :release => nil }
200
+ else
201
+ { :family => 'Base', :release => nil }
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,93 @@
1
+ require 'configspec/backend/exec'
2
+
3
+ module Configspec
4
+ module Backend
5
+ class Ssh < Exec
6
+ def run_command(cmd, opt={})
7
+ cmd = build_command(cmd)
8
+ cmd = add_pre_command(cmd)
9
+ ret = ssh_exec!(cmd)
10
+
11
+ ret[:stdout].gsub!(/\r\n/, "\n")
12
+
13
+ if @example
14
+ @example.metadata[:command] = cmd
15
+ @example.metadata[:stdout] = ret[:stdout]
16
+ end
17
+
18
+ ret
19
+ end
20
+
21
+ def build_command(cmd)
22
+ cmd = super(cmd)
23
+ if RSpec.configuration.ssh.options[:user] != 'root'
24
+ cmd = "#{sudo} #{cmd}"
25
+ cmd.gsub!(/(\&\&\s*!?\(?\s*)/, "\\1#{sudo} ")
26
+ cmd.gsub!(/(\|\|\s*!?\(?\s*)/, "\\1#{sudo} ")
27
+ end
28
+ cmd
29
+ end
30
+
31
+ def add_pre_command(cmd)
32
+ cmd = super(cmd)
33
+ user = RSpec.configuration.ssh.options[:user]
34
+ pre_command = Configspec.configuration.pre_command
35
+ if pre_command && user != 'root'
36
+ cmd = "#{sudo} #{cmd}"
37
+ end
38
+ cmd
39
+ end
40
+
41
+ private
42
+ def ssh_exec!(command)
43
+ stdout_data = ''
44
+ stderr_data = ''
45
+ exit_status = nil
46
+ exit_signal = nil
47
+ pass_prompt = RSpec.configuration.pass_prompt || /^\[sudo\] password for/
48
+
49
+ ssh = RSpec.configuration.ssh
50
+ ssh.open_channel do |channel|
51
+ channel.request_pty do |ch, success|
52
+ abort "Could not obtain pty " if !success
53
+ end
54
+ channel.exec("#{command}") do |ch, success|
55
+ abort "FAILED: couldn't execute command (ssh.channel.exec)" if !success
56
+ channel.on_data do |ch, data|
57
+ if data.match pass_prompt
58
+ abort "Please set sudo password by using SUDO_PASSWORD or ASK_SUDO_PASSWORD environment variable" if RSpec.configuration.sudo_password.nil?
59
+ channel.send_data "#{RSpec.configuration.sudo_password}\n"
60
+ else
61
+ stdout_data += data
62
+ end
63
+ end
64
+
65
+ channel.on_extended_data do |ch, type, data|
66
+ stderr_data += data
67
+ end
68
+
69
+ channel.on_request("exit-status") do |ch, data|
70
+ exit_status = data.read_long
71
+ end
72
+
73
+ channel.on_request("exit-signal") do |ch, data|
74
+ exit_signal = data.read_long
75
+ end
76
+ end
77
+ end
78
+ ssh.loop
79
+ { :stdout => stdout_data, :stderr => stderr_data, :exit_status => exit_status, :exit_signal => exit_signal }
80
+ end
81
+
82
+ def sudo
83
+ sudo_path = Configspec.configuration.sudo_path || RSpec.configuration.sudo_path
84
+ if sudo_path
85
+ "#{sudo_path}/sudo"
86
+ else
87
+ 'sudo'
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,3 @@
1
+ require 'configspec/backend/base'
2
+ require 'configspec/backend/ssh'
3
+ require 'configspec/backend/exec'
@@ -0,0 +1,24 @@
1
+ require 'shellwords'
2
+
3
+ module Configspec
4
+ module Commands
5
+ class Base
6
+ class NotImplementedError < Exception; end
7
+
8
+ def escape(target)
9
+ str = case target
10
+ when Regexp
11
+ target.source
12
+ else
13
+ target.to_s
14
+ end
15
+
16
+ Shellwords.shellescape(str)
17
+ end
18
+
19
+ def check_installed(package, version=nil)
20
+ raise NotImplementedError.new
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ require 'shellwords'
2
+
3
+ module Configspec
4
+ module Commands
5
+ class Linux < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Configspec
2
+ module Commands
3
+ class RedHat < Linux
4
+ def install(package)
5
+ cmd = "yum -y install #{package}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Configspec
2
+ module Configuration
3
+ class << self
4
+ VALID_OPTIONS_KEYS = [:path, :pre_command, :stdout, :stderr, :sudo_path, :pass_prompt].freeze
5
+ attr_accessor(*VALID_OPTIONS_KEYS)
6
+
7
+ def defaults
8
+ VALID_OPTIONS_KEYS.inject({}) { |o, k| o.merge!(k => send(k)) }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ module Configspec
2
+ module Helper
3
+ module Configuration
4
+ def subject
5
+ example.metadata[:subject] = described_class
6
+ build_configurations
7
+ super
8
+ end
9
+
10
+ # You can create a set of configurations provided to all specs in your spec_helper:
11
+ #
12
+ # RSpec.configure { |c| c.pre_command = "source ~/.zshrc" }
13
+ #
14
+ # Any configurations you provide with `let(:option_name)` in a spec will
15
+ # automatically be merged on top of the configurations.
16
+ #
17
+ # @example
18
+ #
19
+ # describe 'Gem' do
20
+ # let(:pre_command) { "source ~/.zshrc" }
21
+ #
22
+ # %w(pry awesome_print bundler).each do |p|
23
+ # describe package(p) do
24
+ # it { should be_installed.by('gem') }
25
+ # end
26
+ # end
27
+ # end
28
+ def build_configurations
29
+ Configspec::Configuration.defaults.keys.each do |c|
30
+ value = self.respond_to?(c.to_sym) ?
31
+ self.send(c) : RSpec.configuration.send(c)
32
+ Configspec::Configuration.send(:"#{c}=", value)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ module Configspec
2
+ module Helper
3
+ module DetectOS
4
+ def commands
5
+ property[:os_by_host] = {} if ! property[:os_by_host]
6
+ host = RSpec.configuration.ssh ? RSpec.configuration.ssh.host : 'localhost'
7
+
8
+ if property[:os_by_host][host]
9
+ os = property[:os_by_host][host]
10
+ else
11
+ os = backend(Configspec::Commands::Base).check_os
12
+ property[:os_by_host][host] = os
13
+ end
14
+
15
+ self.class.const_get('Configspec').const_get('Commands').const_get(os[:family]).new
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Configspec
2
+ module Helper
3
+ module Exec
4
+ def backend(commands_object=nil)
5
+ if commands_object.nil? && ! respond_to?(:commands)
6
+ commands_object = Configspec::Commands::Base.new
7
+ end
8
+ instance = Configspec::Backend::Exec.instance
9
+ instance.set_commands(commands_object || commands)
10
+ instance
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'configspec/properties'
2
+
3
+ module Configspec
4
+ module Helper
5
+ module Properties
6
+ def property
7
+ Configspec::Properties.instance.properties
8
+ end
9
+ def set_property(prop)
10
+ Configspec::Properties.instance.properties(prop)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module Configspec
2
+ module Helper
3
+ module RedHat
4
+ def commands
5
+ Configspec::Commands::RedHat.new
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Configspec
2
+ module Helper
3
+ module Ssh
4
+ def backend(commands_object=nil)
5
+ if ! respond_to?(:commands)
6
+ commands_object = Configspec::Commands::Base.new
7
+ end
8
+ instance = Configspec::Backend::Ssh.instance
9
+ instance.set_commands(commands_object || commands)
10
+ instance
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,20 @@
1
+ module Configspec
2
+ module Helper
3
+ module Type
4
+ types = %w( base package )
5
+
6
+ types.each {|type| require "configspec/type/#{type}" }
7
+
8
+ types.each do |type|
9
+ define_method type do |*args|
10
+ name = args.first
11
+ self.class.const_get('Configspec').const_get('Type').const_get(camelize(type)).new(name)
12
+ end
13
+ end
14
+
15
+ def camelize(string)
16
+ string.split("_").each {|s| s.capitalize! }.join("")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'configspec/helper/exec'
2
+ require 'configspec/helper/ssh'
3
+ require 'configspec/helper/detect_os'
4
+ require 'configspec/helper/redhat'
5
+
6
+ require 'configspec/helper/type'
7
+ include Configspec::Helper::Type
8
+
9
+ require 'configspec/helper/properties'
10
+ include Configspec::Helper::Properties
11
+
12
+ require 'configspec/helper/configuration'
13
+
@@ -0,0 +1,17 @@
1
+ require 'singleton'
2
+
3
+ module Configspec
4
+ class Properties
5
+ include Singleton
6
+ def initialize
7
+ @prop = {}
8
+ end
9
+ def properties(prop=nil)
10
+ if ! prop.nil?
11
+ @prop = prop
12
+ end
13
+ @prop
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,294 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+
4
+ module Configspec
5
+ class Setup
6
+ def self.run
7
+
8
+ # ask_os_type
9
+ @os_type = 'UN*X'
10
+
11
+ if @os_type == 'UN*X'
12
+ ask_unix_backend
13
+ else
14
+ ask_windows_backend
15
+ end
16
+
17
+ if @backend_type == 'Ssh'
18
+ print "Vagrant instance y/n: "
19
+ @vagrant = $stdin.gets.chomp
20
+ if @vagrant =~ (/(true|t|yes|y|1)$/i)
21
+ @vagrant = true
22
+ print "Auto-configure Vagrant from Vagrantfile? y/n: "
23
+ auto_config = $stdin.gets.chomp
24
+ if auto_config =~ (/(true|t|yes|y|1)$/i)
25
+ auto_vagrant_configuration
26
+ else
27
+ print("Input vagrant instance name: ")
28
+ @hostname = $stdin.gets.chomp
29
+ end
30
+ else
31
+ @vagrant = false
32
+ print("Input target host name: ")
33
+ @hostname = $stdin.gets.chomp
34
+ end
35
+ else
36
+ @hostname = 'localhost'
37
+ end
38
+
39
+ [ 'spec', "spec/#{@hostname}" ].each { |dir| safe_mkdir(dir) }
40
+ safe_create_spec
41
+ safe_create_spec_helper
42
+ safe_create_rakefile
43
+ end
44
+
45
+ def self.ask_os_type
46
+ prompt = <<-EOF
47
+ Select OS type:
48
+
49
+ 1) UN*X
50
+ 2) Windows
51
+
52
+ Select number:
53
+ EOF
54
+
55
+ print prompt.chop
56
+ num = $stdin.gets.to_i - 1
57
+ puts
58
+
59
+ @os_type = [ 'UN*X', 'Windows' ][num] || 'UN*X'
60
+ end
61
+
62
+ def self.ask_unix_backend
63
+ prompt = <<-EOF
64
+ Select a backend type:
65
+
66
+ 1) SSH
67
+ 2) Exec (local)
68
+
69
+ Select number:
70
+ EOF
71
+ print prompt.chop
72
+ num = $stdin.gets.to_i - 1
73
+ puts
74
+
75
+ @backend_type = [ 'Ssh', 'Exec' ][num] || 'Exec'
76
+ end
77
+
78
+ def self.ask_windows_backend
79
+ prompt = <<-EOF
80
+ Select a backend type:
81
+
82
+ 1) WinRM
83
+ 2) Cmd (local)
84
+
85
+ Select number:
86
+ EOF
87
+ print prompt.chop
88
+ num = $stdin.gets.to_i - 1
89
+ puts
90
+
91
+ @backend_type = [ 'WinRM', 'Cmd' ][num] || 'Exec'
92
+ end
93
+
94
+ def self.safe_create_spec
95
+ content = <<-EOF
96
+ require 'spec_helper'
97
+
98
+ describe package('httpd') do
99
+ it { should be_installed }
100
+ end
101
+
102
+ describe service('httpd') do
103
+ it { should be_enabled }
104
+ it { should be_running }
105
+ end
106
+
107
+ describe port(80) do
108
+ it { should be_listening }
109
+ end
110
+
111
+ describe file('/etc/httpd/conf/httpd.conf') do
112
+ it { should be_file }
113
+ it { should contain "ServerName #{@hostname}" }
114
+ end
115
+ EOF
116
+
117
+ if File.exists? "spec/#{@hostname}/001_httpd_spec.rb"
118
+ old_content = File.read("spec/#{@hostname}/001_httpd_spec.rb")
119
+ if old_content != content
120
+ $stderr.puts "!! spec/#{@hostname}/httpd_spec.rb already exists and differs from template"
121
+ end
122
+ else
123
+ File.open("spec/#{@hostname}/001_httpd_spec.rb", 'w') do |f|
124
+ f.puts content
125
+ end
126
+ puts " + spec/#{@hostname}/001_httpd_spec.rb"
127
+ end
128
+ end
129
+
130
+ def self.safe_mkdir(dir)
131
+ if File.exists? dir
132
+ unless File.directory? dir
133
+ $stderr.puts "!! #{dir} already exists and is not a directory"
134
+ end
135
+ else
136
+ FileUtils.mkdir dir
137
+ puts " + #{dir}/"
138
+ end
139
+ end
140
+
141
+ def self.safe_create_spec_helper
142
+ requirements = []
143
+ content = ERB.new(spec_helper_template, nil, '-').result(binding)
144
+ if File.exists? 'spec/spec_helper.rb'
145
+ old_content = File.read('spec/spec_helper.rb')
146
+ if old_content != content
147
+ $stderr.puts "!! spec/spec_helper.rb already exists and differs from template"
148
+ end
149
+ else
150
+ File.open('spec/spec_helper.rb', 'w') do |f|
151
+ f.puts content
152
+ end
153
+ puts ' + spec/spec_helper.rb'
154
+ end
155
+ end
156
+
157
+ def self.safe_create_rakefile
158
+ content = <<-'EOF'
159
+ require 'rake'
160
+ require 'rspec/core/rake_task'
161
+
162
+ RSpec::Core::RakeTask.new(:spec) do |t|
163
+ t.pattern = 'spec/*/*_spec.rb'
164
+ end
165
+ EOF
166
+ if File.exists? 'Rakefile'
167
+ old_content = File.read('Rakefile')
168
+ if old_content != content
169
+ $stderr.puts "!! Rakefile already exists and differs from template"
170
+ end
171
+ else
172
+ File.open('Rakefile', 'w') do |f|
173
+ f.puts content
174
+ end
175
+ puts ' + Rakefile'
176
+ end
177
+ end
178
+
179
+ def self.find_vagrantfile
180
+ Pathname.new(Dir.pwd).ascend do |dir|
181
+ path = File.expand_path("Vagrantfile", dir)
182
+ return path if File.exists?(path)
183
+ end
184
+ nil
185
+ end
186
+
187
+ def self.auto_vagrant_configuration
188
+ if find_vagrantfile
189
+ vagrant_list = `vagrant status`
190
+ list_of_vms = []
191
+ if vagrant_list != ''
192
+ vagrant_list.each_line do |line|
193
+ if match = /([a-z_-]+[\s]+)(created|not created|poweroff|running|saved)[\s](\(virtualbox\)|\(vmware\))/.match(line)
194
+ list_of_vms << match[1].strip!
195
+ end
196
+ end
197
+ if list_of_vms.length == 1
198
+ @hostname = list_of_vms[0]
199
+ else
200
+ list_of_vms.each_with_index { |vm, index | puts "#{index}) #{vm}\n" }
201
+ print "Choose a VM from the Vagrantfile: "
202
+ chosen_vm = $stdin.gets.chomp
203
+ @hostname = list_of_vms[chosen_vm.to_i]
204
+ end
205
+ else
206
+ $stderr.puts "Vagrant status error - Check your Vagrantfile or .vagrant"
207
+ exit 1
208
+ end
209
+ else
210
+ $stderr.puts "Vagrantfile not found in directory!"
211
+ exit 1
212
+ end
213
+ end
214
+
215
+ def self.spec_helper_template
216
+ template = <<-EOF
217
+ require 'configspec'
218
+ <% if @os_type == 'UN*X' -%>
219
+ require 'pathname'
220
+ <% end -%>
221
+ <% if @backend_type == 'Ssh' -%>
222
+ require 'net/ssh'
223
+ <% end -%>
224
+ <% if @backend_type == 'WinRM' -%>
225
+ require 'winrm'
226
+ <% end -%>
227
+
228
+ include Configspec::Helper::<%= @backend_type %>
229
+ <% if @os_type == 'UN*X' -%>
230
+ include Configspec::Helper::DetectOS
231
+ <% else -%>
232
+ include Configspec::Helper::Windows
233
+ <% end -%>
234
+
235
+ <% if @os_type == 'UN*X' -%>
236
+ RSpec.configure do |c|
237
+ if ENV['ASK_SUDO_PASSWORD']
238
+ require 'highline/import'
239
+ c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false }
240
+ else
241
+ c.sudo_password = ENV['SUDO_PASSWORD']
242
+ end
243
+ <%- if @backend_type == 'Ssh' -%>
244
+ c.before :all do
245
+ block = self.class.metadata[:example_group_block]
246
+ if RUBY_VERSION.start_with?('1.8')
247
+ file = block.to_s.match(/.*@(.*):[0-9]+>/)[1]
248
+ else
249
+ file = block.source_location.first
250
+ end
251
+ host = File.basename(Pathname.new(file).dirname)
252
+ if c.host != host
253
+ c.ssh.close if c.ssh
254
+ c.host = host
255
+ options = Net::SSH::Config.for(c.host)
256
+ user = options[:user] || Etc.getlogin
257
+ <%- if @vagrant -%>
258
+ vagrant_up = `vagrant up #{@hostname}`
259
+ config = `vagrant ssh-config #{@hostname}`
260
+ if config != ''
261
+ config.each_line do |line|
262
+ if match = /HostName (.*)/.match(line)
263
+ host = match[1]
264
+ elsif match = /User (.*)/.match(line)
265
+ user = match[1]
266
+ elsif match = /IdentityFile (.*)/.match(line)
267
+ options[:keys] = [match[1].gsub(/\"/,'')]
268
+ elsif match = /Port (.*)/.match(line)
269
+ options[:port] = match[1]
270
+ end
271
+ end
272
+ end
273
+ <%- end -%>
274
+ c.ssh = Net::SSH.start(host, user, options)
275
+ end
276
+ end
277
+ <%- end -%>
278
+ end
279
+ <% end -%>
280
+ <% if @backend_type == 'WinRM'-%>
281
+ RSpec.configure do |c|
282
+ user = <username>
283
+ pass = <password>
284
+ endpoint = "http://<hostname>:5985/wsman"
285
+
286
+ c.winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :basic_auth_only => true)
287
+ c.winrm.set_timeout 300 # 5 minutes max timeout for any operation
288
+ end
289
+ <% end -%>
290
+ EOF
291
+ template
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,18 @@
1
+ module Configspec
2
+ module Type
3
+ class Base
4
+ def initialize(name=nil)
5
+ @name = name
6
+ end
7
+
8
+ def to_s
9
+ type = self.class.name.split(':')[-1]
10
+ type.gsub!(/([a-z\d])([A-Z])/, '\1 \2')
11
+ type.capitalize!
12
+ %Q!#{type} "#{@name}"!
13
+ end
14
+
15
+ alias_method :inspect, :to_s
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Configspec
2
+ module Type
3
+ class Package < Base
4
+ def installed?
5
+ backend.install(@name)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Configspec
2
+ VERSION = "0.0.1"
3
+ end
data/lib/configspec.rb ADDED
@@ -0,0 +1,35 @@
1
+ require "configspec/version"
2
+ require "configspec/setup"
3
+ require "configspec/helper"
4
+ require "configspec/backend"
5
+
6
+ require "configspec/commands/base"
7
+ require "configspec/commands/linux"
8
+ require "configspec/commands/redhat"
9
+
10
+ require "configspec/configuration"
11
+
12
+ include Configspec
13
+
14
+ module Configspec
15
+ class << self
16
+ def configuration
17
+ Configspec::Configuration
18
+ end
19
+ end
20
+ end
21
+
22
+ RSpec.configure do |c|
23
+ c.include(Configspec::Helper::Configuration)
24
+
25
+ c.add_setting :ssh, :default => nil
26
+ c.add_setting :host, :default => nil
27
+ c.add_setting :os, :default => nil
28
+ c.add_setting :sudo_password, :default => nil
29
+
30
+ Configspec.configuration.defaults.each { |k, v| c.add_setting k, :default => v }
31
+
32
+ c.before :each do
33
+ backend.set_example(example)
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ include Configspec::Helper::RedHat
4
+
5
+ describe package('httpd') do
6
+ it { should be_installed }
7
+ its(:command) { should eq "yum -y install httpd" }
8
+ end
@@ -0,0 +1,59 @@
1
+ require 'configspec'
2
+ require 'pathname'
3
+ require 'rspec/mocks/standalone'
4
+
5
+ include Configspec::Helper::Exec
6
+
7
+ PROJECT_ROOT = (Pathname.new(File.dirname(__FILE__)) + '..').expand_path
8
+
9
+ Dir[PROJECT_ROOT.join("spec/support/**/*.rb")].each { |file| require(file) }
10
+
11
+
12
+ module Configspec
13
+ module Backend
14
+ module TestCommandRunner
15
+ def do_run cmd
16
+ if @example
17
+ @example.metadata[:subject].set_command(cmd)
18
+ end
19
+
20
+ if cmd =~ /invalid/
21
+ {
22
+ :stdout => ::Configspec.configuration.stdout,
23
+ :stderr => ::Configspec.configuration.stderr,
24
+ :exit_status => 1,
25
+ :exit_signal => nil
26
+ }
27
+ else
28
+ {
29
+ :stdout => ::Configspec.configuration.stdout,
30
+ :stderr => ::Configspec.configuration.stderr,
31
+ :exit_status => 0,
32
+ :exit_signal => nil
33
+ }
34
+ end
35
+ end
36
+ end
37
+ [Exec, Ssh].each do |clz|
38
+ clz.class_eval do
39
+ include TestCommandRunner
40
+ def run_command(cmd)
41
+ cmd = build_command(cmd.to_s)
42
+ cmd = add_pre_command(cmd)
43
+ do_run cmd
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ module Type
50
+ class Base
51
+ def set_command(command)
52
+ @command = command
53
+ end
54
+ def command
55
+ @command
56
+ end
57
+ end
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: configspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gosuke Miyashita
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-25 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: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.13.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.13.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
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
+ description: A simple configuration management tool powered by RSpec
70
+ email:
71
+ - gosukenator@gmail.com
72
+ executables:
73
+ - configspec-init
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/configspec-init
83
+ - configspec.gemspec
84
+ - lib/configspec.rb
85
+ - lib/configspec/backend.rb
86
+ - lib/configspec/backend/base.rb
87
+ - lib/configspec/backend/exec.rb
88
+ - lib/configspec/backend/ssh.rb
89
+ - lib/configspec/commands/base.rb
90
+ - lib/configspec/commands/linux.rb
91
+ - lib/configspec/commands/redhat.rb
92
+ - lib/configspec/configuration.rb
93
+ - lib/configspec/helper.rb
94
+ - lib/configspec/helper/configuration.rb
95
+ - lib/configspec/helper/detect_os.rb
96
+ - lib/configspec/helper/exec.rb
97
+ - lib/configspec/helper/properties.rb
98
+ - lib/configspec/helper/redhat.rb
99
+ - lib/configspec/helper/ssh.rb
100
+ - lib/configspec/helper/type.rb
101
+ - lib/configspec/properties.rb
102
+ - lib/configspec/setup.rb
103
+ - lib/configspec/type/base.rb
104
+ - lib/configspec/type/package.rb
105
+ - lib/configspec/version.rb
106
+ - spec/redhat/package_spec.rb
107
+ - spec/spec_helper.rb
108
+ homepage: https://github.com/mizzy/configspec
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.0.3
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: A simple configuration management tool powered by RSpec
132
+ test_files:
133
+ - spec/redhat/package_spec.rb
134
+ - spec/spec_helper.rb