stackfu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|