rconf 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ = right_env
2
+
3
+ == INTRODUCTION
4
+
5
+ As RightScale systems evolve, developers are faced with having to maintain a
6
+ quickly growing list of applications. Each application requires different tools
7
+ for development and for deployment. Tools may include different versions of
8
+ ruby, rubygems, bundler etc. Being able to setup an environment appropriately
9
+ for each application is becoming increasingly complex and switching from one
10
+ application to another for development in particular is quickly becoming
11
+ close to impossible.
12
+
13
+ RightEnv aims at addressing some of these difficulties by providing a uniform
14
+ and consistent mechanism for applications developed at RightScale to
15
+ declaratively specify the tools they depend on in various systems (development,
16
+ test, staging, production) and platforms (linux, darwin and windows). RightEnv
17
+ uses a DSL close to Chef recipes for defining an application configuration.
18
+ Each application must be equipped with a definition that must reside at the top
19
+ level directory of the application and must be named "<application>.re" (where
20
+ "<application>" should be subsituted with the name of the application.
21
+ Configuring the system to run the application then merely consists of running
22
+ the "script/bootstrap_environment" script provided in the "script" directory
23
+ ("script/bootstrap_environment.bat" on windows).
24
+
25
+ Internally RightEnv relies on 'configurators' to configure the machine
26
+ appropriately. There is one configurator per tool that needs configuration.
27
+ Each configurator is dynamically instantiated by RightEnv as it reads the
28
+ application configuration file. This makes for an extensible system where new
29
+ configurator may be added to configure new tools that new applications may
30
+ rely on. For a list of available configurators run "script/configurator --list".
31
+
32
+ == REQUIREMENTS
33
+
34
+ === Running
35
+
36
+ - Linux and MAc OS X
37
+
38
+ - ruby >= 1.8.6
39
+ - curl
40
+ - tar
41
+
42
+ - Windows
43
+
44
+ - ruby >= 1.8.6
45
+ - Win32API gem
46
+
47
+ === Unit testing
48
+
49
+ Install the following gems for testing:
50
+ - rspec >= 2.0
51
+ - flexmock
52
+
53
+ Then the build can be tested with
54
+
55
+ rake spec
56
+
@@ -0,0 +1,63 @@
1
+ # Copyright (C) 2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and
10
+ # the licensee
11
+
12
+ require 'rubygems'
13
+
14
+ require 'fileutils'
15
+ require 'rake'
16
+ require 'rspec/core/rake_task'
17
+ require 'rake/rdoctask'
18
+ require 'rake/gempackagetask'
19
+ require 'rake/clean'
20
+
21
+ task :default => 'spec'
22
+
23
+ # == Unit Tests == #
24
+
25
+ desc "Run unit tests"
26
+ RSpec::Core::RakeTask.new do |t|
27
+ t.rspec_opts = ["--color"]
28
+ end
29
+
30
+ namespace :spec do
31
+ desc "Run unit tests with RCov"
32
+ RSpec::Core::RakeTask.new(:rcov) do |t|
33
+ t.rcov = true
34
+ t.rcov_opts = %q[--exclude "spec"]
35
+ end
36
+
37
+ desc "Print Specdoc for unit tests"
38
+ RSpec::Core::RakeTask.new(:doc) do |t|
39
+ t.rspec_opts = ["--format", "documentation"]
40
+ end
41
+ end
42
+
43
+ # == Gem == #
44
+
45
+ gemtask = Rake::GemPackageTask.new(Gem::Specification.load('rconf.gemspec')) do |package|
46
+ package.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
47
+ package.need_zip = true
48
+ package.need_tar = true
49
+ end
50
+
51
+ directory gemtask.package_dir
52
+
53
+ CLEAN.include(gemtask.package_dir)
54
+
55
+ # == Documentation == #
56
+
57
+ desc "Generate API documentation to doc/rdocs/index.html"
58
+ Rake::RDocTask.new do |rd|
59
+ rd.rdoc_dir = 'doc/rdocs'
60
+ rd.main = 'README.rdoc'
61
+ rd.rdoc_files.include 'README.rdoc', "lib/**/*.rb"
62
+ end
63
+
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (C) 2011 RightScale, Inc, All Rights Reserved Worldwide.
3
+ #
4
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
5
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
6
+ # reproduction, modification, or disclosure of this program is
7
+ # strictly prohibited. Any use of this program by an authorized
8
+ # licensee is strictly subject to the terms and conditions,
9
+ # including confidentiality obligations, set forth in the applicable
10
+ # License Agreement between RightScale.com, Inc. and
11
+ # the licensee
12
+
13
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'rconf')
14
+
15
+ module RightConf
16
+
17
+ class Configurer
18
+
19
+ include ProgressReporter
20
+
21
+ def self.run
22
+ opts = Trollop::options do
23
+ version 'rconf 0.1 (c) 2011 RightScale'
24
+ banner <<-EOS
25
+ #{DESCRIPTION}
26
+
27
+ Usage:
28
+ rconf [options]
29
+
30
+ where [options] are:
31
+ EOS
32
+
33
+ opt :configurators, 'Show available configurators'
34
+ opt :config, 'Set path to configuration file', :type => :string
35
+ opt :output, 'Output file (output to STDOUT by default)', :type => :string
36
+ end
37
+ if opts[:config].nil?
38
+ opts[:config] = Dir['./*.rc']
39
+ if opts[:config].empty?
40
+ Trollop::die :config, "not used and could not find a '.rc' file in the working directory"
41
+ else
42
+ opts[:config] = opts[:config].first
43
+ end
44
+ end
45
+ if opts[:output]
46
+ begin
47
+ FileUtils.mkdir_p(File.dirname(opts[:output]))
48
+ rescue Exception => e
49
+ Trollop::die :output, "Failed to initialize output file: #{e.message}"
50
+ end
51
+ end
52
+ new.configure(opts)
53
+ end
54
+
55
+ # Actually configure environment
56
+ #
57
+ # === Parameters
58
+ # options[:config](String):: Configuration file
59
+ # options[:output](String):: Output file, optional
60
+ #
61
+ # === Return
62
+ # true:: Always return true
63
+ def configure(options)
64
+ if options[:configurators]
65
+ puts "The following configurators are registered:\n\n"
66
+ ConfiguratorRegistry.each do |key, configurator|
67
+ puts "== #{key} ==".bold
68
+ puts configurator.desc
69
+ puts 'Settings:'
70
+ max_size = configurator.all_settings.keys.map(&:to_s).map(&:size).max
71
+ configurator.all_settings.each do |name, desc|
72
+ num_spaces = max_size - name.to_s.size + 1
73
+ print " - #{name.to_s.blue}:#{' ' * num_spaces}#{desc}"
74
+ if configurator.required_settings.include?(name)
75
+ puts ' [required]'.green
76
+ else
77
+ puts
78
+ end
79
+ end
80
+ puts
81
+ end
82
+ exit 0
83
+ end
84
+ ProgressReporter.report_to_stdout
85
+ ProgressReporter.report_to_file(options[:output]) if options[:output]
86
+ begin
87
+ lang = Language.load(options[:config])
88
+ report_fatal("Validation of configuration file failed:\n -#{lang.validation_errors.join("\n -").map(&:red)}") unless lang.validation_errors.empty?
89
+ Dir.chdir(File.dirname(options[:config])) { lang.configurators.each(&:run) }
90
+ report("Successfully configured #{File.basename(options[:config], '.rc').blue} for #{Platform.family.to_s.blue}")
91
+ lang.configurators.each { |c| report("NOTE: #{c.post_note}") if c.post_note }
92
+ rescue Exception => e
93
+ raise if e.is_a?(SystemExit)
94
+ report_fatal("Execution failed with exception '#{e.message.red}'\n#{e.backtrace.join("\n").map(&:grey)}")
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ # Yeeeehaaaa!
102
+ RightConf::Configurer.run
@@ -0,0 +1,9 @@
1
+ # Configuration settings for my project
2
+ ruby do
3
+ ruby_version 'ree-1.8.7-2010.02'
4
+ rubygems_version '1.3.7'
5
+ gemset 'my_project'
6
+ end
7
+ bundler do
8
+ bundler_version '1.0.7'
9
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright (C) 2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and
10
+ # the licensee
11
+
12
+ BASE_DIR = File.join(File.dirname(__FILE__), 'rconf')
13
+
14
+ require 'FileUtils'
15
+ require File.join(BASE_DIR, 'ruby_extensions')
16
+ require File.join(BASE_DIR, 'progress_reporter')
17
+ require File.join(BASE_DIR, 'command')
18
+ require File.join(BASE_DIR, 'configurator_registry')
19
+ require File.join(BASE_DIR, 'configurator')
20
+ require File.join(BASE_DIR, 'language')
21
+ require File.join(BASE_DIR, 'platform')
22
+ require File.join(BASE_DIR, 'trollop')
23
+ require File.join(BASE_DIR, 'version')
24
+
@@ -0,0 +1,112 @@
1
+ # Copyright (C) 2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and
10
+ # the licensee
11
+
12
+ require 'shellwords'
13
+
14
+ module RightConf
15
+
16
+ class Command
17
+
18
+ include Singleton
19
+ include ProgressReporter
20
+
21
+ # Execute given command with given arguments
22
+ #
23
+ # === Parameters
24
+ # command(String):: Command to run
25
+ # args(Array):: Command arguments
26
+ #
27
+ # === Return
28
+ # result(CommandResult):: Result of execution (output and exit status)
29
+ def execute(command, *args)
30
+ opts = {}
31
+ if !args.empty? && args[-1].is_a?(Hash)
32
+ opts = args[-1]
33
+ args = args[0..-2]
34
+ end
35
+ res = Platform.dispatch(command, *args) { :execute }
36
+ if !res.success? && msg = opts[:abort_on_failure]
37
+ report_fatal("#{msg}: '#{command} #{args.join(' ')}' returned\n#{res.output}")
38
+ end
39
+ res
40
+ end
41
+
42
+ # Execute given command on *nix systems
43
+ #
44
+ # === Parameters
45
+ # command(String):: Command name
46
+ # params(Array):: List of parameters to pass to command
47
+ #
48
+ # === Return
49
+ # result(CommandResult):: Result of execution (output and exit status)
50
+ def execute_linux(command, *args)
51
+ out = `#{@prefix} #{Shellwords.join([command, *args])} 2>&1`
52
+ result = CommandResult.new(out, $?.exitstatus)
53
+ end
54
+ alias :execute_darwin :execute_linux
55
+
56
+ # Execute given command on Windows systems
57
+ #
58
+ # === Parameters
59
+ # command(String):: Command name
60
+ # params(Array):: List of parameters to pass to command
61
+ #
62
+ # === Return
63
+ # result(CommandResult):: Result of execution (output and exit status)
64
+ def execute_windows(command, *args)
65
+ raise 'TBD!'
66
+ end
67
+
68
+ # Set prefix to be used for all commands
69
+ #
70
+ # === Parameters
71
+ # prefix(String):: Commands prefix
72
+ #
73
+ # === Return
74
+ # true:: Always return true
75
+ def set_prefix(prefix)
76
+ @prefix = prefix
77
+ true
78
+ end
79
+
80
+ end
81
+
82
+ # Command results
83
+ class CommandResult
84
+
85
+ # Process output
86
+ attr_reader :output
87
+
88
+ # Process exit status
89
+ attr_reader :status
90
+
91
+ # Initialize output and exit status
92
+ #
93
+ # === Parameters
94
+ # output(String):: Process output
95
+ # status(Fixnum):: Process exit status
96
+ def initialize(output, status)
97
+ @output = output
98
+ @status = status
99
+ end
100
+
101
+ # Whether process exited successfully
102
+ #
103
+ # === Return
104
+ # true:: If process exited with status code 0
105
+ # false:: Otherwise
106
+ def success?
107
+ @status == 0
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,179 @@
1
+ # Copyright (C) 2011 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and
10
+ # the licensee
11
+
12
+ module RightConf
13
+
14
+ # Configurator mixin, defines DSL and common validation method
15
+ module Configurator
16
+
17
+ include ProgressReporter
18
+
19
+ module ClassMethods
20
+
21
+ # Key associated with configurator
22
+ attr_reader :key
23
+
24
+ # Description
25
+ attr_reader :desc
26
+
27
+ # Access to settings for documentation
28
+ attr_reader :all_settings
29
+
30
+ # Access to required settings for validation
31
+ attr_reader :required_settings
32
+
33
+ # Associate configurator with given key
34
+ #
35
+ # === Parameters
36
+ # key(Symbol):: Key configurator should be associated with
37
+ #
38
+ # === Return
39
+ # true:: Always return true
40
+ def register(key)
41
+ ConfiguratorRegistry[key] = self
42
+ @key = key
43
+ true
44
+ end
45
+
46
+ # Store description for documentation
47
+ #
48
+ # === Parameters
49
+ # description(String):: Description
50
+ #
51
+ # === Return
52
+ # true:: Always return true
53
+ def description(description)
54
+ @desc = description
55
+ true
56
+ end
57
+
58
+ # Store settings and their descriptions in a hash
59
+ #
60
+ # === Parameters
61
+ # settings(Hash):: Settings descriptions indexed by names
62
+ #
63
+ # === Return
64
+ # true:: Always return true
65
+ def settings(settings)
66
+ @all_settings = settings
67
+ true
68
+ end
69
+
70
+ # Store required settings for validation
71
+ #
72
+ # === Parameters
73
+ # settings(Array):: List of settings that should be checked
74
+ #
75
+ # === Return
76
+ # true:: Always return true
77
+ def validate_has_settings(*settings)
78
+ @required_settings = settings.flatten
79
+ true
80
+ end
81
+
82
+ end
83
+
84
+ # Extend base class with ClassMethods module methods
85
+ #
86
+ # === Parameters
87
+ # base(Object):: Object including module
88
+ def self.included(base)
89
+ base.__send__(:extend, ClassMethods)
90
+ end
91
+
92
+ # Check whether configurator has values for all required settings
93
+ #
94
+ # === Return
95
+ # nil:: If settings are valid for this configurator
96
+ # error(String):: Error message otherwise
97
+ def validate
98
+ init
99
+ required = self.class.required_settings
100
+ return nil unless required
101
+ missing = required.flatten.select { |s| !@settings_values.include?(s) }
102
+ error = case missing.size
103
+ when 0 then nil
104
+ when 1 then "Required setting #{missing.first} is "
105
+ else
106
+ "Required settings #{missing.join(', ')} are "
107
+ end
108
+ error += "missing for configuration section '#{self.class.key}'" if error
109
+ error
110
+ end
111
+
112
+ # Run configurator for current platform
113
+ #
114
+ # === Parameters
115
+ # args:: Pass-through arguments, given to platform specific implementation
116
+ #
117
+ # === Return
118
+ # true:: Always return true
119
+ def run(*args)
120
+ init
121
+ Platform.dispatch(*args) { :run }
122
+ true
123
+ end
124
+
125
+ # Get value of configuration option
126
+ #
127
+ # === Parameters
128
+ # config_option(Symbol):: Configuration option to return
129
+ #
130
+ # === Returns
131
+ # value:: Value of configuration option if there is one
132
+ # nil:: Otherwise
133
+ def [](config_option)
134
+ init
135
+ @settings_values[config_option]
136
+ end
137
+
138
+ protected
139
+
140
+ # DSL implementation, set settings value if arguments, get it otherwise.
141
+ #
142
+ # === Parameters
143
+ # meth(Symbol):: Method symbol
144
+ # args(Array):: List of arguments
145
+ #
146
+ # === Return
147
+ # res(Object):: Configuration setting value or setter return value if
148
+ # arguments
149
+ def method_missing(meth, *args)
150
+ init
151
+ num_args = args.length
152
+ res = nil
153
+ if num_args > 0
154
+ meth = $1.to_sym unless (meth.to_s =~ /(.+)=$/).nil?
155
+ value = num_args == 1 ? args[0] : args
156
+ method_name = meth.id2name
157
+ if self.public_methods.include?("#{method_name}=")
158
+ res = self.send("#{method_name}=", value)
159
+ else
160
+ res = @settings_values[meth] = value
161
+ end
162
+ end
163
+ res || @settings_values[meth]
164
+ end
165
+
166
+ # Initialize configuration settings hash
167
+ #
168
+ # === Return
169
+ # true:: Always return true
170
+ def init
171
+ @settings_values ||= Hash.new
172
+ true
173
+ end
174
+
175
+ end
176
+ end
177
+
178
+ # Load all configurators
179
+ Dir[File.join(File.dirname(__FILE__), 'configurators', '*.rb')].each { |c| require c }