plow 0.1.0 → 1.0.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.
- 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
|
##################################################################################################
|