vuderacha-syrup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ # Handler class for actually running an application
2
+ module Syrup
3
+ class Runner
4
+ # Initializes the runner with the given configuration store
5
+ def initialize(config_store, logger)
6
+ @config_store = config_store
7
+ @logger = logger
8
+ end
9
+
10
+ # Runs the application
11
+ def run(app_name)
12
+ @logger.debug "Preparing to execute #{app_name}"
13
+ app = @config_store.applications[app_name]
14
+
15
+ # Gather the properties
16
+ props = {}
17
+ # {}.merge! won't work since the config_store properties aren't a real hash...
18
+ @config_store.properties.each do |k,v| props[k] = v end
19
+ app.properties.each do |k,v| props[k] = v end
20
+
21
+ # Find the Fabric by going from app-specific to env-specific to default
22
+ fabric = app.fabric
23
+ fabric = @config_store.fabric if fabric.nil?
24
+ fabric = default_fabric if fabric.nil?
25
+
26
+ # Calculate the application executable
27
+ @app_path = app.app
28
+
29
+ # Activate all of the properties
30
+ props.each do |k,v|
31
+ @logger.debug "Applying constant #{k}=#{v}" if @verbose
32
+ Kernel.const_set k, v
33
+ end
34
+
35
+ @@active_runner = self # Record the active instance of this runner
36
+ old_argv = ARGV.clone
37
+ begin
38
+ # Overwrite ARGV
39
+ ARGV.replace(app.start_parameters)
40
+
41
+ # Execute the fabric, which should internally perform a Syrup::Runner.run_application when it has prepared
42
+ # adequately for the application to execute
43
+ load fabric
44
+ ensure
45
+ ARGV.replace(old_argv)
46
+ @@active_runner = nil
47
+ end
48
+ end
49
+
50
+ def run_application
51
+ raise "Cannot execute application outside of Fabric" if @app_path.nil?
52
+
53
+ load @app_path
54
+ end
55
+
56
+ def self.run_application
57
+ raise "Cannot execute application outside of Fabric" if @@active_runner.nil?
58
+
59
+ active.run_application
60
+ end
61
+
62
+ def self.active
63
+ @@active_runner
64
+ end
65
+
66
+ private
67
+ def default_fabric
68
+ File.join(File.dirname(__FILE__), 'fabrics', 'default.rb')
69
+ end
70
+ end
71
+ end
data/lib/syrup.rb ADDED
@@ -0,0 +1,211 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ require 'syrup/daemon'
4
+ require 'syrup/manager'
5
+ require 'syrup/runner'
6
+ require 'syrup/fabric_support'
7
+
8
+ module Syrup
9
+ # The main application class for Syrup. Handles parsing of command line arguments, configuration, and
10
+ # delegation to the appropriate control classes.
11
+ class Application
12
+ def self.version
13
+ # Load the version from the VERSION.yml file
14
+ version = YAML::load_file(File.join(File.dirname(__FILE__), '..', 'VERSION.yml'))
15
+
16
+ "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
17
+ end
18
+
19
+ def self.logger
20
+ @logger ||= Syrup::Logger.new
21
+ end
22
+
23
+ def run(arguments)
24
+ # Get the logger
25
+ logger = Syrup::Application.logger
26
+
27
+ # Configure defaults
28
+ @options = OpenStruct.new
29
+ @options.directory = File.expand_path('~/.syrup')
30
+ @options.verbose = false
31
+ @options.application = nil
32
+
33
+ # Parse the options
34
+ @opts = build_options
35
+ @opts.parse! arguments
36
+
37
+ # Work out what command was issued
38
+ command, command_args = determine_command(arguments)
39
+ fail "No command given" if command.nil?
40
+
41
+ # Ensure that the configured directory exists
42
+ logger.info "Configured directory as #{@options.directory}" if @options.verbose
43
+ FileUtils.mkdir_p @options.directory if not File.directory? @options.directory
44
+
45
+ # Create a manager instance
46
+ manager = Syrup::Manager.new @options.directory, logger, @options.verbose
47
+
48
+ # Handle the command
49
+ daemon = Syrup::Daemon.new @options.directory, manager
50
+ case command
51
+ when 'start'
52
+ if command_args.length < 1
53
+ manager.start_all
54
+ else
55
+ command_args.each { |arg| manager.start arg }
56
+ end
57
+ when 'stop'
58
+ if command_args.length < 1
59
+ manager.stop_all
60
+ else
61
+ manager.stop(command_args)
62
+ end
63
+ when 'restart'
64
+ if command_args.length < 1
65
+ manager.stop_all
66
+ manager.start_all
67
+ else
68
+ command_args.each { |arg| manager.stop arg }
69
+ command_args.each { |arg| manager.start arg }
70
+ end
71
+ when 'activate'
72
+ fail "No application given to activate a script for!" if command_args.length < 1
73
+ fail "No path given to activate!" if command_args.length < 2
74
+ manager.activate command_args[0], command_args[1], command_args.slice(2, command_args.length - 2)
75
+ when 'run'
76
+ # Provide the command line if provided
77
+ if command_args.length < 1
78
+ manager.run_all
79
+ else
80
+ manager.run command_args
81
+ end
82
+ # when 'run_app'
83
+ # fail('No application name provided') if command_args.length == 0
84
+ # manager.run_app command_args[0] #, command_args.slice(1, command_args.length - 1)
85
+ when 'set'
86
+ fail("No properties provided to set") if command_args.length < 1
87
+ if @options.application
88
+ manager.set_app_properties @options.application, command_args
89
+ else
90
+ manager.set_global_properties command_args
91
+ end
92
+ when 'unset'
93
+ fail("No properties provided to unset") if command_args.length < 1
94
+ if @options.application
95
+ manager.unset_app_properties @options.application, command_args
96
+ else
97
+ manager.unset_global_properties command_args
98
+ end
99
+ when 'clear'
100
+ if @options.application
101
+ manager.clear_app_properties @options.application
102
+ else
103
+ manager.clear_global_properties
104
+ end
105
+ when 'weave'
106
+ fail("No fabric provided to weave") if command_args.length < 1
107
+ if @options.application
108
+ manager.weave_for_application @options.application, command_args[0]
109
+ else
110
+ manager.weave_global command_args[0]
111
+ end
112
+ when 'unweave'
113
+ if @options.application
114
+ manager.unweave_for_application @options.application
115
+ else
116
+ manager.unweave_global
117
+ end
118
+ else
119
+ fail("Unrecognised command \"#{command}\"")
120
+ end
121
+ end
122
+
123
+ def build_options
124
+ opts = OptionParser.new
125
+ opts.banner = "Usage: syrup [options] start [name] | stop [name] | run [<path> | name] | activate [name] <path> |\n" +
126
+ " set <prop>=<value> | unset <prop> | clear | weave <fabric> | unweave"
127
+ opts.separator ""
128
+ opts.separator "Ruby options:"
129
+ opts.on('-d', '--debug', 'set debugging flags (set $DEBUG to true)') { $DEBUG = true }
130
+ opts.on("-I", "--include PATH", "specify $LOAD_PATH (may be used more than once)") { |path|
131
+ $LOAD_PATH.unshift(*path.split(":"))
132
+ }
133
+ opts.on("-r", "--require LIBRARY", "require the library, before executing your script") { |library|
134
+ require library
135
+ }
136
+
137
+ opts.separator ""
138
+ opts.separator "Syrup options:"
139
+ opts.on('-p', '--path PATH', "Sets the base path for this syrup configuration. Defaults to ~/.syrup. See also the --local option to set this") { |value|
140
+ @options.directory = value
141
+ }
142
+ opts.on('--local', "Sets the configuration base path to ./.syrup, allowing for a local configuration") {
143
+ @options.directory = File.expand_path('./.syrup')
144
+ }
145
+ opts.on('--application NAME', "Selects the application that should be updated. Defaults to global settings.") { |value|
146
+ @options.application = value
147
+ }
148
+ opts.on('--verbose', "Places Syrup in verbose mode.") {
149
+ @options.verbose = true
150
+ }
151
+
152
+ opts.separator ""
153
+ opts.separator "Common options:"
154
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit 0 }
155
+ opts.on_tail('-v', '--version', "Print the version and exit") { output_version; exit 0 }
156
+
157
+ opts
158
+ end
159
+
160
+ # Determines the command issued in the provided arguments
161
+ def determine_command(arguments)
162
+ command = nil
163
+ command_args = []
164
+
165
+ # Work through each argument until we reach the command. Then, consume everything
166
+ # after it as arguments to the command
167
+ arguments.each do |arg|
168
+ if command.nil?
169
+ command = arg unless arg =~ /^-/
170
+ else
171
+ command_args << arg
172
+ end
173
+ end
174
+
175
+ return command, command_args
176
+ end
177
+
178
+ # Outputs the version
179
+ def output_version
180
+ puts "Syrup version #{Syrup::Application.version}"
181
+ end
182
+
183
+ def fail(msg)
184
+ puts "ERROR: #{msg}"
185
+ puts @opts
186
+ exit
187
+ end
188
+ end
189
+
190
+ class Logger
191
+ def error(msg)
192
+ puts "ERROR: #{msg}"
193
+ end
194
+ def warn(msg)
195
+ puts "WARN: #{msg}"
196
+ end
197
+ def info(msg)
198
+ puts "INFO: #{msg}"
199
+ end
200
+ def debug(msg)
201
+ puts "DEBUG: #{msg}"
202
+ end
203
+ end
204
+
205
+ # Module Singleton methods
206
+ class << self
207
+ def application
208
+ @application ||= Syrup::Application.new
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,190 @@
1
+ require 'syrup/config_store'
2
+ require 'fileutils'
3
+
4
+ describe Syrup::ConfigStore do
5
+ before :each do
6
+ # Ensure that we have a clear test data directory
7
+ @path = File.join(File.dirname(__FILE__), '..', '..', 'test-data')
8
+ FileUtils.rm_r @path if File.directory? @path
9
+ FileUtils.mkdir_p @path
10
+
11
+ # Create the store
12
+ @store = Syrup::ConfigStore.new @path
13
+ end
14
+
15
+ it "should return an empty list of applications when unconfigured" do
16
+ @store.applications.length.should == 0
17
+ end
18
+
19
+ it "should create an application when requested" do
20
+ @store.create_application 'myapp'
21
+ @store.applications['myapp'].should_not be_nil
22
+ end
23
+
24
+ it "should return the created application when one is created" do
25
+ @store.create_application('myapp').name.should == 'myapp'
26
+ end
27
+
28
+ it "should return no properties on a new config" do
29
+ @store.create_application 'myapp'
30
+ @store.applications['myapp'].properties.length.should == 0
31
+ end
32
+
33
+ it "should allow properties to be stored in a global context" do
34
+ @store.create_application 'myconfapp'
35
+ @store.properties['a']= 'b'
36
+ @store.properties['a'].should == 'b'
37
+ end
38
+
39
+ it "should allow properties to be cleared for the global content" do
40
+ @store.create_application 'myconfapp'
41
+ @store.properties['a']= 'b'
42
+ @store.properties.clear
43
+ @store.properties.length.should == 0
44
+ end
45
+
46
+ it "should allow properties to be deleted for the global context" do
47
+ @store.create_application 'myconfapp'
48
+ @store.properties['a']= 'b'
49
+ @store.properties['b']= 'c'
50
+ @store.properties.delete 'b'
51
+ @store.properties.length.should == 1
52
+ @store.properties['a'].should == 'b'
53
+ end
54
+
55
+ it "should allow all properties to be deleted for the global context" do
56
+ @store.create_application 'myconfapp'
57
+ @store.properties['a']= 'b'
58
+ @store.properties['b']= 'c'
59
+ @store.properties.delete 'a'
60
+ @store.properties.delete 'b'
61
+ @store.properties.length.should == 0
62
+ end
63
+
64
+ it "should return no properties on a new application" do
65
+ @store.create_application 'myapp'
66
+ @store.applications['myapp'].properties.length.should == 0
67
+ end
68
+
69
+ it "should allow properties to be stored for an application" do
70
+ @store.create_application 'myconfapp'
71
+ @store.applications['myconfapp'].properties['a']= 'b'
72
+ @store.applications['myconfapp'].properties['a'].should == 'b'
73
+ end
74
+
75
+ it "should allow properties to be cleared for an application" do
76
+ @store.create_application 'myconfapp'
77
+ @store.applications['myconfapp'].properties['a']= 'b'
78
+ @store.applications['myconfapp'].properties.clear
79
+ @store.applications['myconfapp'].properties.length.should == 0
80
+ end
81
+
82
+ it "should allow properties to be deleted for an application" do
83
+ @store.create_application 'myconfapp'
84
+ @store.applications['myconfapp'].properties['a']= 'b'
85
+ @store.applications['myconfapp'].properties['b']= 'c'
86
+ @store.applications['myconfapp'].properties.delete 'b'
87
+ @store.applications['myconfapp'].properties.length.should == 1
88
+ @store.applications['myconfapp'].properties['a'].should == 'b'
89
+ end
90
+
91
+ it "should allow all properties to be deleted for an application" do
92
+ @store.create_application 'myconfapp'
93
+ @store.applications['myconfapp'].properties['a']= 'b'
94
+ @store.applications['myconfapp'].properties['b']= 'c'
95
+ @store.applications['myconfapp'].properties.delete 'a'
96
+ @store.applications['myconfapp'].properties.delete 'b'
97
+ @store.applications['myconfapp'].properties.length.should == 0
98
+ end
99
+
100
+ it "should return a nil fabric by default" do
101
+ @store.fabric.should be_nil
102
+ end
103
+
104
+ it "should store the fabric location when set" do
105
+ @store.fabric = '../myfabric'
106
+ Syrup::ConfigStore.new(@path).fabric.should == File.expand_path('../myfabric')
107
+ end
108
+
109
+ it "should allow the fabric to be cleared" do
110
+ @store.fabric = '../myfabric'
111
+ @store.fabric = nil
112
+ Syrup::ConfigStore.new(@path).fabric.should be_nil
113
+ end
114
+
115
+ it "should return a nil fabric by default for applications" do
116
+ @store.create_application('myfabapp')
117
+ @store.applications['myfabapp'].fabric.should be_nil
118
+ end
119
+
120
+ it "should store the fabric location when set for applications" do
121
+ @store.create_application('myfabapp')
122
+ @store.applications['myfabapp'].fabric = '../myappfabric'
123
+ Syrup::ConfigStore.new(@path).applications['myfabapp'].fabric.should == File.expand_path('../myappfabric')
124
+ end
125
+
126
+ it "should allow the fabric to be cleared for application" do
127
+ @store.create_application('myfabapp')
128
+ @store.applications['myfabapp'].fabric = '../myappfabric'
129
+ @store.applications['myfabapp'].fabric = nil
130
+ Syrup::ConfigStore.new(@path).applications['myfabapp'].fabric.should be_nil
131
+ end
132
+
133
+ it "should return a nil application path by default for applications" do
134
+ @store.create_application('myfabapp')
135
+ @store.applications['myfabapp'].app.should be_nil
136
+ end
137
+
138
+ it "should store the application path when set for applications" do
139
+ @store.create_application('myfabapp')
140
+ @store.applications['myfabapp'].app = '../myapp.rb'
141
+ Syrup::ConfigStore.new(@path).applications['myfabapp'].app.should == File.expand_path('../myapp.rb')
142
+ end
143
+
144
+ it "should allow the application path to be cleared for application" do
145
+ @store.create_application('myfabapp')
146
+ @store.applications['myfabapp'].app = '../myapp.rb'
147
+ @store.applications['myfabapp'].app = nil
148
+ Syrup::ConfigStore.new(@path).applications['myfabapp'].app.should be_nil
149
+ end
150
+
151
+ it "should return an empty list of start parameters for a new application" do
152
+ @store.create_application('mynpapp')
153
+ @store.applications['mynpapp'].start_parameters.should == []
154
+ end
155
+
156
+ it "should allow start parameters to be stored for an application" do
157
+ @store.create_application('mypapp')
158
+ @store.applications['mypapp'].start_parameters = ['param1', 'param 2']
159
+
160
+ Syrup::ConfigStore.new(@path).applications['mypapp'].start_parameters.should == ['param1', 'param 2']
161
+ end
162
+
163
+ it "should allow start parameters to be cleared for an application" do
164
+ @store.create_application('mycapp')
165
+ @store.applications['mycapp'].start_parameters = ['param1', 'param 2']
166
+ @store.applications['mycapp'].start_parameters = []
167
+
168
+ Syrup::ConfigStore.new(@path).applications['mycapp'].start_parameters.should == []
169
+ end
170
+
171
+ it "should return a nil pid for a new application" do
172
+ @store.create_application('mypidapp')
173
+ @store.applications['mypidapp'].pid.should be_nil
174
+ end
175
+
176
+ it "should allow start parameters to be stored for an application" do
177
+ @store.create_application('mypidapp')
178
+ @store.applications['mypidapp'].pid = 1234
179
+
180
+ Syrup::ConfigStore.new(@path).applications['mypidapp'].pid.should == 1234
181
+ end
182
+
183
+ it "should allow the pid to be cleared for an application" do
184
+ @store.create_application('mypidapp')
185
+ @store.applications['mypidapp'].pid = 1234
186
+ @store.applications['mypidapp'].pid = nil
187
+
188
+ Syrup::ConfigStore.new(@path).applications['mypidapp'].pid.should be_nil
189
+ end
190
+ end