pawnee 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +22 -0
  6. data/README.md +158 -0
  7. data/Rakefile +11 -0
  8. data/bin/pawnee +10 -0
  9. data/docs/FAQ.md +33 -0
  10. data/docs/GUIDE.md +102 -0
  11. data/docs/TODO.md +10 -0
  12. data/lib/pawnee/pawnee/actions/base_model.rb +35 -0
  13. data/lib/pawnee/pawnee/actions/compile.rb +140 -0
  14. data/lib/pawnee/pawnee/actions/package.rb +111 -0
  15. data/lib/pawnee/pawnee/actions/user.rb +116 -0
  16. data/lib/pawnee/pawnee/actions.rb +29 -0
  17. data/lib/pawnee/pawnee/base.rb +265 -0
  18. data/lib/pawnee/pawnee/cli.rb +72 -0
  19. data/lib/pawnee/pawnee/invocation.rb +13 -0
  20. data/lib/pawnee/pawnee/parser/options.rb +55 -0
  21. data/lib/pawnee/pawnee/railtie.rb +23 -0
  22. data/lib/pawnee/pawnee/roles.rb +79 -0
  23. data/lib/pawnee/pawnee/setup.rb +23 -0
  24. data/lib/pawnee/pawnee/templates/newgem/Gemfile.tt +4 -0
  25. data/lib/pawnee/pawnee/templates/newgem/LICENSE.tt +22 -0
  26. data/lib/pawnee/pawnee/templates/newgem/README.md.tt +29 -0
  27. data/lib/pawnee/pawnee/templates/newgem/Rakefile.tt +2 -0
  28. data/lib/pawnee/pawnee/templates/newgem/bin/newgem.tt +3 -0
  29. data/lib/pawnee/pawnee/templates/newgem/gitignore.tt +17 -0
  30. data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem/base.rb.tt +27 -0
  31. data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem/version.rb.tt +7 -0
  32. data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem.rb.tt +10 -0
  33. data/lib/pawnee/pawnee/templates/newgem/newgem.gemspec.tt +18 -0
  34. data/lib/pawnee/pawnee/version.rb +3 -0
  35. data/lib/pawnee/pawnee.rb +7 -0
  36. data/pawnee.gemspec +31 -0
  37. data/spec/actions/compile_spec.rb +26 -0
  38. data/spec/actions/package_spec.rb +41 -0
  39. data/spec/actions/user_spec.rb +54 -0
  40. data/spec/base_spec.rb +167 -0
  41. data/spec/spec_helper.rb +24 -0
  42. data/spec/vagrant/.vagrant +1 -0
  43. data/spec/vagrant/Vagrantfile +99 -0
  44. data/spec/vagrant/vagrant.rb +30 -0
  45. 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in <%=config[:name]%>.gemspec
4
+ gemspec