pawnee 0.0.5
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 +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
|