stackfu 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +24 -0
- data/Manifest +118 -0
- data/README +0 -0
- data/README.md +218 -0
- data/Rakefile +73 -0
- data/bin/stackfu +10 -0
- data/lib/stackfu/api_hooks.rb +17 -0
- data/lib/stackfu/app.rb +69 -0
- data/lib/stackfu/commands/command.rb +127 -0
- data/lib/stackfu/commands/config_command.rb +43 -0
- data/lib/stackfu/commands/deploy_command.rb +144 -0
- data/lib/stackfu/commands/dump_command.rb +101 -0
- data/lib/stackfu/commands/generate_command.rb +107 -0
- data/lib/stackfu/commands/help_command.rb +28 -0
- data/lib/stackfu/commands/list_command.rb +26 -0
- data/lib/stackfu/commands/publish_command.rb +108 -0
- data/lib/stackfu/commands/server_command.rb +124 -0
- data/lib/stackfu/helpers/providers_credentials.rb +82 -0
- data/lib/stackfu/helpers/rendering.rb +201 -0
- data/lib/stackfu/operating_systems.rb +30 -0
- data/lib/stackfu.rb +88 -0
- data/stackfu-installer/config/01-controls.yml +17 -0
- data/stackfu-installer/config/02-requirements.yml +3 -0
- data/stackfu-installer/config/03-scripts.yml +16 -0
- data/stackfu-installer/config/04-validations.yml +3 -0
- data/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
- data/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
- data/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
- data/stackfu-installer/script/redis_installation.sh.erb +33 -0
- data/stackfu-installer/script/resque_installation.sh.erb +7 -0
- data/stackfu-installer/script/ruby_environment.sh.erb +20 -0
- data/stackfu-installer/script/stackfu.sh.erb +114 -0
- data/stackfu-installer/stack.yml +5 -0
- data/stackfu.gemspec +60 -0
- data/templates/01-controls.yml.erb +31 -0
- data/templates/02-requirements.yml.erb +26 -0
- data/templates/03-scripts.yml.erb +34 -0
- data/templates/04-validations.yml.erb +17 -0
- data/templates/script.sh.erb +7 -0
- data/templates/stack.yml.erb +17 -0
- data/test/fixtures/add_server_error +7 -0
- data/test/fixtures/deployment_add +7 -0
- data/test/fixtures/deployment_add_error +8 -0
- data/test/fixtures/deployments +7 -0
- data/test/fixtures/logs +7 -0
- data/test/fixtures/logs_partial +7 -0
- data/test/fixtures/plugin_add +6 -0
- data/test/fixtures/plugin_add_error +8 -0
- data/test/fixtures/plugin_deployment_add +7 -0
- data/test/fixtures/plugin_not_found +7 -0
- data/test/fixtures/plugin_unauthorized +8 -0
- data/test/fixtures/plugins +7 -0
- data/test/fixtures/plugins_by_name +7 -0
- data/test/fixtures/plugins_by_name_other +7 -0
- data/test/fixtures/plugins_empty +7 -0
- data/test/fixtures/plugins_multiple +7 -0
- data/test/fixtures/providers +7 -0
- data/test/fixtures/providers_servers +7 -0
- data/test/fixtures/server_add +6 -0
- data/test/fixtures/server_add_dupe +7 -0
- data/test/fixtures/server_add_error +7 -0
- data/test/fixtures/server_delete +7 -0
- data/test/fixtures/server_delete_error +7 -0
- data/test/fixtures/servers +7 -0
- data/test/fixtures/servers_by_name +7 -0
- data/test/fixtures/servers_empty +8 -0
- data/test/fixtures/servers_not_found +26 -0
- data/test/fixtures/servers_unauthorized +8 -0
- data/test/fixtures/servers_webbynode +7 -0
- data/test/fixtures/stack/stackfu-installer/config/01-controls.yml +22 -0
- data/test/fixtures/stack/stackfu-installer/config/02-requirements.yml +1 -0
- data/test/fixtures/stack/stackfu-installer/config/03-scripts.yml +23 -0
- data/test/fixtures/stack/stackfu-installer/config/04-validations.yml +1 -0
- data/test/fixtures/stack/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
- data/test/fixtures/stack/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
- data/test/fixtures/stack/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
- data/test/fixtures/stack/stackfu-installer/script/redis_installation.sh.erb +33 -0
- data/test/fixtures/stack/stackfu-installer/script/resque_installation.sh.erb +7 -0
- data/test/fixtures/stack/stackfu-installer/script/ruby_environment.sh.erb +20 -0
- data/test/fixtures/stack/stackfu-installer/script/stackfu.sh.erb +76 -0
- data/test/fixtures/stack/stackfu-installer/stack.yml +5 -0
- data/test/fixtures/stack_add +6 -0
- data/test/fixtures/stack_add_error +8 -0
- data/test/fixtures/stack_add_error_dupe +8 -0
- data/test/fixtures/stack_adds_by_name +7 -0
- data/test/fixtures/stack_delete_not_found +7 -0
- data/test/fixtures/stacks +7 -0
- data/test/fixtures/stacks_by_name +7 -0
- data/test/fixtures/stacks_by_name_other +7 -0
- data/test/fixtures/stacks_empty +7 -0
- data/test/fixtures/stacks_multiple +7 -0
- data/test/fixtures/stacks_not_found +7 -0
- data/test/fixtures/stacks_realworld +7 -0
- data/test/fixtures/stacks_stackfu-installer +7 -0
- data/test/fixtures/stacks_unauthorized +8 -0
- data/test/fixtures/stacks_with_controls +0 -0
- data/test/fixtures/users +7 -0
- data/test/fixtures/users_no_credentials +7 -0
- data/test/fixtures/users_update +6 -0
- data/test/stack.yml +26 -0
- data/test/support/custom_matchers.rb +69 -0
- data/test/support/fixtures.rb +98 -0
- data/test/support/io_stub.rb +10 -0
- data/test/support/web_fixtures.rb +91 -0
- data/test/test_helper.rb +186 -0
- data/test/unit/commands/test_command.rb +112 -0
- data/test/unit/commands/test_config_command.rb +92 -0
- data/test/unit/commands/test_deploy_command.rb +303 -0
- data/test/unit/commands/test_dump_command.rb +155 -0
- data/test/unit/commands/test_generate_command.rb +112 -0
- data/test/unit/commands/test_help_command.rb +33 -0
- data/test/unit/commands/test_list_command.rb +63 -0
- data/test/unit/commands/test_publish_command.rb +265 -0
- data/test/unit/commands/test_server_command.rb +259 -0
- data/test/unit/helpers/test_rendering.rb +141 -0
- data/test/unit/test_array.rb +26 -0
- data/test/unit/test_provider.rb +14 -0
- data/test/unit/test_stackfu.rb +27 -0
- metadata +311 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
module StackFu
|
2
|
+
class ConfigCommand < Command
|
3
|
+
include StackFu::ApiHooks
|
4
|
+
include StackFu::ProvidersCredentials
|
5
|
+
|
6
|
+
def default(parameters, options)
|
7
|
+
while true
|
8
|
+
login = options[:login] || ask("StackFu Login: ")
|
9
|
+
token = options[:token] || ask("StackFu Token: ")
|
10
|
+
|
11
|
+
break if options[:login] and options[:token]
|
12
|
+
|
13
|
+
puts ""
|
14
|
+
break if agree("Is this information correct? ", true)
|
15
|
+
puts ""
|
16
|
+
puts "OK, let's try it again"
|
17
|
+
puts ""
|
18
|
+
end
|
19
|
+
|
20
|
+
puts ""
|
21
|
+
puts "Configuration saved to #{ENV['HOME']}/.stackfu"
|
22
|
+
save_config(login, token)
|
23
|
+
end
|
24
|
+
|
25
|
+
def webbynode(parameters, options)
|
26
|
+
user = spinner { User.find(:all).first }
|
27
|
+
add_webbynode_credentials(user)
|
28
|
+
end
|
29
|
+
|
30
|
+
def slicehost(parameters, options)
|
31
|
+
user = spinner { User.find(:all).first }
|
32
|
+
add_slicehost_credentials(user)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def save_config(login, token)
|
38
|
+
File.open("#{ENV["HOME"]}/.stackfu", "w") do |file|
|
39
|
+
YAML.dump({ :login => login, :token => token }, file)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module StackFu
|
2
|
+
class DeployCommand < Command
|
3
|
+
include ApiHooks
|
4
|
+
|
5
|
+
error_messages :missing_subcommand => "You have to tell what you want to deploy (a stack or a plugin) and to which server."
|
6
|
+
|
7
|
+
subcommand :plugin, :required_parameters => [:plugin_name, :server]
|
8
|
+
subcommand :stack, :required_parameters => [:stack_name, :server]
|
9
|
+
|
10
|
+
def plugin(parameters, options)
|
11
|
+
execute "plugin", parameters, options, false do |target|
|
12
|
+
puts "*** Preparing: #{target.name.foreground(:yellow).bright}"
|
13
|
+
puts " #{target.description}"
|
14
|
+
puts ""
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def stack(parameters, options)
|
19
|
+
execute "stack", parameters, options, true do |target|
|
20
|
+
puts "*** Deploying: #{target.name.foreground(:yellow).bright} (based on #{os_name(target.operating_system.to_sym).foreground(:yellow)})"
|
21
|
+
puts " #{target.description}"
|
22
|
+
puts ""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def extract_settings(target)
|
29
|
+
target_name = parameters[0]
|
30
|
+
server_name = parameters[1]
|
31
|
+
|
32
|
+
target_class = StackFu::ApiHooks.const_get(target.capitalize)
|
33
|
+
|
34
|
+
targets = target_class.find(:all, :params => { target.to_sym => { :name => target_name } })
|
35
|
+
unless targets.any?
|
36
|
+
error "#{target.capitalize} '#{target_name}' was not found"
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
server = Server.find(:all, :params => { :server => { :hostname => server_name } })
|
41
|
+
unless server.any?
|
42
|
+
error "Server '#{server_name}' was not found.",
|
43
|
+
"You can add servers 'stackfu server add' or list your current servers with 'stackfu servers'."
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
return targets.first, server
|
48
|
+
end
|
49
|
+
|
50
|
+
def execute(target_name, parameters, options, stack)
|
51
|
+
target, server = extract_settings(target_name)
|
52
|
+
return unless target
|
53
|
+
|
54
|
+
yield target
|
55
|
+
|
56
|
+
params = fill_values_from_options(target, options)
|
57
|
+
names = target.controls.map(&:name)
|
58
|
+
review = params.any?
|
59
|
+
|
60
|
+
if (names - params.keys.map(&:to_s)).any?
|
61
|
+
puts "Please configure your deployment by answering the configuration settings below."
|
62
|
+
puts ""
|
63
|
+
|
64
|
+
params = render_target(params, target, options)
|
65
|
+
|
66
|
+
puts ""
|
67
|
+
end
|
68
|
+
|
69
|
+
# We'll only show a review if > 1 item was not collected from console
|
70
|
+
if review
|
71
|
+
max_length = target.controls.map { |c| c.label.size }.max
|
72
|
+
|
73
|
+
puts "Please review the configuration for your deployment:"
|
74
|
+
puts ""
|
75
|
+
target.controls.each do |c|
|
76
|
+
puts " #{c.label.rjust(max_length).foreground(:yellow)}#{":".foreground(:yellow)} #{params[c.name]}"
|
77
|
+
end
|
78
|
+
puts ""
|
79
|
+
end
|
80
|
+
|
81
|
+
continue = if stack
|
82
|
+
warning "This will destroy current contents of your server. Are you sure?\n"
|
83
|
+
else
|
84
|
+
agree "Continue with plugin installation?\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
unless continue
|
88
|
+
puts "Aborted."
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
|
92
|
+
item = target if stack
|
93
|
+
hash = { :stack => item, :server_id => server.first.id, :params => params }
|
94
|
+
|
95
|
+
unless stack
|
96
|
+
hash[:plugin_ids] = [target.id]
|
97
|
+
end
|
98
|
+
|
99
|
+
deployment = Deployment.new(hash)
|
100
|
+
|
101
|
+
unless deployment.save
|
102
|
+
error "There was a problem submitting your deployment: #{deployment.errors.full_messages.to_s}"
|
103
|
+
return
|
104
|
+
end
|
105
|
+
|
106
|
+
if options[:"no-follow"]
|
107
|
+
puts "Your deployment have been submitted"
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
verbose = options[:verbose]
|
112
|
+
|
113
|
+
from = nil
|
114
|
+
while true
|
115
|
+
opts = {:formatted => "true", :from => from}
|
116
|
+
opts.merge!(:verbose => "true") if verbose
|
117
|
+
|
118
|
+
status = spinner {
|
119
|
+
Deployment.find(deployment.id).get(:logs, opts)
|
120
|
+
}
|
121
|
+
|
122
|
+
if status["id"]
|
123
|
+
show_log status["log"]
|
124
|
+
from = status["id"]
|
125
|
+
end
|
126
|
+
|
127
|
+
break if status["state"] == "finished" or status["state"] == "failed"
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def show_log(logs)
|
133
|
+
logs.each do |s|
|
134
|
+
if s =~ /^ \[stdout\]/
|
135
|
+
puts s.chop.gsub(" [stdout] ", "").gsub(/^/, " ").foreground(:yellow)
|
136
|
+
elsif s =~ /^ \[stderr\]/
|
137
|
+
puts s.chop.gsub(" [stderr] ", "").gsub(/^/, " ").foreground(:red)
|
138
|
+
else
|
139
|
+
puts s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module StackFu
|
2
|
+
class DumpCommand < Command
|
3
|
+
include ApiHooks
|
4
|
+
|
5
|
+
error_messages :missing_subcommand => "You have to tell what you want to dump: a stack or a plugin"
|
6
|
+
subcommand :stack, :required_parameters => [:stack_name]
|
7
|
+
|
8
|
+
def stack(parameters, options)
|
9
|
+
stack_name = parameters[0]
|
10
|
+
stack = spinner { Stack.find(:all, :params => { :stack => { :name => stack_name } }).first }
|
11
|
+
|
12
|
+
if stack
|
13
|
+
if directory?(stack_name)
|
14
|
+
unless agree("There is already a folder called '#{stack_name}'. Do you want to overwrite its contents?")
|
15
|
+
puts "Aborted."
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
create_folder(stack_name)
|
21
|
+
create_file "#{stack_name}/stack.yml", {
|
22
|
+
"type" => "stack",
|
23
|
+
"name" => stack.name,
|
24
|
+
"description" => stack.description,
|
25
|
+
"operating_system" => stack.operating_system.to_s
|
26
|
+
}.to_yaml
|
27
|
+
|
28
|
+
create_folder "#{stack_name}/config"
|
29
|
+
|
30
|
+
controls = map stack.controls, "controls" do |c|
|
31
|
+
{ "name" => c.name,
|
32
|
+
"label" => c.label,
|
33
|
+
"type" => c._type }
|
34
|
+
end
|
35
|
+
|
36
|
+
requirements = map stack.requirements, "requirements" do |req|
|
37
|
+
{ "data" => req.data,
|
38
|
+
"error" => req.error,
|
39
|
+
"type" => req._type }
|
40
|
+
end
|
41
|
+
|
42
|
+
executions = map stack.executions, "scripts" do |exec|
|
43
|
+
{ "description" => exec.description,
|
44
|
+
"file" => exec.description.downcase.gsub(" ", "_") }
|
45
|
+
end
|
46
|
+
|
47
|
+
validations = map stack.validations, "validations" do |val|
|
48
|
+
{ "data" => val.data,
|
49
|
+
"error" => val.error,
|
50
|
+
"type" => val._type }
|
51
|
+
end
|
52
|
+
|
53
|
+
create_file "#{stack_name}/config/01-controls.yml", controls
|
54
|
+
create_file "#{stack_name}/config/02-requirements.yml", requirements
|
55
|
+
create_file "#{stack_name}/config/03-scripts.yml", executions
|
56
|
+
create_file "#{stack_name}/config/04-validations.yml", validations
|
57
|
+
|
58
|
+
create_folder "#{stack_name}/script"
|
59
|
+
stack.executions.each do |script|
|
60
|
+
create_file "#{stack_name}/script/#{script.description.downcase.gsub(" ", "_")}.sh.erb", script.data
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "Stack #{stack_name} dumped successfully..."
|
64
|
+
else
|
65
|
+
puts "Stack '#{stack_name}' was not found"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def directory?(folder)
|
72
|
+
File.directory?(folder)
|
73
|
+
end
|
74
|
+
|
75
|
+
def map(collection, name)
|
76
|
+
{ name => collection.map { |item| yield item } }.to_yaml
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_folder(folder)
|
80
|
+
puts "\tcreate #{folder}/"
|
81
|
+
mkdir folder
|
82
|
+
end
|
83
|
+
|
84
|
+
def mkdir(folder)
|
85
|
+
unless File.directory?(folder)
|
86
|
+
Dir.mkdir folder
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_file(file, contents)
|
91
|
+
puts "\tcreate #{file}"
|
92
|
+
write_file(file, contents)
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_file(file, contents)
|
96
|
+
File.open(file, "w") do |file|
|
97
|
+
file.write(contents)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module StackFu
|
5
|
+
class GenerateCommand < Command
|
6
|
+
aliases :create
|
7
|
+
subcommand :stack, :required_parameters => [:stack_name]
|
8
|
+
subcommand :plugin, :required_parameters => [:plugin_name]
|
9
|
+
error_messages :missing_subcommand => "You have to tell what you want to generate: a stack or a plugin."
|
10
|
+
|
11
|
+
Types = {
|
12
|
+
[:checkbox, :numericbox, :combobox, :password, :radio, :textbox] => :control
|
13
|
+
}
|
14
|
+
|
15
|
+
def type(t)
|
16
|
+
return :unknown unless t
|
17
|
+
|
18
|
+
ctrl = t.to_sym
|
19
|
+
Types.each_pair do |key, value|
|
20
|
+
ctrl = value if key.include?(ctrl)
|
21
|
+
end
|
22
|
+
ctrl
|
23
|
+
end
|
24
|
+
|
25
|
+
def plugin(parameters, options)
|
26
|
+
generate("plugin", parameters, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def stack(parameters, options)
|
30
|
+
generate("stack", parameters, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate(what, parameters, options)
|
34
|
+
begin
|
35
|
+
item_name = parameters.shift
|
36
|
+
items = {}
|
37
|
+
while (p = parameters.shift)
|
38
|
+
name, type = p.split(":")
|
39
|
+
|
40
|
+
case type(type)
|
41
|
+
when :script
|
42
|
+
(items["scripts"]||=[]) << [name, template("script.sh.erb", {
|
43
|
+
"filename" => name,
|
44
|
+
"description" => name.titleize
|
45
|
+
})]
|
46
|
+
|
47
|
+
when :control
|
48
|
+
(items["controls"]||=[]) << [name, type]
|
49
|
+
|
50
|
+
else
|
51
|
+
raise Exceptions::InvalidParameter,
|
52
|
+
"Don't know how to generate a #{what} with #{type ? "#{type} " : ""}#{name}. Use 'stackfu help generate' for more information."
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
stack = template("stack.yml.erb", {
|
58
|
+
"name" => item_name,
|
59
|
+
"description" => "Enter a description for this stack here"
|
60
|
+
})
|
61
|
+
|
62
|
+
create("#{item_name}", "#{what}.yml", stack)
|
63
|
+
|
64
|
+
i = 1
|
65
|
+
%w[controls requirements scripts validations].each do |item|
|
66
|
+
template_name = "0#{i}-#{item}.yml"
|
67
|
+
create "#{item_name}/config", template_name, template("#{template_name}.erb", {
|
68
|
+
item => items[item]
|
69
|
+
})
|
70
|
+
i += 1
|
71
|
+
end
|
72
|
+
|
73
|
+
items["scripts"].try(:each) do |item|
|
74
|
+
create("#{item_name}/script", "#{item.first}.sh.erb", item.last)
|
75
|
+
end or create("#{item_name}/script")
|
76
|
+
|
77
|
+
puts "#{what.titleize} #{item_name} created successfully"
|
78
|
+
rescue Exceptions::InvalidParameter
|
79
|
+
puts $!.message
|
80
|
+
rescue IOError, Errno::EEXIST
|
81
|
+
puts "There was an error creating your #{what}: #{$!.message}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def template(t, vars)
|
88
|
+
file = "#{File.dirname(__FILE__)}/../../../templates/#{t}"
|
89
|
+
ERB.new(File.open(file, "r").readlines.join("")).result(OpenStruct.new(vars).send(:binding))
|
90
|
+
end
|
91
|
+
|
92
|
+
def create(d, f=nil, contents=nil)
|
93
|
+
unless File.directory?(d)
|
94
|
+
puts "\tcreate #{d}/"
|
95
|
+
::FileUtils.mkdir_p(d)
|
96
|
+
end
|
97
|
+
|
98
|
+
if f.present?
|
99
|
+
f = "#{d}/#{f}"
|
100
|
+
puts "\tcreate #{f}"
|
101
|
+
File.open(f, "w") do |file|
|
102
|
+
file.write(contents)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module StackFu
|
2
|
+
class HelpCommand < Command
|
3
|
+
def default(parameters, options)
|
4
|
+
stackfu = "stackfu".foreground(:green).bright
|
5
|
+
|
6
|
+
puts "#{"StackFu #{StackFu::VERSION}".foreground(:green).bright}, a server deployment manager."
|
7
|
+
puts " (c) 2009-2020 StackFu - http://stackfu.com"
|
8
|
+
puts ""
|
9
|
+
puts " Usage:"
|
10
|
+
puts " #{stackfu} #{"command".foreground(:cyan)} [arguments...] [options...]"
|
11
|
+
puts ""
|
12
|
+
puts " Examples:"
|
13
|
+
puts " #{stackfu} #{"generate".foreground(:cyan)} stack lamp"
|
14
|
+
puts " #{stackfu} #{"pub".foreground(:cyan)}"
|
15
|
+
puts " #{stackfu} #{"deploy".foreground(:cyan)} lamp server1"
|
16
|
+
puts ""
|
17
|
+
puts " Commands:"
|
18
|
+
puts " #{"help".foreground(:cyan)} you already know about this one, dontcha?"
|
19
|
+
puts " #{"config".foreground(:cyan)} configure StackFu CLI options"
|
20
|
+
puts " #{"server".foreground(:cyan)} create, delete, update and deploy to StackFu servers"
|
21
|
+
puts " #{"generate".foreground(:cyan)} [stack|plugin] creates a new stack or plugin in current_dir/name"
|
22
|
+
puts " #{"publish".foreground(:cyan)} publishes the item on the current folder to StackFu.com"
|
23
|
+
puts ""
|
24
|
+
puts " For a complete guide on using StackFu from command line:"
|
25
|
+
puts " #{"http://stackfu.com/guides/stackfu-cli".underline}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module StackFu
|
2
|
+
class ListCommand < Command
|
3
|
+
include ApiHooks
|
4
|
+
|
5
|
+
def default(parameters, options)
|
6
|
+
user = User.find(:all).first
|
7
|
+
items = spinner {
|
8
|
+
[
|
9
|
+
Stack.find(:all, :conditions => { "user.id" => user.id }),
|
10
|
+
Plugin.find(:all, :conditions => { "user.id" => user.id })
|
11
|
+
].flatten
|
12
|
+
}
|
13
|
+
|
14
|
+
params = {
|
15
|
+
:class => [Stack, Plugin],
|
16
|
+
:collection => items,
|
17
|
+
:display => [:name, :type, :description],
|
18
|
+
:main_column => :name,
|
19
|
+
:empty => "You have nothing to list yet. To generate stacks or plugins, try the 'stackfu generate' command.",
|
20
|
+
:ansi => options[:plain].nil?
|
21
|
+
}
|
22
|
+
|
23
|
+
puts table(params) { |item| [item.name, item.class.name.demodulize.downcase, item.description.try(:truncate_words)] }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module StackFu
|
2
|
+
class PublishCommand < Command
|
3
|
+
include ApiHooks
|
4
|
+
aliases :pub
|
5
|
+
|
6
|
+
def stack?
|
7
|
+
File.exists?("stack.yml")
|
8
|
+
end
|
9
|
+
|
10
|
+
def plugin?
|
11
|
+
File.exists?("plugin.yml")
|
12
|
+
end
|
13
|
+
|
14
|
+
def default(parameters, options)
|
15
|
+
what = :stack if stack?
|
16
|
+
what = :plugin if plugin?
|
17
|
+
|
18
|
+
unless what
|
19
|
+
error "Couldn't find an item to publish on current folder.",
|
20
|
+
"Make sure you have a file named 'stack.yml', 'plugin.yml' or use 'stackfu generate' for creating a new stack."
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
stack_spec = YAML.load(read("#{what}.yml"))
|
25
|
+
|
26
|
+
%w[controls requirements scripts validations].each_with_index do |item, i|
|
27
|
+
if (yaml = read("config/0#{i+1}-#{item}.yml"))
|
28
|
+
yaml.gsub!("type:", "_type:") if item == "controls"
|
29
|
+
if (from_yaml = YAML.load(yaml))
|
30
|
+
stack_spec[item == "scripts" ? "executions" : item] = from_yaml[item]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
unless stack_spec["executions"].present?
|
36
|
+
error "To publish a #{what} you have to define at least one script.",
|
37
|
+
"Take a look at the scripts descriptor file config/03-script.yml for more information.\nYou can also use 'stackfu generate stack_name script_name:script' command to auto-generate a sample script."
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
return unless stack_spec["executions"].each do |script|
|
42
|
+
template = "script/#{script["file"]}.sh.erb"
|
43
|
+
|
44
|
+
begin
|
45
|
+
script["script"] = read(template)
|
46
|
+
rescue Errno::ENOENT
|
47
|
+
error "The template file for the script '#{script["description"]}' was not found.", "This stack has a script called '#{script["description"]}', and the template for it should be in a file called script/#{script["file"]}.sh.erb."
|
48
|
+
break false
|
49
|
+
end
|
50
|
+
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
item_class = ApiHooks.const_get("#{what.to_s.classify}")
|
55
|
+
|
56
|
+
stacks = item_class.find(:all, :params => { what => { :name => stack_spec["name"] } })
|
57
|
+
if stacks.any?
|
58
|
+
unless options[:update]
|
59
|
+
if agree("You already have a #{what} named #{stack_spec["name"]}. Do you want to update it?")
|
60
|
+
puts ""
|
61
|
+
puts "Tip: Next time you can avoid this question using 'stack pub --update'."
|
62
|
+
puts ""
|
63
|
+
else
|
64
|
+
puts "Aborted."
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
stack = stacks.first
|
70
|
+
begin
|
71
|
+
item_class.delete(stack.id)
|
72
|
+
rescue ActiveResource::ResourceNotFound
|
73
|
+
puts "There was a problem updating your #{what}. Please report this problem at support@stackfu.com or try again in a few minutes."
|
74
|
+
return
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
puts "Publishing #{what} #{stack_spec["name"]}..."
|
79
|
+
|
80
|
+
stack = item_class.new(stack_spec)
|
81
|
+
if publish(stack)
|
82
|
+
done "#{what.to_s.titleize} #{stack.name} published."
|
83
|
+
else
|
84
|
+
error "Could not publish your stack: #{stack.errors.full_messages.to_s}"
|
85
|
+
end
|
86
|
+
rescue ActiveResource::ServerError
|
87
|
+
error "#{$!.message}"
|
88
|
+
rescue Errno::ENOENT
|
89
|
+
error "There was an error opening your file descriptor"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def publish(stack)
|
96
|
+
spinner {
|
97
|
+
stack.save
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def read(file)
|
102
|
+
File.open(file, "r").readlines.join("")
|
103
|
+
end
|
104
|
+
|
105
|
+
def load_stack
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module StackFu
|
2
|
+
class ServerCommand < Command
|
3
|
+
include StackFu::ApiHooks
|
4
|
+
include StackFu::ProvidersCredentials
|
5
|
+
|
6
|
+
aliases :servers
|
7
|
+
|
8
|
+
alias_subcommand :list => :default
|
9
|
+
# subcommand :add, :required_parameters => [:provider, :server_name]
|
10
|
+
subcommand :delete, :required_parameters => [:server_name]
|
11
|
+
|
12
|
+
def default(parameters, options)
|
13
|
+
servers = spinner {
|
14
|
+
Server.find(:all)
|
15
|
+
}
|
16
|
+
|
17
|
+
params = {
|
18
|
+
:class => Server,
|
19
|
+
:collection => servers,
|
20
|
+
:display => [:hostname, :provider_class, :ip, :status],
|
21
|
+
:labels => { :hostname => "Name", :provider_class => "Provider", :ip => "IP", :status => "Status" },
|
22
|
+
:main_column => :hostname,
|
23
|
+
:empty => "You have no servers under your account. Try adding some with 'server add' command.",
|
24
|
+
:ansi => options[:plain].nil?
|
25
|
+
}
|
26
|
+
|
27
|
+
puts table(params) { |item| [item.hostname, item.provider_class, item.ip, item.status ? item.status[0] : ""] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(parameters, options)
|
31
|
+
# TODO more than one server with the same hostname
|
32
|
+
spinner {
|
33
|
+
server = Server.find(:all).select { |s| s.hostname == parameters[0] }.first
|
34
|
+
server.destroy
|
35
|
+
}
|
36
|
+
|
37
|
+
puts "Server #{parameters[0]} deleted successfully"
|
38
|
+
end
|
39
|
+
|
40
|
+
def add(parameters, options)
|
41
|
+
if params?
|
42
|
+
if parameters.size < 2
|
43
|
+
puts "The command #{"server add".to_s.foreground(:yellow)} requires 2 parameters.\nUsage: stackfu server add PROVIDER SERVER_NAME"
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
provider = parameters[0]
|
47
|
+
server_name = parameters[1]
|
48
|
+
|
49
|
+
user = User.find(:all).first
|
50
|
+
return false unless send("check_#{provider.downcase}", user)
|
51
|
+
else
|
52
|
+
server_add_header
|
53
|
+
end
|
54
|
+
|
55
|
+
unless params?
|
56
|
+
provider, server_name = *server_menu
|
57
|
+
end
|
58
|
+
|
59
|
+
server = Server.new(:provider_class => provider, :hostname => server_name)
|
60
|
+
result = spinner {
|
61
|
+
server.save
|
62
|
+
}
|
63
|
+
|
64
|
+
puts " "
|
65
|
+
if result
|
66
|
+
puts "Server #{server_name} added successfully."
|
67
|
+
else
|
68
|
+
puts " "
|
69
|
+
puts "Server #{server_name} couldn't be added. Here's the error we've got:\n#{server.errors.full_messages.to_s}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def check_slicehost(user)
|
76
|
+
unless user.settings.respond_to?(:slicehost_token)
|
77
|
+
return false unless add_slicehost_credentials(user)
|
78
|
+
end
|
79
|
+
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_webbynode(user)
|
84
|
+
unless user.settings.respond_to?(:webbynode_login) and user.settings.respond_to?(:webbynode_token)
|
85
|
+
return false unless add_webbynode_credentials(user)
|
86
|
+
end
|
87
|
+
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
|
91
|
+
def server_add_header
|
92
|
+
puts "=== Add Server ===".foreground(:green).bright
|
93
|
+
end
|
94
|
+
|
95
|
+
def server_menu
|
96
|
+
user = User.find(:all).first
|
97
|
+
|
98
|
+
providers = spinner { Provider.find(:all) }
|
99
|
+
provider_id = menu_for("provider", providers, true)
|
100
|
+
|
101
|
+
provider = providers.select { |p| p.id == provider_id }.first
|
102
|
+
|
103
|
+
puts ""
|
104
|
+
puts "Provider: #{provider_id.foreground(:blue)}"
|
105
|
+
puts ""
|
106
|
+
|
107
|
+
return false unless send("check_#{provider_id.downcase}", user)
|
108
|
+
|
109
|
+
servers = spinner { provider.get(:servers).to_structs }
|
110
|
+
server = menu_for("server", servers)
|
111
|
+
|
112
|
+
puts ""
|
113
|
+
puts "Adding server #{server.foreground(:blue)}..."
|
114
|
+
|
115
|
+
[provider_id.titleize, server]
|
116
|
+
end
|
117
|
+
|
118
|
+
def slicehost
|
119
|
+
spinner {
|
120
|
+
UserAccount.find(:conditions => { :provider => "Slicehost" }).servers
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|