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.
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