plow 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -2
- data/.yardopts +4 -2
- data/README.markdown +86 -41
- data/Rakefile +34 -28
- data/VERSION +1 -1
- data/bin/plow +1 -7
- data/bin/plow1.9 +1 -7
- data/doc/HISTORY.markdown +41 -0
- data/doc/ROAD-MAP.markdown +12 -0
- data/{SECURITY → doc/SECURITY.markdown} +4 -3
- data/lib/plow/application.rb +33 -1
- data/lib/plow/binding_struct.rb +38 -2
- data/lib/plow/core_ext/object.rb +9 -1
- data/lib/plow/dependencies.rb +123 -0
- data/lib/plow/errors.rb +8 -0
- data/lib/plow/generator.rb +88 -6
- data/lib/plow/strategy/ubuntu_hardy.rb +316 -0
- data/lib/plow.rb +5 -11
- data/spec/plow/application_spec.rb +2 -2
- data/spec/plow/binding_struct_spec.rb +1 -1
- data/spec/plow/dependencies_spec.rb +175 -0
- data/spec/plow/errors_spec.rb +1 -1
- data/spec/plow/generator_spec.rb +6 -6
- data/spec/plow/strategy/{ubuntu_hardy/user_home_web_app_spec.rb → ubuntu_hardy_spec.rb} +96 -96
- data/spec/plow_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -24
- metadata +25 -13
- data/HISTORY +0 -39
- data/ROAD-MAP +0 -10
- data/lib/plow/strategy/ubuntu_hardy/templates/README.txt +0 -19
- data/lib/plow/strategy/ubuntu_hardy/user_home_web_app.rb +0 -304
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class Plow
|
4
|
+
# `Plow::Dependencies` is a class-methods-only **singleton** class that performs both strict
|
5
|
+
# parse-time dependency checking and simple, elegant run-time dependency warning.
|
6
|
+
#
|
7
|
+
# My preferred mental model is to consider software library dependencies as a snapshot in time.
|
8
|
+
# I also make the assumption, sometimes incorrectly, that a newer version of a software library
|
9
|
+
# is not always a better version.
|
10
|
+
#
|
11
|
+
# `Plow::Dependencies` automatically enforces a strict parse-time check for the
|
12
|
+
# `REQUIRED_RUBY_VERSION` on both application and development processes for the `Plow` library.
|
13
|
+
# (i.e. `bin/plow`, `rake`, `spec`, etc) Because of this, I've ensured this file is
|
14
|
+
# syntactically compatible with Ruby 1.8.6 or higher.
|
15
|
+
#
|
16
|
+
# Currently, `Plow` does **not** enforce strict parse-time version checking on `DEVELOPMENT_GEMS`.
|
17
|
+
# In the future, I would like to experiment with using RubyGems and the `Kernel#gem` method to
|
18
|
+
# this end. For now, each developer is responsible for ensuring the correct versions of their
|
19
|
+
# necessary development gems are located in the `$LOAD_PATH` on their system.
|
20
|
+
#
|
21
|
+
# When a gem is required, but a `LoadError` is raised, and rescued, `Plow::Dependencies` can be
|
22
|
+
# incorporated into the process to warn a developer of missing development features. Even with a
|
23
|
+
# few methods -- `.create_warning_for` and `.warn_at_exit` -- users are automatically warned, in
|
24
|
+
# this case at the moment of termination, about gems that could not found be in the `$LOAD_PATH`.
|
25
|
+
# Using `Plow::Dependencies` is **not** a mandatory inclusion for all gem requirements, merely a
|
26
|
+
# guide to help developers quickly see obstacles in their path.
|
27
|
+
#
|
28
|
+
# @example Simple usage with the Jeweler gem
|
29
|
+
# Plow::Dependencies.warn_at_exit
|
30
|
+
# begin
|
31
|
+
# require 'jeweler'
|
32
|
+
# # work with the Jeweler gem
|
33
|
+
# rescue LoadError => e
|
34
|
+
# Plow::Dependencies.create_warning_for(e)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# @see Plow::Dependencies.create_warning_for
|
38
|
+
# @see Plow::Dependencies.warn_at_exit
|
39
|
+
class Dependencies
|
40
|
+
# For now, starting with Ruby 1.9.1 but I would like to experiment with compatibility with Ruby >= 1.9.1 in the future.
|
41
|
+
REQUIRED_RUBY_VERSION = '1.9.1'
|
42
|
+
|
43
|
+
# bluecloth is a hidden yard dependency for markdown support
|
44
|
+
DEVELOPMENT_GEMS = {
|
45
|
+
:jeweler => '1.3.0',
|
46
|
+
:rspec => '1.2.9',
|
47
|
+
:yard => '0.4.0',
|
48
|
+
:bluecloth => '2.0.5'
|
49
|
+
}
|
50
|
+
|
51
|
+
# Thanx rspec for bucking the pattern :(
|
52
|
+
FILE_NAME_TO_GEM_NAME = {
|
53
|
+
:spec => :rspec
|
54
|
+
}
|
55
|
+
|
56
|
+
# Empties the warnings cache. This method is called when the class is required.
|
57
|
+
def self.destroy_warnings
|
58
|
+
@@warnings_cache = []
|
59
|
+
end
|
60
|
+
destroy_warnings
|
61
|
+
|
62
|
+
# Creates and caches a warning from a `LoadError` exception. Warnings are only created for
|
63
|
+
# known development gem dependencies.
|
64
|
+
#
|
65
|
+
# @param [LoadError] error A rescued exception
|
66
|
+
# @raise [RuntimeError] Raised when the `LoadError` argument is an unknown development gem.
|
67
|
+
def self.create_warning_for(error)
|
68
|
+
error.message.match(/no such file to load -- (\w*)/) do |match_data|
|
69
|
+
file_name = match_data[1].to_sym
|
70
|
+
gem_name = if DEVELOPMENT_GEMS.has_key?(file_name)
|
71
|
+
file_name
|
72
|
+
elsif FILE_NAME_TO_GEM_NAME.has_key?(file_name)
|
73
|
+
FILE_NAME_TO_GEM_NAME[file_name]
|
74
|
+
else
|
75
|
+
raise "Cannot create a dependency warning for unknown development gem -- #{file_name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
@@warnings_cache << "#{gem_name} --version '#{DEVELOPMENT_GEMS[gem_name]}'"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Displays a warning message to the user on the standard output channel if there are warnings
|
83
|
+
# to render.
|
84
|
+
#
|
85
|
+
# @example Sample warning message
|
86
|
+
# The following development gem dependencies could not be found. Without them, some available development features are missing:
|
87
|
+
# jeweler --version '1.3.0'
|
88
|
+
# rspec --version '1.2.9'
|
89
|
+
# yard --version '0.4.0'
|
90
|
+
# bluecloth --version '2.0.5'
|
91
|
+
def self.render_warnings
|
92
|
+
unless @@warnings_cache.empty?
|
93
|
+
message = []
|
94
|
+
message << "The following development gem dependencies could not be found. Without them, some available development features are missing:"
|
95
|
+
message += @@warnings_cache
|
96
|
+
puts "\n" + message.join("\n")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Attaches a call to `render_warnings` to `Kernel#at_exit`
|
101
|
+
def self.warn_at_exit
|
102
|
+
at_exit { render_warnings }
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Checks that the version of the current Ruby process matches the `REQUIRED_RUBY_VERSION`.
|
108
|
+
# This method is automatically invoked at the first time this class is required, ensuring the
|
109
|
+
# correct Ruby version at parse-time.
|
110
|
+
#
|
111
|
+
# @param [String] ruby_version Useful for automated specifications. Defaults to `RUBY_VERSION`.
|
112
|
+
# @raise [SystemExit] Raised, with a message, when the process is using an incorrect version of Ruby.
|
113
|
+
def self.check_ruby_version(ruby_version = RUBY_VERSION)
|
114
|
+
unless ruby_version == REQUIRED_RUBY_VERSION
|
115
|
+
abort <<-ERROR
|
116
|
+
This library requires Ruby #{REQUIRED_RUBY_VERSION}, but you're using #{ruby_version}.
|
117
|
+
Please visit http://www.ruby-lang.org/ for installation instructions.
|
118
|
+
ERROR
|
119
|
+
end
|
120
|
+
end
|
121
|
+
check_ruby_version
|
122
|
+
end
|
123
|
+
end
|
data/lib/plow/errors.rb
CHANGED
@@ -1,27 +1,35 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
class Plow
|
4
|
+
# Should be raised when the current process is owned by a non-root user.
|
4
5
|
class NonRootProcessOwnerError < StandardError
|
5
6
|
end
|
6
7
|
|
8
|
+
# Should be raised when the user-supplied system user name is invalid.
|
7
9
|
class InvalidSystemUserNameError < StandardError
|
8
10
|
end
|
9
11
|
|
12
|
+
# Should be raised when the user-supplied web-site name is invalid.
|
10
13
|
class InvalidWebSiteNameError < StandardError
|
11
14
|
end
|
12
15
|
|
16
|
+
# Should be raised when the user-supplied web-site alias is invalid.
|
13
17
|
class InvalidWebSiteAliasError < StandardError
|
14
18
|
end
|
15
19
|
|
20
|
+
# Should be raised when the user-supplied system user name is reserved.
|
16
21
|
class ReservedSystemUserNameError < StandardError
|
17
22
|
end
|
18
23
|
|
24
|
+
# Should be raised when the user-supplied system user name is not found when it should be.
|
19
25
|
class SystemUserNameNotFoundError < StandardError
|
20
26
|
end
|
21
27
|
|
28
|
+
# Should be raised when an application root path already exists when it should not.
|
22
29
|
class AppRootAlreadyExistsError < StandardError
|
23
30
|
end
|
24
31
|
|
32
|
+
# Should be raised when a configuration file already exsits when it should not.
|
25
33
|
class ConfigFileAlreadyExistsError < StandardError
|
26
34
|
end
|
27
35
|
end
|
data/lib/plow/generator.rb
CHANGED
@@ -1,15 +1,49 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
3
2
|
require 'erb'
|
4
|
-
|
5
3
|
require 'plow/binding_struct'
|
6
|
-
require 'plow/strategy/ubuntu_hardy
|
4
|
+
require 'plow/strategy/ubuntu_hardy'
|
7
5
|
|
8
6
|
class Plow
|
7
|
+
# `Plow::Generator` is a **context** class.
|
8
|
+
#
|
9
|
+
# At run-time, `Plow::Generator` selects an appropiate strategy class. Each strategy class
|
10
|
+
# implements a specific algorithm for web-site template generation. An instance of
|
11
|
+
# `Plow::Generator` is intended to be passed to the choosen strategy object, as it provides
|
12
|
+
# read-only data accessors for user-supplied input as well as convience methods that communicate
|
13
|
+
# messages to the user and execute commands on the system.
|
14
|
+
#
|
15
|
+
# Currently, there is a single strategy implementation.
|
16
|
+
#
|
17
|
+
# @see Plow::Strategy::UbuntuHardy
|
9
18
|
class Generator
|
10
19
|
attr_reader :user_name, :site_name, :site_aliases
|
11
20
|
attr_reader :strategy
|
12
21
|
|
22
|
+
# Instantiates a new `Plow::Generator` object with a user name, site name, and optional
|
23
|
+
# site aliases.
|
24
|
+
#
|
25
|
+
# During instantiation, `Plow::Generator` performs basic data validation on the user-supplied
|
26
|
+
# input, raising an exception on any failure. On success, parameters are cached to instance
|
27
|
+
# variables with read-only, public accessors. Finally, the correct strategy class is selected.
|
28
|
+
# This decision is very simple as there is only 1 strategy class at the moment.
|
29
|
+
#
|
30
|
+
# @example Initialization with two required arguments
|
31
|
+
# generator = Plow::Generator.new('steve', 'www.apple.com')
|
32
|
+
#
|
33
|
+
# @example Initialization with an optional third argument
|
34
|
+
# generator = Plow::Generator.new('steve', 'www.apple.com', 'apple.com')
|
35
|
+
#
|
36
|
+
# @return [Plow::Generator] A new instance of `Plow::Generator`
|
37
|
+
#
|
38
|
+
# @param [String] user_name Name of a Linux system account user (e.g. steve)
|
39
|
+
# @param [String] site_name Name of the web-site (e.g. www.apple.com)
|
40
|
+
# @param [splat] *site_aliases (Optional) List of alias names of the web-site (e.g. apple.com)
|
41
|
+
#
|
42
|
+
# @raise [Plow::InvalidSystemUserNameError] Raised when a `user_name` is blank or includes an space character
|
43
|
+
# @raise [Plow::InvalidWebSiteNameError] Raised when a `site_name` is blank or includes an space character
|
44
|
+
# @raise [Plow::InvalidWebSiteAliasError] Raised when any `site_alias` is blank or includes an space character
|
45
|
+
#
|
46
|
+
# @see Plow::Strategy::UbuntuHardy
|
13
47
|
def initialize(user_name, site_name, *site_aliases)
|
14
48
|
if user_name.blank? || user_name.include?(' ')
|
15
49
|
raise(Plow::InvalidSystemUserNameError, user_name)
|
@@ -29,18 +63,51 @@ class Plow
|
|
29
63
|
@site_name = site_name
|
30
64
|
@site_aliases = site_aliases
|
31
65
|
|
32
|
-
@strategy = Plow::Strategy::UbuntuHardy
|
66
|
+
@strategy = Plow::Strategy::UbuntuHardy.new(self)
|
33
67
|
end
|
34
68
|
|
69
|
+
# Executes the pre-choosen strategy.
|
70
|
+
#
|
71
|
+
# @example Executing a strategy pre-choosen by a `Plow::Generator` instance
|
72
|
+
# generator = Plow::Generator.new('steve', 'www.apple.com')
|
73
|
+
# generator.run!
|
74
|
+
#
|
75
|
+
# @raise [Plow::NonRootProcessOwnerError] Raised when the process is owned by a non-root user
|
35
76
|
def run!
|
36
77
|
raise Plow::NonRootProcessOwnerError unless Process.uid == 0
|
37
|
-
strategy.execute
|
78
|
+
strategy.execute!
|
38
79
|
end
|
39
80
|
|
81
|
+
# Renders a message, via the standard output channel, to the user.
|
82
|
+
#
|
83
|
+
# @example A sample user message
|
84
|
+
# generator.say("Hello World!")
|
85
|
+
#
|
86
|
+
# @example Sample output
|
87
|
+
# ==> Hello World!
|
88
|
+
#
|
89
|
+
# @param [String] message A brief message to the user
|
40
90
|
def say(message)
|
41
91
|
puts "==> #{message}"
|
42
92
|
end
|
43
93
|
|
94
|
+
# Executes a set of commands in the user's shell enviroment. Any text rendered out to any
|
95
|
+
# channel during execution is **not** captured and simply displayed to the user. Leading
|
96
|
+
# spaces in a command are stripped away and empty lines are ignored.
|
97
|
+
#
|
98
|
+
# @example Sample usage
|
99
|
+
# generator.shell <<-COMMANDS
|
100
|
+
#
|
101
|
+
# echo "Hello World!"
|
102
|
+
#
|
103
|
+
# cat /etc/passwd
|
104
|
+
# COMMANDS
|
105
|
+
#
|
106
|
+
# @example Sample shell execution
|
107
|
+
# echo "Hello World!"
|
108
|
+
# cat /etc/passwd
|
109
|
+
#
|
110
|
+
# @param [String] commands A set of commands, delimited by line-breaks
|
44
111
|
def shell(commands)
|
45
112
|
commands.each_line do |command|
|
46
113
|
command.strip!
|
@@ -48,8 +115,23 @@ class Plow
|
|
48
115
|
end
|
49
116
|
end
|
50
117
|
|
118
|
+
# Evaluates a template file, located on the user's filesystem, with a context
|
119
|
+
#
|
120
|
+
# @example Evaluating a template file with a context and writing the result to back disk
|
121
|
+
# File.open('/path/to/output/file', 'wt') do |file|
|
122
|
+
# generator = Plow::Generator.new('steve', 'www.apple.com')
|
123
|
+
# context = { :site_name => generator.site_name }
|
124
|
+
#
|
125
|
+
# config = generator.evaluate_template('/path/to/template/file', context)
|
126
|
+
# file.write(config)
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# @return [String] The evaluated template data
|
130
|
+
# @param [String] template_path A Unix path to a template file
|
131
|
+
# @param [Hash] context Key/value pairs, where the key is a template file instance variable,
|
132
|
+
# and a value is the value to be substituted during evaluation
|
51
133
|
def evaluate_template(template_path, context)
|
52
|
-
template
|
134
|
+
template = File.read(template_path)
|
53
135
|
context_struct = Plow::BindingStruct.new(context)
|
54
136
|
ERB.new(template).result(context_struct.get_binding)
|
55
137
|
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class Plow
|
4
|
+
class Strategy
|
5
|
+
# `Plow::Strategy::UbuntuHardy` is a **strategy** class, conditionally instantiated at
|
6
|
+
# run-time, that implements a generator algorithm for it's **context** class,
|
7
|
+
# `Plow::Generator`.
|
8
|
+
#
|
9
|
+
# The algorithm implementation is compatible for and tested with Linux Ubuntu 8.04.3 LTS
|
10
|
+
# (Hardy Heron) running the Apache 2.2.8 web-server. For a description of the algorithm,
|
11
|
+
# please see `Plow::Strategy::UbuntuHardy#execute!`.
|
12
|
+
#
|
13
|
+
# This class has a few code smells that I expect to iron out in time. However, I'll worry
|
14
|
+
# about this when it comes time to share parts of this algorthm across multiple strategy
|
15
|
+
# classes.
|
16
|
+
#
|
17
|
+
# @see Plow::Strategy::UbuntuHardy#execute!
|
18
|
+
# @see Plow::Generator
|
19
|
+
class UbuntuHardy
|
20
|
+
attr_reader :context, :users_file_path, :vhost_file_name, :vhost_file_path, :vhost_template_file_path
|
21
|
+
attr_reader :user_home_path, :sites_home_path, :app_root_path, :app_public_path, :app_log_path
|
22
|
+
|
23
|
+
# Instantiates a new `Plow::Strategy::UbuntuHardy` object from the context of a
|
24
|
+
# `Plow::Generator` instance.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# class Plow
|
28
|
+
# class Generator
|
29
|
+
# def initialize
|
30
|
+
# @strategy = Plow::Strategy::UbuntuHardy.new(self)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @return [Plow::Strategy::UbuntuHardy] A new instance
|
36
|
+
# @param [Plow::Generator] context A reference to the generator context.
|
37
|
+
def initialize(context)
|
38
|
+
@context = context
|
39
|
+
@users_file_path = "/etc/passwd"
|
40
|
+
@vhost_file_name = "#{context.site_name}.conf"
|
41
|
+
@vhost_file_path = "/etc/apache2/sites-available/#{vhost_file_name}"
|
42
|
+
|
43
|
+
@vhost_template_file_path = "#{File.dirname(__FILE__)}/ubuntu_hardy/templates/apache2-vhost.conf"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Starts the generator algorithm. The algorithm works as follows:
|
47
|
+
#
|
48
|
+
# 1. Check for a system user account, create it if one does not exist
|
49
|
+
# 2. Check for a system user home directory, create it if one does not exist
|
50
|
+
# 3. Check for a sites home directory within the system user home, create it if one does not
|
51
|
+
# exist
|
52
|
+
# 4. Create an application root directory within the sites home, but raise an exception if
|
53
|
+
# one already exists
|
54
|
+
# 5. Create an application public directory
|
55
|
+
# 6. Create an application log directory
|
56
|
+
# 7. Create an virtual host configuration file, but raise an exception if one already exists
|
57
|
+
# 8. Install the new virtual host configuration file into the web server
|
58
|
+
#
|
59
|
+
# In addition to the below exceptions, this method may pass-up a raised exception from within
|
60
|
+
# any of the private instance methods.
|
61
|
+
#
|
62
|
+
# @raise [Plow::AppRootAlreadyExistsError] Raised if the web-app root path directory
|
63
|
+
# aleady exists
|
64
|
+
# @raise [Plow::ConfigFileAlreadyExistsError] Raised if a apache2 vhost configuration file
|
65
|
+
# cannot be found
|
66
|
+
def execute!
|
67
|
+
if user_exists?
|
68
|
+
say "existing #{context.user_name} user"
|
69
|
+
else
|
70
|
+
say "creating #{context.user_name} user"
|
71
|
+
create_user!
|
72
|
+
end
|
73
|
+
|
74
|
+
if user_home_exists?
|
75
|
+
say "existing #{user_home_path}"
|
76
|
+
else
|
77
|
+
say "creating #{user_home_path}"
|
78
|
+
create_user_home!
|
79
|
+
end
|
80
|
+
|
81
|
+
if sites_home_exists?
|
82
|
+
say "existing #{sites_home_path}"
|
83
|
+
else
|
84
|
+
say "creating #{sites_home_path}"
|
85
|
+
create_sites_home!
|
86
|
+
end
|
87
|
+
|
88
|
+
if app_root_exists?
|
89
|
+
raise(Plow::AppRootAlreadyExistsError, app_root_path)
|
90
|
+
else
|
91
|
+
say "creating #{app_root_path}"
|
92
|
+
create_app_root!
|
93
|
+
end
|
94
|
+
|
95
|
+
@app_public_path = "#{app_root_path}/public"
|
96
|
+
say "creating #{@app_public_path}"
|
97
|
+
create_app_public!
|
98
|
+
|
99
|
+
@app_log_path = "#{app_root_path}/log"
|
100
|
+
say "creating #{app_log_path}"
|
101
|
+
create_app_logs!
|
102
|
+
|
103
|
+
if vhost_config_exists?
|
104
|
+
raise(Plow::ConfigFileAlreadyExistsError, vhost_file_path)
|
105
|
+
else
|
106
|
+
say "creating #{vhost_file_path}"
|
107
|
+
create_vhost_config!
|
108
|
+
end
|
109
|
+
|
110
|
+
say "installing #{vhost_file_path}"
|
111
|
+
install_vhost_config!
|
112
|
+
end
|
113
|
+
|
114
|
+
############################################################################################################
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Proxy method to `Plow::Generator#say`.
|
119
|
+
# @param [String] message A user output message
|
120
|
+
# @see Plow::Generator#say
|
121
|
+
def say(message)
|
122
|
+
context.say(message)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Proxy method to `Plow::Generator#shell`.
|
126
|
+
# @param [String] commands Shell commands with multi-line support.
|
127
|
+
# @see Plow::Generator#shell
|
128
|
+
def shell(commands)
|
129
|
+
context.shell(commands)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Reads the file at `users_file_path` and yields each user iteratively.
|
133
|
+
#
|
134
|
+
# @yield [Hash] Each user account
|
135
|
+
# @example
|
136
|
+
# users do |user|
|
137
|
+
# user[:name] #=> [String] The name
|
138
|
+
# user[:password] #=> [String] The bogus password
|
139
|
+
# user[:id] #=> [Number] The uid number
|
140
|
+
# user[:group_ip] #=> [Number] The gid number
|
141
|
+
# user[:info] #=> [String] General account info
|
142
|
+
# user[:home_path] #=> [String] The path to the home directory
|
143
|
+
# user[:shell_path] #=> [String] The path to the default shell
|
144
|
+
# end
|
145
|
+
def users(&block)
|
146
|
+
File.readlines(users_file_path).each do |user_line|
|
147
|
+
user_line = user_line.chomp.split(':')
|
148
|
+
user = {
|
149
|
+
:name => user_line[0],
|
150
|
+
:password => user_line[1],
|
151
|
+
:id => user_line[2].to_i,
|
152
|
+
:group_id => user_line[3].to_i,
|
153
|
+
:info => user_line[4],
|
154
|
+
:home_path => user_line[5],
|
155
|
+
:shell_path => user_line[6]
|
156
|
+
}
|
157
|
+
yield user
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
############################################################################################################
|
162
|
+
|
163
|
+
# Determines if the context.user_name already exists or not.
|
164
|
+
#
|
165
|
+
# @return [Boolean] `true` if found otherwise `false`
|
166
|
+
# @raise [Plow::ReservedSystemUserNameError] Raised if the `context.user_name` is a reserved
|
167
|
+
# system user name
|
168
|
+
def user_exists?
|
169
|
+
users do |user|
|
170
|
+
if user[:name] == context.user_name
|
171
|
+
unless user[:id] >= 1000 && user[:id] != 65534
|
172
|
+
raise(Plow::ReservedSystemUserNameError, context.user_name)
|
173
|
+
end
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
return false
|
179
|
+
end
|
180
|
+
|
181
|
+
# Creates a system user account for `context.user_name`.
|
182
|
+
def create_user!
|
183
|
+
shell "adduser #{context.user_name}"
|
184
|
+
end
|
185
|
+
|
186
|
+
############################################################################################################
|
187
|
+
|
188
|
+
# Determines if a home path for the `context.user_name` already exists. As a side-effect,
|
189
|
+
# also correctly sets the `@user_home_path` variable.
|
190
|
+
#
|
191
|
+
# @return [Boolean] `true` if the path already exists, otherwise `false`
|
192
|
+
# @raise [Plow::SystemUserNameNotFoundError] Raised if the `context.user_name` is not found
|
193
|
+
# though it is expected to exist
|
194
|
+
def user_home_exists?
|
195
|
+
users do |user|
|
196
|
+
if user[:name] == context.user_name
|
197
|
+
@user_home_path = user[:home_path]
|
198
|
+
return Dir.exists?(user[:home_path])
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
raise(Plow::SystemUserNameNotFoundError, context.user_name)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Creates a user home directory structure at `user_home_path`.
|
206
|
+
def create_user_home!
|
207
|
+
shell <<-RUN
|
208
|
+
mkdir #{user_home_path}
|
209
|
+
chown #{context.user_name}:#{context.user_name} #{user_home_path}
|
210
|
+
RUN
|
211
|
+
end
|
212
|
+
|
213
|
+
############################################################################################################
|
214
|
+
|
215
|
+
# Determines if the `sites_home_path` already exists. As a side-effect, also correctly
|
216
|
+
# sets the `@sites_home_path` variable.
|
217
|
+
#
|
218
|
+
# @return [Boolean] `true` if the path already exists, otherwise `false`
|
219
|
+
def sites_home_exists?
|
220
|
+
@sites_home_path = "#{user_home_path}/sites"
|
221
|
+
Dir.exists?(sites_home_path)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Creates the sites home directory structure at `sites_home_path`.
|
225
|
+
def create_sites_home!
|
226
|
+
shell <<-RUN
|
227
|
+
mkdir #{sites_home_path}
|
228
|
+
chown #{context.user_name}:#{context.user_name} #{sites_home_path}
|
229
|
+
RUN
|
230
|
+
end
|
231
|
+
|
232
|
+
############################################################################################################
|
233
|
+
|
234
|
+
# Determines if the `app_root_path` already exists. As a side-effect, also correctly
|
235
|
+
# sets the `@app_root_path` variable.
|
236
|
+
#
|
237
|
+
# @return [Boolean] `true` if the path exists, otherwise `false`
|
238
|
+
def app_root_exists?
|
239
|
+
@app_root_path = "#{sites_home_path}/#{context.site_name}"
|
240
|
+
Dir.exists?(app_root_path)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Creates the app root directory structure at `app_root_path`.
|
244
|
+
def create_app_root!
|
245
|
+
shell <<-RUN
|
246
|
+
mkdir #{app_root_path}
|
247
|
+
chown #{context.user_name}:#{context.user_name} #{app_root_path}
|
248
|
+
RUN
|
249
|
+
end
|
250
|
+
|
251
|
+
############################################################################################################
|
252
|
+
|
253
|
+
# Creates the app public directory structure at `app_public_path`.
|
254
|
+
def create_app_public!
|
255
|
+
shell <<-RUN
|
256
|
+
mkdir #{app_public_path}
|
257
|
+
touch #{app_public_path}/index.html
|
258
|
+
chown -R #{context.user_name}:#{context.user_name} #{app_public_path}
|
259
|
+
RUN
|
260
|
+
end
|
261
|
+
|
262
|
+
############################################################################################################
|
263
|
+
|
264
|
+
# Creates the app log directory structure at `app_log_path`.
|
265
|
+
def create_app_logs!
|
266
|
+
shell <<-RUN
|
267
|
+
mkdir #{app_log_path}
|
268
|
+
mkdir #{app_log_path}/apache2
|
269
|
+
chmod 750 #{app_log_path}/apache2
|
270
|
+
|
271
|
+
touch #{app_log_path}/apache2/access.log
|
272
|
+
touch #{app_log_path}/apache2/error.log
|
273
|
+
|
274
|
+
chmod 640 #{app_log_path}/apache2/*.log
|
275
|
+
chown -R #{context.user_name}:#{context.user_name} #{app_log_path}
|
276
|
+
chown root -R #{app_log_path}/apache2
|
277
|
+
RUN
|
278
|
+
end
|
279
|
+
|
280
|
+
############################################################################################################
|
281
|
+
|
282
|
+
# Determines if the apache2 vhost config file already exists.
|
283
|
+
#
|
284
|
+
# @return [Boolean] `true` if the file exists, otherwise `false`
|
285
|
+
def vhost_config_exists?
|
286
|
+
Dir.exists?(vhost_file_path)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Creates an apache2 vhost config file at `vhost_file_path` by evaluateing a template file.
|
290
|
+
def create_vhost_config!
|
291
|
+
File.open(vhost_file_path, 'wt') do |file|
|
292
|
+
template_context = {
|
293
|
+
:site_name => context.site_name,
|
294
|
+
:site_aliases => context.site_aliases,
|
295
|
+
:app_public_path => app_public_path,
|
296
|
+
:app_log_path => app_log_path
|
297
|
+
}
|
298
|
+
|
299
|
+
config = context.evaluate_template(vhost_template_file_path, template_context)
|
300
|
+
file.write(config)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
############################################################################################################
|
305
|
+
|
306
|
+
# Installs the apache2 vhost config file by enabling the site and restarting the web server.
|
307
|
+
def install_vhost_config!
|
308
|
+
shell <<-RUN
|
309
|
+
a2ensite #{vhost_file_name} > /dev/null
|
310
|
+
apache2ctl graceful
|
311
|
+
RUN
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
data/lib/plow.rb
CHANGED
@@ -1,18 +1,12 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
3
|
-
|
4
|
-
abort <<-ERROR
|
5
|
-
Incompatible ruby version error in #{__FILE__} near line #{__LINE__}
|
6
|
-
This library requires at least ruby v1.9.1 but you're using ruby v#{RUBY_VERSION}
|
7
|
-
Please see http://www.ruby-lang.org/
|
8
|
-
ERROR
|
9
|
-
end
|
10
|
-
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__))
|
3
|
+
require 'plow/dependencies' # ruby version guard
|
11
4
|
require 'plow/core_ext/object'
|
12
|
-
|
13
5
|
require 'plow/errors'
|
14
6
|
require 'plow/application'
|
15
7
|
|
8
|
+
# Library namespace
|
16
9
|
class Plow
|
17
|
-
|
10
|
+
# Current stable released version
|
11
|
+
VERSION = "1.0.0"
|
18
12
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
require
|
2
|
+
require 'spec_helper'
|
3
3
|
|
4
4
|
describe Plow::Application do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
@expected_version_stamp = "Plow
|
7
|
+
@expected_version_stamp = "Plow 1.0.0. Copyright (c) 2009 Ryan Sobol. Licensed under the MIT license."
|
8
8
|
end
|
9
9
|
|
10
10
|
##################################################################################################
|