pawnee 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +158 -0
- data/Rakefile +11 -0
- data/bin/pawnee +10 -0
- data/docs/FAQ.md +33 -0
- data/docs/GUIDE.md +102 -0
- data/docs/TODO.md +10 -0
- data/lib/pawnee/pawnee/actions/base_model.rb +35 -0
- data/lib/pawnee/pawnee/actions/compile.rb +140 -0
- data/lib/pawnee/pawnee/actions/package.rb +111 -0
- data/lib/pawnee/pawnee/actions/user.rb +116 -0
- data/lib/pawnee/pawnee/actions.rb +29 -0
- data/lib/pawnee/pawnee/base.rb +265 -0
- data/lib/pawnee/pawnee/cli.rb +72 -0
- data/lib/pawnee/pawnee/invocation.rb +13 -0
- data/lib/pawnee/pawnee/parser/options.rb +55 -0
- data/lib/pawnee/pawnee/railtie.rb +23 -0
- data/lib/pawnee/pawnee/roles.rb +79 -0
- data/lib/pawnee/pawnee/setup.rb +23 -0
- data/lib/pawnee/pawnee/templates/newgem/Gemfile.tt +4 -0
- data/lib/pawnee/pawnee/templates/newgem/LICENSE.tt +22 -0
- data/lib/pawnee/pawnee/templates/newgem/README.md.tt +29 -0
- data/lib/pawnee/pawnee/templates/newgem/Rakefile.tt +2 -0
- data/lib/pawnee/pawnee/templates/newgem/bin/newgem.tt +3 -0
- data/lib/pawnee/pawnee/templates/newgem/gitignore.tt +17 -0
- data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem/base.rb.tt +27 -0
- data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem/version.rb.tt +7 -0
- data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem.rb.tt +10 -0
- data/lib/pawnee/pawnee/templates/newgem/newgem.gemspec.tt +18 -0
- data/lib/pawnee/pawnee/version.rb +3 -0
- data/lib/pawnee/pawnee.rb +7 -0
- data/pawnee.gemspec +31 -0
- data/spec/actions/compile_spec.rb +26 -0
- data/spec/actions/package_spec.rb +41 -0
- data/spec/actions/user_spec.rb +54 -0
- data/spec/base_spec.rb +167 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/vagrant/.vagrant +1 -0
- data/spec/vagrant/Vagrantfile +99 -0
- data/spec/vagrant/vagrant.rb +30 -0
- metadata +328 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'pawnee/actions/base_model'
|
2
|
+
|
3
|
+
module Pawnee
|
4
|
+
module Actions
|
5
|
+
def create_user(attributes)
|
6
|
+
User.new(self, attributes).save
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete_user(login)
|
10
|
+
User.new(self, {:login => login}).destroy
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
# The user class handles creating, updating, and deleting users. Users are tied to the
|
16
|
+
# login attribute and all other attributes will update based on that login.
|
17
|
+
#
|
18
|
+
# === Passwords
|
19
|
+
# Instead of putting passwords in code, you should either:
|
20
|
+
#
|
21
|
+
# 1) not use a password (which is fine for system users)
|
22
|
+
# or
|
23
|
+
# 2) Set the password value to an encryped password. You can generated an encryped password
|
24
|
+
# with the following ruby command:
|
25
|
+
#
|
26
|
+
# ruby -e "puts 'password'.crypt('passphrase')"
|
27
|
+
#
|
28
|
+
#
|
29
|
+
class User < BaseModel
|
30
|
+
define_attribute_methods [:login, :uid, :gid, :groups, :comment, :shell, :password]
|
31
|
+
change_attr_accessor [:login, :uid, :gid, :groups, :comment, :shell, :password]
|
32
|
+
|
33
|
+
attr_accessor :base
|
34
|
+
|
35
|
+
def initialize(base, attributes)
|
36
|
+
@base = base
|
37
|
+
|
38
|
+
if attributes[:login]
|
39
|
+
self.login = attributes[:login]
|
40
|
+
# Read the current attributes from the system
|
41
|
+
read_from_system()
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set the attributes, track what changed
|
45
|
+
update_attributes(attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
def exec(*args)
|
49
|
+
return base.exec(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def run(*args)
|
53
|
+
return base.run(*args)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Pull in the current (starting) state of the attributes
|
57
|
+
# for the User model
|
58
|
+
def read_from_system
|
59
|
+
@uid, stderr, exit_code, _ = exec("id -u #{login}", true)
|
60
|
+
@uid = @uid.strip
|
61
|
+
if exit_code == 0
|
62
|
+
# The login exists, load in more data
|
63
|
+
@gid = exec("id -g #{login}").strip
|
64
|
+
@groups = exec("groups #{login}").gsub(/^[^:]+[:]/, '').strip.split(/ /)
|
65
|
+
self.new_record = false
|
66
|
+
|
67
|
+
# Reject any ones we just changed, so its as if we did a find with these
|
68
|
+
@changed_attributes = @changed_attributes.reject {|k,v| [:uid, :gid, :groups, :login].include?(k.to_sym) }
|
69
|
+
else
|
70
|
+
# No user
|
71
|
+
@uid = nil
|
72
|
+
self.new_record = true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Write any changes out
|
77
|
+
def save
|
78
|
+
if changed?
|
79
|
+
raise "A login must be specified" unless login
|
80
|
+
|
81
|
+
if new_record?
|
82
|
+
# Just create a new user
|
83
|
+
command = ["useradd"]
|
84
|
+
base.say_status :create_user, login
|
85
|
+
else
|
86
|
+
# Modify an existing user
|
87
|
+
command = ["usermod"]
|
88
|
+
base.say_status :update_user, login
|
89
|
+
end
|
90
|
+
|
91
|
+
# Set options
|
92
|
+
command << "-u #{uid}" if uid && uid_changed?
|
93
|
+
command << "-g #{gid}" if gid && gid_changed?
|
94
|
+
command << "-G #{groups.join(',')}" if groups && groups_changed?
|
95
|
+
command << "-c #{comment.inspect}" if comment && comment_changed?
|
96
|
+
command << "-s #{shell.inspect}" if shell && shell_changed?
|
97
|
+
command << "-p #{password.inspect}" if password && password_changed?
|
98
|
+
command << login
|
99
|
+
|
100
|
+
base.as_root do
|
101
|
+
base.exec(command.join(' '))
|
102
|
+
end
|
103
|
+
else
|
104
|
+
base.say_status :user_exists, login, :blue
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def destroy
|
109
|
+
self.new_record = true
|
110
|
+
base.as_root do
|
111
|
+
base.exec("userdel #{login}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'pawnee/actions/package'
|
2
|
+
require 'pawnee/actions/compile'
|
3
|
+
require 'pawnee/actions/user'
|
4
|
+
|
5
|
+
module Pawnee
|
6
|
+
# The pawnee gem provides the Pawnee::Base class which includes Thor::Actions,
|
7
|
+
# ThorSsh::Actions. Pawnee::Base also adds in its own actions from the pawnee
|
8
|
+
# gem
|
9
|
+
module Actions
|
10
|
+
# This is copied in from thor/actions.rb#initialize,
|
11
|
+
# we can't extend from a module, so we just move this setup
|
12
|
+
# to a method
|
13
|
+
def pawnee_setup_actions(args=[], options={}, config={})
|
14
|
+
self.behavior = case config[:behavior].to_s
|
15
|
+
when "force", "skip"
|
16
|
+
_cleanup_options_and_set(options, config[:behavior])
|
17
|
+
:invoke
|
18
|
+
when "revoke"
|
19
|
+
:revoke
|
20
|
+
else
|
21
|
+
:invoke
|
22
|
+
end
|
23
|
+
|
24
|
+
yield
|
25
|
+
self.destination_root = config[:destination_root]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,265 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'thor'
|
3
|
+
require 'thor-ssh'
|
4
|
+
require "pawnee/setup"
|
5
|
+
require "pawnee/version"
|
6
|
+
require 'pawnee/actions'
|
7
|
+
require 'pawnee/parser/options'
|
8
|
+
require 'active_support/core_ext/hash/deep_merge'
|
9
|
+
require 'pawnee/roles'
|
10
|
+
require 'pawnee/invocation'
|
11
|
+
|
12
|
+
module Pawnee
|
13
|
+
# The pawnee gem provides the Pawnee::Base class, which includes actions
|
14
|
+
# from the thor gem, thor-ssh gem, and the pawnee gem its self. Any class
|
15
|
+
# that inherits from Pawnee::Base will automatically be registered as a
|
16
|
+
# recipe.
|
17
|
+
class Base < Thor
|
18
|
+
include Thor::Actions
|
19
|
+
include ThorSsh::Actions
|
20
|
+
include Pawnee::Actions
|
21
|
+
include Pawnee::Invocation
|
22
|
+
include Roles
|
23
|
+
|
24
|
+
attr_accessor :server
|
25
|
+
|
26
|
+
# Calls the thor initializers, then if :server is passed in as
|
27
|
+
# an option, it will set it up
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
# args<Array[Object]>:: An array of objects. The objects are applied to their
|
31
|
+
# respective accessors declared with <tt>argument</tt>.
|
32
|
+
#
|
33
|
+
# options<Hash>:: An options hash that will be available as self.options.
|
34
|
+
# The hash given is converted to a hash with indifferent
|
35
|
+
# access, magic predicates (options.skip?) and then frozen.
|
36
|
+
#
|
37
|
+
# config<Hash>:: Configuration for this Thor class.
|
38
|
+
# def initialize(*args)
|
39
|
+
#
|
40
|
+
# ==== Options
|
41
|
+
# :server - This can be:
|
42
|
+
# 1) a connected ssh connection (created by Net::SSH.start)
|
43
|
+
# 2) an Array of options to pass to Net::SSH.start
|
44
|
+
# 3) a domain name for the first argument to Net::SSH.start
|
45
|
+
def initialize(args=[], options={}, config={})
|
46
|
+
pawnee_setup_invocations(args, options, config)
|
47
|
+
|
48
|
+
pawnee_setup_actions(args, options, config) do
|
49
|
+
|
50
|
+
# We need to change Thor::Options to use Pawnee::Options on
|
51
|
+
# invoke so we can include the defaults from the config file,
|
52
|
+
# so here we copy in the initialize from thor/base.rb#initialize
|
53
|
+
parse_options = self.class.class_options
|
54
|
+
|
55
|
+
# The start method splits inbound arguments at the first argument
|
56
|
+
# that looks like an option (starts with - or --). It then calls
|
57
|
+
# new, passing in the two halves of the arguments Array as the
|
58
|
+
# first two parameters.
|
59
|
+
|
60
|
+
if options.is_a?(Array)
|
61
|
+
task_options = config.delete(:task_options) # hook for start
|
62
|
+
parse_options = parse_options.merge(task_options) if task_options
|
63
|
+
array_options, hash_options = options, {}
|
64
|
+
else
|
65
|
+
# Handle the case where the class was explicitly instantiated
|
66
|
+
# with pre-parsed options.
|
67
|
+
array_options, hash_options = [], options
|
68
|
+
end
|
69
|
+
|
70
|
+
# Let Thor::Options parse the options first, so it can remove
|
71
|
+
# declared options from the array. This will leave us with
|
72
|
+
# a list of arguments that weren't declared.
|
73
|
+
# --- We change Thor::Options to Pawnee::Options to pull in
|
74
|
+
# the config default's
|
75
|
+
opts = Pawnee::Options.new(parse_options, hash_options)
|
76
|
+
self.options = opts.parse(array_options)
|
77
|
+
|
78
|
+
# If unknown options are disallowed, make sure that none of the
|
79
|
+
# remaining arguments looks like an option.
|
80
|
+
opts.check_unknown! if self.class.check_unknown_options?(config)
|
81
|
+
|
82
|
+
# Add the remaining arguments from the options parser to the
|
83
|
+
# arguments passed in to initialize. Then remove any positional
|
84
|
+
# arguments declared using #argument (this is primarily used
|
85
|
+
# by Thor::Group). Tis will leave us with the remaining
|
86
|
+
# positional arguments.
|
87
|
+
thor_args = Thor::Arguments.new(self.class.arguments)
|
88
|
+
thor_args.parse(args + opts.remaining).each { |k,v| send("#{k}=", v) }
|
89
|
+
args = thor_args.remaining
|
90
|
+
|
91
|
+
@args = args
|
92
|
+
#-- end copy from thor/base.rb#initialize
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
desc "setup", 'setup on the destination server'
|
99
|
+
# All recipies should subclass Pawnee::Base and implement setup to
|
100
|
+
# install everything needed for the gem
|
101
|
+
# setup should be able to be called multiple times
|
102
|
+
def setup
|
103
|
+
raise 'this gem does not implement the setup method'
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "teardown", 'teardown on the destination server'
|
107
|
+
# All recipies should also implement teardown to uninstall anything
|
108
|
+
# that gets installed
|
109
|
+
def teardown
|
110
|
+
raise 'this gem does not implement the teardown method'
|
111
|
+
end
|
112
|
+
|
113
|
+
# Guess the gem name based on the class name
|
114
|
+
def self.gem_name
|
115
|
+
self.name.gsub(/[:][:][^:]+$/, '').gsub(/^[^:]+[:][:]/, '').gsub('::', '-').downcase
|
116
|
+
end
|
117
|
+
|
118
|
+
no_tasks {
|
119
|
+
|
120
|
+
# # Invoke the given task if the given args.
|
121
|
+
def invoke_task(task, *args) #:nodoc:
|
122
|
+
current = @_invocations[self.class]
|
123
|
+
|
124
|
+
unless current.include?(task.name)
|
125
|
+
current << task.name
|
126
|
+
|
127
|
+
# Setup the server connections before we run the task
|
128
|
+
servers = options[:servers] || options['servers']
|
129
|
+
|
130
|
+
if !servers || self.class == Pawnee::CLI
|
131
|
+
# No servers, just run locally
|
132
|
+
task.run(self, *args)
|
133
|
+
else
|
134
|
+
# Run the setup task, setting up the needed connections
|
135
|
+
servers.each do |server|
|
136
|
+
# Only run on this server if the server supports the current recipe's
|
137
|
+
# role.
|
138
|
+
next unless server.is_a?(String) || (server['roles'] && server['roles'].include?(self.class.class_role))
|
139
|
+
|
140
|
+
# Set the server for this call
|
141
|
+
self.server = server.is_a?(String) ? server : server['domain']
|
142
|
+
|
143
|
+
# Run the task
|
144
|
+
task.run(self, *args)
|
145
|
+
|
146
|
+
# Remove the server
|
147
|
+
self.server = nil
|
148
|
+
|
149
|
+
# Close the connection
|
150
|
+
if self.destination_connection
|
151
|
+
self.destination_connection.close
|
152
|
+
self.destination_connection = nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Whenever say is used, also print out the server name
|
160
|
+
def say(*args)
|
161
|
+
text = args[0]
|
162
|
+
text = "[#{server}]:\t" + text
|
163
|
+
super(text, *args[1..-1])
|
164
|
+
end
|
165
|
+
}
|
166
|
+
|
167
|
+
private
|
168
|
+
def self.global_options
|
169
|
+
@global_options = true
|
170
|
+
yield
|
171
|
+
@global_options = false
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.check_unknown_options?(config)
|
175
|
+
false
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# Add options to create global method_options, otherwise
|
180
|
+
# they are now prefixed by the recipe name by default
|
181
|
+
def self.method_option(name, options={})
|
182
|
+
scope = if options[:for]
|
183
|
+
find_and_refresh_task(options[:for]).options
|
184
|
+
else
|
185
|
+
method_options
|
186
|
+
end
|
187
|
+
|
188
|
+
unless @global_options
|
189
|
+
prefix = self.gem_name.gsub('-', '_')
|
190
|
+
name = "#{prefix}_#{name}".to_sym
|
191
|
+
end
|
192
|
+
|
193
|
+
build_option(name, options, scope)
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
# Inherited is called when a class inherits from Pawnee::Base, it then
|
198
|
+
# sets up the class in the pawnee command cli, and sets up the namespace.
|
199
|
+
# It also registeres the recipe so that it can be accessed later
|
200
|
+
def self.inherited(subclass)
|
201
|
+
super(subclass)
|
202
|
+
|
203
|
+
# Make sure cli has been loaded at this point
|
204
|
+
require 'pawnee/cli'
|
205
|
+
|
206
|
+
# Skip the main CLI class
|
207
|
+
return if subclass == Pawnee::CLI
|
208
|
+
|
209
|
+
# Get the name of the parent module, which should what we want to register
|
210
|
+
# this class unser
|
211
|
+
class_name = subclass.gem_name
|
212
|
+
subclass.namespace(class_name)
|
213
|
+
|
214
|
+
# Register the class with the namespace
|
215
|
+
Pawnee::CLI.register subclass, class_name.to_sym, class_name, "Setup #{class_name} on the remote server"
|
216
|
+
|
217
|
+
@recipes ||= []
|
218
|
+
@recipes << subclass
|
219
|
+
|
220
|
+
# Assign the default role (can be overridden by 'role :something' in the class)
|
221
|
+
subclass.role(class_name)
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.recipes
|
225
|
+
@recipes
|
226
|
+
end
|
227
|
+
|
228
|
+
# Pulls in the configuration options from the local path (relative to the Gemfile)
|
229
|
+
# Usually this is also the rails config directory
|
230
|
+
def self.config_options
|
231
|
+
return @config_options if @config_options
|
232
|
+
if defined?(Bundler)
|
233
|
+
@config_options = {}
|
234
|
+
require 'psych' rescue nil
|
235
|
+
require 'yaml'
|
236
|
+
|
237
|
+
config_file = File.join(File.dirname(Bundler.default_gemfile), '/config/pawnee.yml')
|
238
|
+
if File.exists?(config_file)
|
239
|
+
options = YAML.load(File.read(config_file))
|
240
|
+
# Change keys to sym's
|
241
|
+
options = options.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo }
|
242
|
+
|
243
|
+
@config_options.deep_merge!(options)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
return @config_options
|
248
|
+
end
|
249
|
+
|
250
|
+
# Fire this callback whenever a method is added. Added methods are
|
251
|
+
# tracked as tasks by invoking the create_task method.
|
252
|
+
def self.method_added(meth)
|
253
|
+
super
|
254
|
+
|
255
|
+
# Take all of the setup options and copy them to the main
|
256
|
+
# CLI setup task
|
257
|
+
meth = meth.to_s
|
258
|
+
if meth != 'gem' && Pawnee::CLI.tasks[meth]
|
259
|
+
Pawnee::CLI.tasks[meth].options.merge!(self.tasks[meth].options)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'pawnee/base'
|
2
|
+
require 'thor'
|
3
|
+
require 'thor-ssh'
|
4
|
+
|
5
|
+
# thor/group does not get correctly included in thor
|
6
|
+
require 'thor/group'
|
7
|
+
|
8
|
+
module Pawnee
|
9
|
+
class CLI < Pawnee::Base
|
10
|
+
# Set blank namespace
|
11
|
+
namespace ''
|
12
|
+
|
13
|
+
desc "setup", "calls setup for each pawnee gem in bundler"
|
14
|
+
global_options do
|
15
|
+
method_option :roles, :type => :array, :default => :all
|
16
|
+
end
|
17
|
+
def setup
|
18
|
+
Pawnee::Base.invoke_roles(:setup, self.options[:roles], self.options)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
desc "teardown", "calls teardown for each pawnee gem in bundler"
|
23
|
+
global_options do
|
24
|
+
method_option :roles, :type => :array, :default => :all
|
25
|
+
end
|
26
|
+
def teardown
|
27
|
+
Pawnee::Base.invoke_roles(:setup, self.options[:roles], self.options)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Create a new gem (pulled from bundler and modified - MIT LICENSE)
|
32
|
+
desc "gem GEM", "Creates a skeleton recipie"
|
33
|
+
method_option :bin, :type => :boolean, :default => false, :aliases => '-b', :banner => "Generate a binary for your library."
|
34
|
+
def gem(name)
|
35
|
+
# Prefix all gems with pawnee-
|
36
|
+
name = 'pawnee-' + name.chomp("/") # remove trailing slash if present
|
37
|
+
folder_name = name.gsub('-', '/')
|
38
|
+
target = File.join(Dir.pwd, name)
|
39
|
+
constant_name = name.split('_').map{|p| p[0..0].upcase + p[1..-1] }.join
|
40
|
+
constant_name = constant_name.split('-').map{|q| q[0..0].upcase + q[1..-1] }.join('::') if constant_name =~ /-/
|
41
|
+
constant_array = constant_name.split('::')
|
42
|
+
git_user_name = `git config user.name`.chomp
|
43
|
+
git_user_email = `git config user.email`.chomp
|
44
|
+
opts = {
|
45
|
+
# Don't require the pawnee- when requring though
|
46
|
+
:name => name.gsub(/^pawnee[-]/, ''),
|
47
|
+
:constant_name => constant_name,
|
48
|
+
:constant_array => constant_array,
|
49
|
+
:author => git_user_name.empty? ? "TODO: Write your name" : git_user_name,
|
50
|
+
:email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email
|
51
|
+
}
|
52
|
+
template(File.join("newgem/Gemfile.tt"), File.join(target, "Gemfile"), opts)
|
53
|
+
template(File.join("newgem/Rakefile.tt"), File.join(target, "Rakefile"), opts)
|
54
|
+
template(File.join("newgem/LICENSE.tt"), File.join(target, "LICENSE"), opts)
|
55
|
+
template(File.join("newgem/README.md.tt"), File.join(target, "README.md"), opts)
|
56
|
+
template(File.join("newgem/gitignore.tt"), File.join(target, ".gitignore"), opts)
|
57
|
+
template(File.join("newgem/newgem.gemspec.tt"), File.join(target, "#{name}.gemspec"), opts)
|
58
|
+
template(File.join("newgem/lib/pawnee/newgem.rb.tt"), File.join(target, "lib/pawnee/#{name}.rb"), opts)
|
59
|
+
template(File.join("newgem/lib/pawnee/newgem/version.rb.tt"), File.join(target, "lib/#{folder_name}/version.rb"), opts)
|
60
|
+
template(File.join("newgem/lib/pawnee/newgem/base.rb.tt"), File.join(target, "lib/#{folder_name}/base.rb"), opts)
|
61
|
+
if options[:bin]
|
62
|
+
template(File.join("newgem/bin/newgem.tt"), File.join(target, 'bin', name), opts)
|
63
|
+
end
|
64
|
+
Bundler.ui.info "Initializating git repo in #{target}"
|
65
|
+
Dir.chdir(target) { `git init`; `git add .` }
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.source_root
|
69
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Pawnee
|
2
|
+
module Invocation
|
3
|
+
# This is copied in from thor/invocation.rb#initialize,
|
4
|
+
# we can't extend from a module, so we just move this setup
|
5
|
+
# to a method
|
6
|
+
def pawnee_setup_invocations(args=[], options={}, config={}) #:nodoc:
|
7
|
+
# TODO: This may be also called as Thor::Invocation since we're calling
|
8
|
+
# super in the main initialize
|
9
|
+
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
|
10
|
+
@_initializer = [ args, options, config ]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Pawnee
|
2
|
+
class Options < Thor::Options
|
3
|
+
|
4
|
+
# Add the config options in as defaults first
|
5
|
+
def initialize(hash_options={}, defaults={})
|
6
|
+
# TODO: Add options to flatten from yaml
|
7
|
+
Pawnee::Base.config_options.each_pair do |key,value|
|
8
|
+
unless defaults[key]
|
9
|
+
defaults[key] = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
super(hash_options, defaults)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Change the option parsing so it does not freeze the hash
|
17
|
+
def parse(args)
|
18
|
+
@pile = args.dup
|
19
|
+
|
20
|
+
while peek
|
21
|
+
match, is_switch = current_is_switch?
|
22
|
+
shifted = shift
|
23
|
+
|
24
|
+
if is_switch
|
25
|
+
case shifted
|
26
|
+
when SHORT_SQ_RE
|
27
|
+
unshift($1.split('').map { |f| "-#{f}" })
|
28
|
+
next
|
29
|
+
when EQ_RE, SHORT_NUM
|
30
|
+
unshift($2)
|
31
|
+
switch = $1
|
32
|
+
when LONG_RE, SHORT_RE
|
33
|
+
switch = $1
|
34
|
+
end
|
35
|
+
|
36
|
+
switch = normalize_switch(switch)
|
37
|
+
option = switch_option(switch)
|
38
|
+
@assigns[option.human_name] = parse_peek(switch, option)
|
39
|
+
elsif match
|
40
|
+
@extra << shifted
|
41
|
+
@extra << shift while peek && peek !~ /^-/
|
42
|
+
else
|
43
|
+
@extra << shifted
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
check_requirement!
|
48
|
+
|
49
|
+
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
|
50
|
+
# assigns.freeze
|
51
|
+
assigns
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This railtie is for bootstrapping the pawnee gem only
|
2
|
+
# It adds the config/pawnee directory to the loadpath
|
3
|
+
if defined?(Rails)
|
4
|
+
module Pawnee
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "pawnee.configure_rails_initialization" do
|
7
|
+
puts "INITIALIZE1"
|
8
|
+
|
9
|
+
path = (Rails.root + 'config/pawnee').to_s
|
10
|
+
puts path
|
11
|
+
unless $LOAD_PATH.include?(path)
|
12
|
+
$LOAD_PATH.unshift(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
puts "Require: pawnee/base"
|
16
|
+
require 'pawnee/base'
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
else
|
22
|
+
require 'pawnee/base'
|
23
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Pawnee
|
2
|
+
class Base
|
3
|
+
module Roles
|
4
|
+
def self.included(base) #:nodoc:
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Assigns the role for this class
|
10
|
+
def role(role_name)
|
11
|
+
@role = role_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def class_role
|
15
|
+
@role.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the recipe classes in order based on the Gemfile order
|
19
|
+
def ordered_recipes
|
20
|
+
return @ordered_recipes if @ordered_recipes
|
21
|
+
names = Bundler.load.dependencies.map(&:name)
|
22
|
+
|
23
|
+
# Setup a hash with the recipe name and the recipe class
|
24
|
+
recipe_pool = recipes.dup.inject({}) {|memo,recipe| memo[recipe.gem_name] = recipe ; memo }
|
25
|
+
|
26
|
+
# Go through the gems in the order they are in the Gemfile, then
|
27
|
+
# add them to the ordered list
|
28
|
+
@ordered_recipes = []
|
29
|
+
names.each do |name|
|
30
|
+
if recipe_pool[name]
|
31
|
+
@ordered_recipes << recipe_pool[name]
|
32
|
+
recipe_pool.delete(name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add the remaining recipes (load them after everything else)
|
37
|
+
@ordered_recipes += recipe_pool.values
|
38
|
+
|
39
|
+
return @ordered_recipes
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the list of classes that match the current list of roles
|
43
|
+
# in the correct run order
|
44
|
+
def recipe_classes_with_roles(roles)
|
45
|
+
# Check to make sure some recipes have been added
|
46
|
+
if ordered_recipes.size == 0
|
47
|
+
raise Thor::InvocationError, 'no recipes have been defined'
|
48
|
+
end
|
49
|
+
if (roles.is_a?(Array) && roles.size == 0) || roles == :all
|
50
|
+
# Use all classes
|
51
|
+
role_classes = ordered_recipes
|
52
|
+
else
|
53
|
+
# Remove classes that don't fit the roles being used
|
54
|
+
role_classes = ordered_recipes.reject do |recipe_class|
|
55
|
+
![roles].flatten.map(&:to_s).include?(recipe_class.class_role)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Invokes all recipes that implement the passed in role
|
61
|
+
def invoke_roles(task_name, roles, options={})
|
62
|
+
role_classes = self.recipe_classes_with_roles(roles)
|
63
|
+
|
64
|
+
# Run the taks on each role class
|
65
|
+
role_classes.each do |recipe_class|
|
66
|
+
# This class matches the role, so we should run it
|
67
|
+
recipe = recipe_class.new([], options)
|
68
|
+
|
69
|
+
task = recipe_class.tasks[task_name.to_s]
|
70
|
+
recipe.invoke_task(task)
|
71
|
+
|
72
|
+
# Copy back and updated options
|
73
|
+
options = recipe.options
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
# Add setup to the base class
|
4
|
+
module Pawnee
|
5
|
+
class Base < Thor
|
6
|
+
if defined?(Rails)
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.setup(gem_name)
|
12
|
+
# Setup the railtie
|
13
|
+
require "#{gem_name.gsub(/^pawnee[-]/, '')}/base"
|
14
|
+
|
15
|
+
if defined?(Rails)
|
16
|
+
Railtie.initializer "#{gem_name}.configure_rails_initialization" do
|
17
|
+
gem_recipie_name = gem_name.gsub(/^pawnee[-]/, '')
|
18
|
+
require "#{gem_recipie_name}/base"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|