rconf 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 }