cult 0.1.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +240 -0
- data/Rakefile +6 -0
- data/cult +1 -0
- data/cult.gemspec +38 -0
- data/doc/welcome.txt +1 -0
- data/exe/cult +86 -0
- data/lib/cult/artifact.rb +45 -0
- data/lib/cult/cli/common.rb +265 -0
- data/lib/cult/cli/console_cmd.rb +124 -0
- data/lib/cult/cli/cri_extensions.rb +84 -0
- data/lib/cult/cli/init_cmd.rb +116 -0
- data/lib/cult/cli/load.rb +26 -0
- data/lib/cult/cli/node_cmd.rb +205 -0
- data/lib/cult/cli/provider_cmd.rb +123 -0
- data/lib/cult/cli/role_cmd.rb +149 -0
- data/lib/cult/cli/task_cmd.rb +140 -0
- data/lib/cult/commander.rb +103 -0
- data/lib/cult/config.rb +22 -0
- data/lib/cult/definition.rb +112 -0
- data/lib/cult/driver.rb +88 -0
- data/lib/cult/drivers/common.rb +192 -0
- data/lib/cult/drivers/digital_ocean_driver.rb +179 -0
- data/lib/cult/drivers/linode_driver.rb +282 -0
- data/lib/cult/drivers/load.rb +26 -0
- data/lib/cult/drivers/script_driver.rb +27 -0
- data/lib/cult/drivers/vultr_driver.rb +217 -0
- data/lib/cult/named_array.rb +129 -0
- data/lib/cult/node.rb +62 -0
- data/lib/cult/project.rb +169 -0
- data/lib/cult/provider.rb +134 -0
- data/lib/cult/role.rb +213 -0
- data/lib/cult/skel.rb +85 -0
- data/lib/cult/task.rb +64 -0
- data/lib/cult/template.rb +92 -0
- data/lib/cult/transferable.rb +61 -0
- data/lib/cult/version.rb +3 -0
- data/lib/cult.rb +4 -0
- data/skel/.cultconsolerc +4 -0
- data/skel/.cultrc.erb +29 -0
- data/skel/README.md.erb +22 -0
- data/skel/keys/.keep +0 -0
- data/skel/nodes/.keep +0 -0
- data/skel/providers/.keep +0 -0
- data/skel/roles/all/role.json +4 -0
- data/skel/roles/all/tasks/00000-do-something-cool +27 -0
- data/skel/roles/bootstrap/files/cult-motd +45 -0
- data/skel/roles/bootstrap/role.json +4 -0
- data/skel/roles/bootstrap/tasks/00000-set-hostname +22 -0
- data/skel/roles/bootstrap/tasks/00001-add-cult-user +21 -0
- data/skel/roles/bootstrap/tasks/00002-install-cult-motd +9 -0
- metadata +183 -0
@@ -0,0 +1,265 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module Cult
|
5
|
+
module CLI
|
6
|
+
|
7
|
+
class CLIError < RuntimeError
|
8
|
+
end
|
9
|
+
|
10
|
+
module_function
|
11
|
+
|
12
|
+
# This sets the global project based on a directory
|
13
|
+
def set_project(path)
|
14
|
+
Cult.project = Cult::Project.locate(path)
|
15
|
+
if Cult.project.nil?
|
16
|
+
fail CLIError, "#{path} does not contain a valid Cult project"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# Quiet mode controls how verbose `say` is
|
22
|
+
def quiet=(v)
|
23
|
+
@quiet = v
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def quiet?
|
28
|
+
@quiet
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def say(v)
|
33
|
+
puts v unless @quiet
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# yes=true automatically answers yes to "yes_no" questions.
|
38
|
+
def yes=(v)
|
39
|
+
@yes = v
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def yes?
|
44
|
+
@yes
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Asks a yes or no question with promp. The prompt defaults to "Yes". If
|
49
|
+
# Cli.yes=true, true is returned without showing the prompt.
|
50
|
+
def yes_no?(prompt, default: true)
|
51
|
+
return true if yes?
|
52
|
+
|
53
|
+
default = case default
|
54
|
+
when :y, :yes
|
55
|
+
true
|
56
|
+
when :n, :no
|
57
|
+
false
|
58
|
+
when true, false
|
59
|
+
default
|
60
|
+
else
|
61
|
+
fail ArgumentError, "invalid :default"
|
62
|
+
end
|
63
|
+
|
64
|
+
loop do
|
65
|
+
y = default ? Rainbow('Y').bright : Rainbow('y').darkgray
|
66
|
+
n = !default ? Rainbow('N').bright : Rainbow('n').darkgray
|
67
|
+
|
68
|
+
begin
|
69
|
+
print "#{prompt} #{y}/#{n}: "
|
70
|
+
case $stdin.gets.chomp
|
71
|
+
when ''
|
72
|
+
return default
|
73
|
+
when /^[Yy]/
|
74
|
+
return true
|
75
|
+
when /^[Nn]/
|
76
|
+
return false
|
77
|
+
else
|
78
|
+
$stderr.puts "Unrecognized response"
|
79
|
+
end
|
80
|
+
rescue Interrupt
|
81
|
+
puts
|
82
|
+
raise
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Asks the user a question, and returns the response. Ensures a newline
|
89
|
+
# exists after the response.
|
90
|
+
def ask(prompt)
|
91
|
+
print "#{prompt}: "
|
92
|
+
$stdin.gets.chomp
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def prompt(*args)
|
97
|
+
ask(*args)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# Disables echo to ask the user a password.
|
102
|
+
def password(prompt)
|
103
|
+
STDIN.noecho do
|
104
|
+
begin
|
105
|
+
ask(prompt)
|
106
|
+
ensure
|
107
|
+
puts
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# it's common for drivers to need the user to visit a URL to
|
114
|
+
# confirm an API key or similar. This does this in the most
|
115
|
+
# compatable way I know.
|
116
|
+
def launch_browser(url)
|
117
|
+
case RUBY_PLATFORM
|
118
|
+
when /darwin/
|
119
|
+
system "open", url
|
120
|
+
when /mswin|mingw|cygwin/
|
121
|
+
system "start", url
|
122
|
+
else
|
123
|
+
system "xdg-open", url
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# We actually want "base 47", so we have to generate substantially more
|
129
|
+
# characters than len. The method already generates 1.25*len characters,
|
130
|
+
# but is offset by _ and - that we discard. With the other characters we
|
131
|
+
# discard, we usethe minimum multiplier which makes a retry "rare" (every
|
132
|
+
# few thousand ids at 6 len), then handle that case.
|
133
|
+
def unique_id(len = 8)
|
134
|
+
@uniq_id_disallowed ||= /[^abcdefhjkmnpqrtvwxyzABCDEFGHJKMNPQRTVWXY2346789]/
|
135
|
+
candidate = SecureRandom.urlsafe_base64((len * 2.1).ceil)
|
136
|
+
.gsub(@uniq_id_disallowed, '')
|
137
|
+
fail RangeError if candidate.size < len
|
138
|
+
candidate[0...len]
|
139
|
+
rescue RangeError
|
140
|
+
retry
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# v is an option or argv value from a user, label: is the name of it.
|
145
|
+
#
|
146
|
+
# This asserts that `v` is in the collection `from`, and returns it.
|
147
|
+
# if `exist` is false, it verifies that v is NOT in the collection and
|
148
|
+
# returns v.
|
149
|
+
#
|
150
|
+
# As a convenience, `from` can be a class like Role, which will imply
|
151
|
+
# 'Cult.project.roles'
|
152
|
+
#
|
153
|
+
# CLIError is raised if these invariants are violated
|
154
|
+
def fetch_item(v, from:, label: nil, exist: true, method: :fetch)
|
155
|
+
implied_from = case
|
156
|
+
when from == Driver; Cult::Drivers.all
|
157
|
+
when from == Provider; Cult.project.providers
|
158
|
+
when from == Role; Cult.project.roles
|
159
|
+
when from == Node; Cult.project.nodes
|
160
|
+
else; nil
|
161
|
+
end
|
162
|
+
|
163
|
+
label ||= implied_from ? from.name.split('::')[-1].downcase : nil
|
164
|
+
from = implied_from
|
165
|
+
|
166
|
+
fail ArgumentError, "label cannot be implied" if label.nil?
|
167
|
+
|
168
|
+
unless [:fetch, :all].include?(method)
|
169
|
+
fail ArgumentError, "method must be :fetch or :all"
|
170
|
+
end
|
171
|
+
|
172
|
+
# We got no argument
|
173
|
+
fail CLIError, "Expected #{label}" if v.nil?
|
174
|
+
|
175
|
+
if exist
|
176
|
+
begin
|
177
|
+
from.send(method, v).tap do |r|
|
178
|
+
# Make sure
|
179
|
+
fail KeyError if method == :all && r.empty?
|
180
|
+
end
|
181
|
+
rescue KeyError
|
182
|
+
fail CLIError, "#{label} does not exist: #{v}"
|
183
|
+
end
|
184
|
+
else
|
185
|
+
if from.key?(v)
|
186
|
+
fail CLIError, "#{label} already exists: #{v}"
|
187
|
+
end
|
188
|
+
v
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Takes a list of keys and returns an array of objects that correspond
|
194
|
+
# to any of them. If required is true, each key must correspond to at
|
195
|
+
# least one object.
|
196
|
+
def fetch_items(*keys, **kw)
|
197
|
+
keys.flatten.map do |key|
|
198
|
+
fetch_item(key, method: :all, **kw)
|
199
|
+
end.flatten
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
# This intercepts GemNeededError and does the installation dance. It looks
|
204
|
+
# a bit hairy because it has a few resumption points, e.g., attempts user
|
205
|
+
# gem install, and if that fails, tries the sudo gem install.
|
206
|
+
def offer_gem_install(&block)
|
207
|
+
prompt_install = ->(gems) do
|
208
|
+
unless quiet?
|
209
|
+
print <<~EOD
|
210
|
+
This driver requires the installation of one or more gems:
|
211
|
+
|
212
|
+
#{gems.inspect}
|
213
|
+
|
214
|
+
Cult can install them for you.
|
215
|
+
EOD
|
216
|
+
end
|
217
|
+
yes_no?("Install?")
|
218
|
+
end
|
219
|
+
|
220
|
+
try_install = ->(gem, sudo: false) do
|
221
|
+
cmd = "gem install #{Shellwords.escape(gem)}"
|
222
|
+
cmd = "sudo #{cmd}" if sudo
|
223
|
+
puts "executing: #{cmd}"
|
224
|
+
system cmd
|
225
|
+
$?.success?
|
226
|
+
end
|
227
|
+
|
228
|
+
begin
|
229
|
+
yield
|
230
|
+
rescue ::Cult::Driver::GemNeededError => needed
|
231
|
+
sudo = false
|
232
|
+
loop do
|
233
|
+
sudo = catch :sudo_attempt do
|
234
|
+
# We don't want to show this again on a retry
|
235
|
+
raise unless sudo || prompt_install.(needed.gems)
|
236
|
+
|
237
|
+
needed.gems.each do |gem|
|
238
|
+
success = try_install.(gem, sudo: sudo)
|
239
|
+
if !success
|
240
|
+
if sudo
|
241
|
+
puts "Nothing seemed to have worked. Giving up."
|
242
|
+
puts "The gems needed are #{needed.gems.inspect}."
|
243
|
+
raise
|
244
|
+
else
|
245
|
+
puts "It doesn't look like that went well."
|
246
|
+
if yes_no?("Retry with sudo?")
|
247
|
+
throw :sudo_attempt, true
|
248
|
+
end
|
249
|
+
raise
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# We exit our non-loop: Everything went fine.
|
255
|
+
break
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Everything went fine, we need to retry the user-supplied block.
|
260
|
+
Gem.refresh
|
261
|
+
retry
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'rainbow'
|
3
|
+
|
4
|
+
module Cult
|
5
|
+
module CLI
|
6
|
+
|
7
|
+
class ConsoleContext < SimpleDelegator
|
8
|
+
attr_accessor :original_argv
|
9
|
+
|
10
|
+
|
11
|
+
def initialize(project, argv)
|
12
|
+
super(project)
|
13
|
+
|
14
|
+
@original_argv = [$0, *argv]
|
15
|
+
ENV['CULT_PROJECT'] = self.path
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def load_rc
|
20
|
+
consolerc = project.location_of(".cultconsolerc")
|
21
|
+
|
22
|
+
# We don't `load' so the rc file has a more convenient context.
|
23
|
+
eval File.read(consolerc) if File.exist?(consolerc)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
private def exit(*)
|
28
|
+
# IRB tries to alias this. And it must be private, or it warns. WTF.
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Gives us an escape hatch to get the real, non-decorated object
|
34
|
+
def project
|
35
|
+
__getobj__
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def cult(*argv)
|
40
|
+
system $0, *argv
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def binding
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module_function
|
50
|
+
def console_cmd
|
51
|
+
Cri::Command.define do
|
52
|
+
name 'console'
|
53
|
+
summary 'Launch an REPL with you project loaded'
|
54
|
+
description <<~EOD.format_description
|
55
|
+
The Cult console loads your project, and starts a Ruby REPL. This can
|
56
|
+
be useful for troubleshooting, or just poking around the project.
|
57
|
+
|
58
|
+
A few convenience global variables are set to inspect.
|
59
|
+
EOD
|
60
|
+
|
61
|
+
flag :i, :irb, 'IRB (default)'
|
62
|
+
flag :r, :ripl, 'Ripl'
|
63
|
+
flag :p, :pry, 'Pry'
|
64
|
+
flag nil, :reexec, 'Console has been exec\'d for a reload'
|
65
|
+
|
66
|
+
run(arguments: 0) do |opts, args, cmd|
|
67
|
+
context = ConsoleContext.new(Cult.project, ARGV)
|
68
|
+
|
69
|
+
if opts[:reexec]
|
70
|
+
$stderr.puts "Reloaded."
|
71
|
+
else
|
72
|
+
$stderr.puts <<~EOD
|
73
|
+
|
74
|
+
Welcome to the #{Rainbow('Cult').green} Console.
|
75
|
+
|
76
|
+
Your project has been made accessible via 'project', and forwards
|
77
|
+
via 'self':
|
78
|
+
|
79
|
+
=> #{context.inspect}
|
80
|
+
|
81
|
+
Useful methods: nodes, roles, providers
|
82
|
+
|
83
|
+
EOD
|
84
|
+
end
|
85
|
+
|
86
|
+
context.load_rc
|
87
|
+
|
88
|
+
if opts[:ripl]
|
89
|
+
require 'ripl'
|
90
|
+
ARGV.clear
|
91
|
+
# Look, something reasonable:
|
92
|
+
Ripl.start(binding: context.binding)
|
93
|
+
|
94
|
+
elsif opts[:pry]
|
95
|
+
require 'pry'
|
96
|
+
context.binding.pry
|
97
|
+
else
|
98
|
+
# irb: This is ridiculous.
|
99
|
+
require 'irb'
|
100
|
+
ARGV.clear
|
101
|
+
IRB.setup(nil)
|
102
|
+
|
103
|
+
irb = IRB::Irb.new(IRB::WorkSpace.new(context.binding))
|
104
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
105
|
+
IRB.conf[:IRB_RC].call(irb.context) if IRB.conf[:IRB_RC]
|
106
|
+
|
107
|
+
trap("SIGINT") do
|
108
|
+
irb.signal_handle
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
catch(:IRB_EXIT) do
|
113
|
+
irb.eval_input
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
IRB::irb_at_exit
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'cri'
|
2
|
+
|
3
|
+
module Cult
|
4
|
+
module CLI
|
5
|
+
class ::String
|
6
|
+
def format_description
|
7
|
+
self.gsub(/(\S)\n(\S)/m, '\1 \2')
|
8
|
+
.gsub(/\.[ ]{2}(\S)/m, '. \1')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
module CommandExtensions
|
14
|
+
def project_required?
|
15
|
+
defined?(@project_required) ? @project_required : true
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def project_required=(v)
|
20
|
+
@project_required = v
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
attr_accessor :argument_spec
|
25
|
+
|
26
|
+
|
27
|
+
# This function returns a wrapped version of the block passed to `run`
|
28
|
+
def block
|
29
|
+
lambda do |opts, args, cmd|
|
30
|
+
if project_required? && Cult.project.nil?
|
31
|
+
fail CLIError, "command '#{name}' requires a Cult project"
|
32
|
+
end
|
33
|
+
|
34
|
+
check_argument_spec!(args, argument_spec) if argument_spec
|
35
|
+
|
36
|
+
super.call(opts, args, cmd)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def check_argument_spec!(args, range)
|
42
|
+
range = (range..range) if range.is_a?(Integer)
|
43
|
+
if range.end == -1
|
44
|
+
range = range.begin .. Float::INFINITY
|
45
|
+
end
|
46
|
+
|
47
|
+
unless range.cover?(args.size)
|
48
|
+
msg = case
|
49
|
+
when range.size == 1 && range.begin == 0
|
50
|
+
"accepts no arguments"
|
51
|
+
when range.size == 1 && range.begin == 1
|
52
|
+
"accepts one argument"
|
53
|
+
when range.begin == range.end
|
54
|
+
"accepts exactly #{range.begin} arguments"
|
55
|
+
else
|
56
|
+
if range.end == Float::INFINITY
|
57
|
+
"requires #{range.begin}+ arguments"
|
58
|
+
else
|
59
|
+
"accepts #{range} arguments"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
fail CLIError, "Command #{msg}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Cri::Command.prepend(self)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
module CommandDSLExtensions
|
71
|
+
def optional_project
|
72
|
+
@command.project_required = false
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def run(arguments: nil, &block)
|
77
|
+
@command.argument_spec = arguments if arguments
|
78
|
+
super(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
Cri::CommandDSL.prepend(self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'cult/skel'
|
2
|
+
require 'cult/project'
|
3
|
+
require 'cult/drivers/load'
|
4
|
+
|
5
|
+
module Cult
|
6
|
+
module CLI
|
7
|
+
|
8
|
+
module_function
|
9
|
+
def init_cmd
|
10
|
+
Cri::Command.define do
|
11
|
+
drivers = Cult::Drivers.all.map{|d| d.driver_name }.join ", "
|
12
|
+
|
13
|
+
optional_project
|
14
|
+
name 'init'
|
15
|
+
aliases 'new'
|
16
|
+
usage 'init DIRECTORY'
|
17
|
+
summary 'Create a new Cult project'
|
18
|
+
description <<~EOD.format_description
|
19
|
+
Generates a new Cult project, based on a project skeleton.
|
20
|
+
|
21
|
+
The most useful option is --driver, which both specifies a driver and
|
22
|
+
sets up a provider of the same name. This will make sure the
|
23
|
+
dependencies for using the driver are install, and any bookkeeping
|
24
|
+
required to start interacting with your VPS provider is handled.
|
25
|
+
|
26
|
+
This usually involves entering an account name or getting an API key.
|
27
|
+
|
28
|
+
The default provider is "script", which isn't too pleasant, but has
|
29
|
+
no dependencies. The "script" driver manages your fleet by executing
|
30
|
+
scripts in $CULT_PROJECT/script, which you have to implement. This is
|
31
|
+
tedious, but very doable. However, if Cult knows about your provider,
|
32
|
+
it can handle all of this without you having to do anything.
|
33
|
+
|
34
|
+
Cult knows about the following providers:
|
35
|
+
|
36
|
+
> #{drivers}
|
37
|
+
|
38
|
+
The init process just gets you started, and it's nothing that couldn't
|
39
|
+
be accomplished by hand, so if you want to change anything later, it's
|
40
|
+
not a big deal.
|
41
|
+
|
42
|
+
The project generated sets up a pretty common configuration: an 'all'
|
43
|
+
role, a 'bootstrap' role, and a demo task that puts a colorful banner
|
44
|
+
in each node's MOTD.
|
45
|
+
EOD
|
46
|
+
|
47
|
+
required :d, :driver, 'Driver with which to create your provider'
|
48
|
+
required :p, :provider, 'Specify an explicit provider name'
|
49
|
+
flag :g, :git, 'Enable Git integration'
|
50
|
+
|
51
|
+
run(arguments: 1) do |opts, args, cmd|
|
52
|
+
project = Project.new(args[0])
|
53
|
+
if project.exist?
|
54
|
+
fail CLIError, "a Cult project already exists in #{project.path}"
|
55
|
+
end
|
56
|
+
|
57
|
+
project.git_integration = opts[:git]
|
58
|
+
|
59
|
+
driver_cls = if !opts[:provider] && !opts[:driver]
|
60
|
+
opts[:provider] ||= 'scripts'
|
61
|
+
CLI.fetch_item(opts[:provider], from: Driver)
|
62
|
+
elsif opts[:provider] && !opts[:driver]
|
63
|
+
CLI.fetch_item(opts[:provider], from: Driver)
|
64
|
+
elsif opts[:driver] && !opts[:provider]
|
65
|
+
CLI.fetch_item(opts[:driver], from: Driver).tap do |dc|
|
66
|
+
opts[:provider] = dc.driver_name
|
67
|
+
end
|
68
|
+
elsif opts[:driver]
|
69
|
+
CLI.fetch_item(opts[:driver], from: Driver)
|
70
|
+
end
|
71
|
+
|
72
|
+
fail CLIError, "Hmm, no driver class" if driver_cls.nil?
|
73
|
+
|
74
|
+
skel = Skel.new(project)
|
75
|
+
skel.copy!
|
76
|
+
|
77
|
+
provider_conf = {
|
78
|
+
name: opts[:provider],
|
79
|
+
driver: driver_cls.driver_name
|
80
|
+
}
|
81
|
+
|
82
|
+
CLI.offer_gem_install do
|
83
|
+
driver_conf = driver_cls.setup!
|
84
|
+
provider_conf.merge!(driver_conf)
|
85
|
+
|
86
|
+
|
87
|
+
provider_dir = File.join(project.location_of("providers"),
|
88
|
+
provider_conf[:name])
|
89
|
+
FileUtils.mkdir_p(provider_dir)
|
90
|
+
|
91
|
+
|
92
|
+
provider_file = File.join(provider_dir,
|
93
|
+
project.dump_name("provider"))
|
94
|
+
File.write(provider_file, project.dump_object(provider_conf))
|
95
|
+
|
96
|
+
|
97
|
+
defaults_file = File.join(provider_dir,
|
98
|
+
project.dump_name("defaults"))
|
99
|
+
defaults = Provider.generate_defaults(provider_conf)
|
100
|
+
File.write(defaults_file, project.dump_object(defaults))
|
101
|
+
end
|
102
|
+
|
103
|
+
if opts[:git]
|
104
|
+
Dir.chdir(project.path) do
|
105
|
+
`git init .`
|
106
|
+
`git add -A`
|
107
|
+
`git commit -m "[Cult] Created new project"`
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'cri'
|
2
|
+
require 'cult/cli/cri_extensions'
|
3
|
+
|
4
|
+
module Cult
|
5
|
+
module CLI
|
6
|
+
|
7
|
+
module_function
|
8
|
+
def load_commands!
|
9
|
+
Dir.glob(File.join(__dir__, "*_cmd.rb")).each do |file|
|
10
|
+
require file
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def commands
|
16
|
+
Cult::CLI.methods(false).select do |m|
|
17
|
+
m.to_s.match(/_cmd\z/)
|
18
|
+
end.map do |m|
|
19
|
+
Cult::CLI.send(m)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Cult::CLI.load_commands!
|