stackfu 0.1.1 → 0.1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Manifest +36 -4
- data/Rakefile +1 -1
- data/autotest/discover.rb +1 -0
- data/firewall/config/01-controls.yml +5 -0
- data/firewall/config/02-requirements.yml +4 -0
- data/firewall/config/03-executions.yml +6 -0
- data/firewall/config/03-scripts.yml +6 -0
- data/firewall/config/04-validations.yml +4 -0
- data/firewall/executables/configure_ufw.sh.erb +9 -0
- data/firewall/executables/install_ufw.sh.erb +7 -0
- data/firewall/script/configure_ufw.sh.erb +9 -0
- data/firewall/script/install_ufw.sh.erb +7 -0
- data/firewall/script.yml +8 -0
- data/lib/stackfu/api_hooks.rb +2 -2
- data/lib/stackfu/app.rb +5 -5
- data/lib/stackfu/commands/command.rb +7 -3
- data/lib/stackfu/commands/config_command.rb +1 -1
- data/lib/stackfu/commands/deploy_command.rb +53 -61
- data/lib/stackfu/commands/dump_command.rb +46 -40
- data/lib/stackfu/commands/generate_command.rb +10 -12
- data/lib/stackfu/commands/help_command.rb +1 -1
- data/lib/stackfu/commands/list_command.rb +76 -22
- data/lib/stackfu/commands/publish_command.rb +35 -18
- data/lib/stackfu/commands/server_command.rb +124 -124
- data/lib/stackfu/date_helper.rb +111 -0
- data/lib/stackfu.rb +4 -2
- data/spec/fixtures/scripts/all.json +11 -0
- data/spec/fixtures/scripts/create.json +10 -0
- data/spec/fixtures/scripts/delete.json +10 -0
- data/spec/fixtures/scripts/firewall.json +12 -0
- data/spec/fixtures/scripts/none.json +12 -0
- data/spec/fixtures/scripts/not_found.json +11 -0
- data/spec/fixtures/scripts/script_not_found.json +11 -0
- data/spec/fixtures/servers/all.json +12 -0
- data/spec/fixtures/servers/cannot_deploy.json +0 -0
- data/spec/fixtures/servers/deploy.json +290 -0
- data/spec/fixtures/servers/none.json +12 -0
- data/spec/fixtures/servers/not_found.json +11 -0
- data/spec/fixtures/servers/webbynode/deploy.json +12 -0
- data/spec/fixtures/servers/webbynode.json +12 -0
- data/spec/spec_helper.rb +191 -0
- data/spec/stackfu/api_hooks_spec.rb +7 -0
- data/spec/stackfu/commands/deploy_command_spec.rb +65 -0
- data/spec/stackfu/commands/dump_command_spec.rb +47 -0
- data/spec/stackfu/commands/generate_command_spec.rb +90 -0
- data/spec/stackfu/commands/list_command_spec.rb +53 -0
- data/spec/stackfu/commands/publish_command_spec.rb +67 -0
- data/stackfu.gemspec +4 -4
- data/templates/02-requirements.yml.erb +8 -10
- data/templates/{03-scripts.yml.erb → 03-executions.yml.erb} +3 -3
- data/templates/04-validations.yml.erb +1 -2
- data/templates/stack.yml.erb +4 -13
- data/test/support/fixtures.rb +1 -1
- data/test/unit/commands/test_server_command.rb +259 -259
- metadata +42 -7
- data/test/stack.yml +0 -26
- /data/stackfu-installer/config/{03-scripts.yml → 03-executions.yml} +0 -0
@@ -1,31 +1,85 @@
|
|
1
|
-
module StackFu
|
1
|
+
module StackFu::Commands
|
2
2
|
class ListCommand < Command
|
3
|
-
include ApiHooks
|
3
|
+
include StackFu::ApiHooks
|
4
|
+
|
5
|
+
class << self
|
6
|
+
include DateHelper
|
7
|
+
end
|
4
8
|
|
9
|
+
# error_messages :missing_subcommand => "You have to tell what you want to deploy and to which server."
|
10
|
+
|
11
|
+
subcommand :servers, :required_parameters => []
|
12
|
+
subcommand :scripts, :required_parameters => []
|
13
|
+
|
14
|
+
TableAttributes = {
|
15
|
+
Server => [
|
16
|
+
[:name, :key, :ip, :validated, :last_seen],
|
17
|
+
lambda do |item|
|
18
|
+
last_seen = item.last_checked_in if item.respond_to?(:last_checked_in)
|
19
|
+
|
20
|
+
if last_seen
|
21
|
+
last_seen = distance_of_time_in_words(Time.now, Time.parse(last_seen))
|
22
|
+
last_seen = "#{last_seen} ago"
|
23
|
+
else
|
24
|
+
last_seen = "- never -"
|
25
|
+
end
|
26
|
+
|
27
|
+
validated = item.validated? ? "yes" : ""
|
28
|
+
|
29
|
+
[item.name, item._id, item.ip, validated, last_seen]
|
30
|
+
end,
|
31
|
+
"You have no servers yet. You can add new servers to your account in http://stackfu.com."
|
32
|
+
],
|
33
|
+
Script => [
|
34
|
+
[:name, :description],
|
35
|
+
lambda { |item| [item.name, (item.description || "").truncate_words(10)] },
|
36
|
+
"You have nothing to list yet. To generate a new script, use the 'stackfu generate' command."
|
37
|
+
]
|
38
|
+
}
|
39
|
+
|
40
|
+
def servers(parameters, options)
|
41
|
+
list Server
|
42
|
+
end
|
43
|
+
|
44
|
+
def scripts(parameters, options)
|
45
|
+
list Script
|
46
|
+
end
|
47
|
+
|
5
48
|
def default(parameters, options)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
49
|
+
list [Script, Server]
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def list(things_to_list)
|
55
|
+
things_to_list = [things_to_list] unless things_to_list.is_a?(Enumerable)
|
56
|
+
|
57
|
+
things_to_list.each_with_index do |kls, i|
|
58
|
+
attributes = TableAttributes[kls]
|
59
|
+
|
60
|
+
display = attributes[0]
|
61
|
+
fields = attributes[1]
|
62
|
+
error = attributes[2]
|
63
|
+
|
64
|
+
items = spinner {
|
65
|
+
[
|
66
|
+
kls.find(:all)
|
67
|
+
].flatten
|
68
|
+
}
|
13
69
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
}
|
70
|
+
params = {
|
71
|
+
:class => [kls],
|
72
|
+
:collection => items,
|
73
|
+
:display => display,
|
74
|
+
:main_column => :name,
|
75
|
+
:empty => error,
|
76
|
+
:ansi => options[:plain].nil?
|
77
|
+
}
|
23
78
|
|
24
|
-
|
25
|
-
description = item.respond_to?(:description) ? item.description : ""
|
79
|
+
puts table(params) { |item| fields.call item }
|
26
80
|
|
27
|
-
|
28
|
-
|
81
|
+
puts "" if i < things_to_list.size-1
|
82
|
+
end
|
29
83
|
end
|
30
84
|
end
|
31
85
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
module StackFu
|
1
|
+
module StackFu::Commands
|
2
2
|
class PublishCommand < Command
|
3
|
-
include ApiHooks
|
3
|
+
include StackFu::ApiHooks
|
4
4
|
aliases :pub
|
5
5
|
|
6
6
|
def stack?
|
@@ -12,8 +12,7 @@ module StackFu
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def default(parameters, options)
|
15
|
-
what = :
|
16
|
-
what = :plugin if plugin?
|
15
|
+
what = :script
|
17
16
|
|
18
17
|
unless what
|
19
18
|
error "Couldn't find an item to publish on current folder.",
|
@@ -23,41 +22,58 @@ module StackFu
|
|
23
22
|
begin
|
24
23
|
stack_spec = YAML.load(read("#{what}.yml"))
|
25
24
|
|
26
|
-
%w[controls requirements
|
25
|
+
%w[controls requirements executions validations].each_with_index do |item, i|
|
27
26
|
if (yaml = read("config/0#{i+1}-#{item}.yml"))
|
28
|
-
yaml.gsub!("type:", "_type:")
|
27
|
+
yaml.gsub!("type:", "_type:")
|
29
28
|
if (from_yaml = YAML.load(yaml))
|
29
|
+
if item == 'requirements' or item == 'validations'
|
30
|
+
buffer = []
|
31
|
+
from_yaml[item].each do |itm|
|
32
|
+
itm["params"] = { "data" => itm.delete("data") }
|
33
|
+
buffer << itm
|
34
|
+
end
|
35
|
+
from_yaml[item] = buffer
|
36
|
+
end
|
37
|
+
|
30
38
|
stack_spec[item == "scripts" ? "executions" : item] = from_yaml[item]
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
34
|
-
|
42
|
+
|
35
43
|
unless stack_spec["executions"].present?
|
36
|
-
error "To publish a #{what} you have to define at least one
|
37
|
-
"Take a look at the
|
44
|
+
error "To publish a #{what} you have to define at least one execution.",
|
45
|
+
"Take a look at the executions descriptor file config/03-executions.yml for more information.\nYou can also use 'stackfu generate stack_name script_name:script' command to auto-generate a sample execution."
|
38
46
|
return false
|
39
47
|
end
|
40
48
|
|
41
49
|
return unless stack_spec["executions"].each do |script|
|
42
|
-
template = "
|
50
|
+
template = "executables/#{script["file"]}.sh.erb"
|
43
51
|
|
44
52
|
begin
|
45
|
-
script["
|
53
|
+
script["body"] = read(template)
|
46
54
|
rescue Errno::ENOENT
|
47
|
-
error "The template file for the script '#{script["description"]}' was not found.", "This
|
55
|
+
error "The template file for the script '#{script["description"]}' was not found.", "This script has an executable called '#{script["description"]}', and the template for it should be in a file called executables/#{script["file"]}.sh.erb."
|
48
56
|
break false
|
49
57
|
end
|
50
58
|
|
51
59
|
true
|
52
60
|
end
|
53
61
|
|
54
|
-
item_class = ApiHooks.const_get("#{what.to_s.classify}")
|
62
|
+
item_class = StackFu::ApiHooks.const_get("#{what.to_s.classify}")
|
55
63
|
item_class.format = :json
|
56
64
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
begin
|
66
|
+
stack = item_class.find(stack_spec["name"])
|
67
|
+
rescue ActiveResource::ResourceNotFound
|
68
|
+
rescue NoMethodError
|
69
|
+
if $!.message =~ /closed\?/
|
70
|
+
raise Errno::ECONNREFUSED
|
71
|
+
else
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if stack
|
61
77
|
unless options[:update]
|
62
78
|
if agree("You already have a #{what} named #{stack_spec["name"]}. Do you want to update it?")
|
63
79
|
puts ""
|
@@ -69,7 +85,6 @@ module StackFu
|
|
69
85
|
end
|
70
86
|
end
|
71
87
|
|
72
|
-
stack = stacks.first
|
73
88
|
begin
|
74
89
|
item_class.delete(stack.name)
|
75
90
|
rescue ActiveResource::ResourceNotFound
|
@@ -81,6 +96,7 @@ module StackFu
|
|
81
96
|
puts "Publishing #{what} #{stack_spec["name"]}..."
|
82
97
|
|
83
98
|
stack = item_class.new(stack_spec)
|
99
|
+
|
84
100
|
if publish(stack)
|
85
101
|
done "#{what.to_s.titleize} #{stack.name} published."
|
86
102
|
else
|
@@ -90,6 +106,7 @@ module StackFu
|
|
90
106
|
error "#{$!.message}"
|
91
107
|
rescue Errno::ENOENT
|
92
108
|
error "There was an error opening your file descriptor"
|
109
|
+
raise
|
93
110
|
end
|
94
111
|
end
|
95
112
|
|
@@ -1,124 +1,124 @@
|
|
1
|
-
module StackFu
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
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
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module DateHelper
|
2
|
+
def distance_of_time_in_words_hash(from_time, to_time, options={})
|
3
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
4
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
5
|
+
|
6
|
+
distance = (from_time - to_time).abs
|
7
|
+
distance_of_time_hash(distance, from_time, to_time, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def distance_of_time_hash(distance, from_time = nil, to_time = nil, options={})
|
11
|
+
output = HashWithIndifferentAccess.new
|
12
|
+
from_time ||= Time.now
|
13
|
+
to_time ||= from_time + distance.seconds
|
14
|
+
while distance > 0
|
15
|
+
if distance < 1.minute
|
16
|
+
output["seconds"] = distance.to_i
|
17
|
+
distance = 0
|
18
|
+
elsif distance < 1.hour
|
19
|
+
output["minutes"], distance = distance.divmod(1.minute)
|
20
|
+
elsif distance < 1.day
|
21
|
+
output["hours"], distance = distance.divmod(1.hour)
|
22
|
+
elsif distance < 28.days
|
23
|
+
output["days"], distance = distance.divmod(1.day)
|
24
|
+
# Time has to be greater than a month
|
25
|
+
else
|
26
|
+
smallest, largest = from_time < to_time ? [from_time, to_time] : [to_time, from_time]
|
27
|
+
|
28
|
+
months = (largest.year - smallest.year) * 12 + (largest.month - smallest.month)
|
29
|
+
years, months = months.divmod(12)
|
30
|
+
|
31
|
+
days = largest.day - smallest.day
|
32
|
+
|
33
|
+
# Will otherwise incorrectly say one more day if our range goes over a day.
|
34
|
+
days -= 1 if largest.hour < smallest.hour
|
35
|
+
|
36
|
+
if days < 0
|
37
|
+
# Convert the last month to days and add to total
|
38
|
+
months -= 1
|
39
|
+
last_month = largest.advance(:months => -1)
|
40
|
+
days += Time.days_in_month(last_month.month, last_month.year)
|
41
|
+
end
|
42
|
+
|
43
|
+
if months < 0
|
44
|
+
# Convert a year to months
|
45
|
+
years -= 1
|
46
|
+
months += 12
|
47
|
+
end
|
48
|
+
|
49
|
+
output["years"] = years
|
50
|
+
output["months"] = months
|
51
|
+
output["days"] = days
|
52
|
+
|
53
|
+
total_days, distance = distance.divmod(1.day)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
output
|
57
|
+
end
|
58
|
+
|
59
|
+
def distance_of_time(seconds, options = {})
|
60
|
+
display_time_in_words(distance_of_time_hash(seconds), options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def display_time_in_words(hash, include_seconds = false, options = {})
|
64
|
+
options.symbolize_keys!
|
65
|
+
hash.delete(:seconds) if !include_seconds && hash[:minutes]
|
66
|
+
# Remove all the values that are nil.
|
67
|
+
time_measurements = ["years",
|
68
|
+
"months",
|
69
|
+
"weeks",
|
70
|
+
"days",
|
71
|
+
"hours",
|
72
|
+
"minutes",
|
73
|
+
"seconds" ].delete_if do |key|
|
74
|
+
hash[key].nil? || hash[key].zero? ||
|
75
|
+
# Remove the keys that we don't want.
|
76
|
+
(!options[:except].nil? && options[:except].include?(key)) ||
|
77
|
+
# keep the keys we only want.
|
78
|
+
(options[:only] && !options[:only].include?(key))
|
79
|
+
end
|
80
|
+
|
81
|
+
options.delete(:except)
|
82
|
+
options.delete(:only)
|
83
|
+
output = []
|
84
|
+
time_measurements.each do |key|
|
85
|
+
name = hash[key] > 1 ? key : key.singularize
|
86
|
+
output += ["#{hash[key]} #{name}"]
|
87
|
+
end
|
88
|
+
|
89
|
+
# maybe only grab the first few values
|
90
|
+
if options[:precision]
|
91
|
+
output = output[0...options[:precision]]
|
92
|
+
options.delete(:precision)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
output.to_sentence(options)
|
97
|
+
end
|
98
|
+
|
99
|
+
def distance_of_time_in_words(from_time, to_time, include_seconds = false, options = {})
|
100
|
+
return old_distance_of_time_in_words(from_time, to_time, include_seconds, options) if options.delete(:vague)
|
101
|
+
hash = distance_of_time_in_words_hash(from_time, to_time, options)
|
102
|
+
display_time_in_words(hash, include_seconds, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def distance_of_time_in_percent(from_time, current_time, to_time, options = {})
|
106
|
+
options[:precision] ||= 0
|
107
|
+
distance = to_time - from_time
|
108
|
+
number_with_precision(((current_time - from_time) / distance) * 100, options).to_s + "%"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
data/lib/stackfu.rb
CHANGED
@@ -37,6 +37,7 @@ require "#{dir}/helpers/providers_credentials"
|
|
37
37
|
require "#{dir}/operating_systems"
|
38
38
|
require "#{dir}/api_hooks"
|
39
39
|
require "#{dir}/app"
|
40
|
+
require "#{dir}/date_helper"
|
40
41
|
|
41
42
|
require "#{dir}/commands/command"
|
42
43
|
require "#{dir}/commands/help_command"
|
@@ -50,12 +51,13 @@ require "#{dir}/commands/dump_command"
|
|
50
51
|
|
51
52
|
module StackFu
|
52
53
|
VERSION = '0.1.1'
|
53
|
-
API = "http://stackfu.com"
|
54
|
+
# API = "http://beta.stackfu.com"
|
55
|
+
API = "http://localhost:3000"
|
54
56
|
CONFIG_FILE = "#{ENV['HOME']}/.stackfu"
|
55
57
|
|
56
58
|
include StackFu::OperatingSystems
|
57
59
|
|
58
|
-
module Exceptions
|
60
|
+
module Commands::Exceptions
|
59
61
|
class UnknownCommand < StandardError; end
|
60
62
|
class InvalidCommand < StandardError; end
|
61
63
|
class InvalidSubcommand < StandardError; end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Etag: "473e36b98defa0d6d071cbbcc0d4d637"
|
3
|
+
Connection: Keep-Alive
|
4
|
+
Content-Type: application/json; charset=utf-8
|
5
|
+
Date: Fri, 03 Sep 2010 18:55:17 GMT
|
6
|
+
Server: WEBrick/1.3.1 (Ruby/1.8.7/2009-12-24)
|
7
|
+
X-Runtime: 1.067382
|
8
|
+
Content-Length: 6099
|
9
|
+
Cache-Control: max-age=0, private, must-revalidate
|
10
|
+
|
11
|
+
[{"slug":"firewall","name":"firewall","controls":[{"name":"Ports","label":"Ports","_id":"4c7fd205d489e8d081000005","_type":"Textbox","type":"Textbox","hint":"separate by comma's"}],"created_at":"2010-09-02T13:34:13-03:00","updated_at":"2010-09-02T13:34:13-03:00","_id":"4c7fd205d489e8d081000008","validations":[{"body":"test -x ufw","_id":"4c7fd205d489e8d081000002","_type":"ExecutableExists","params":{"data":"ufw"},"description":"File ufw exists and is executable"}],"user_id":"4c7fd205d489e8d081000003","requirements":[{"body":"test -x apt-get","_id":"4c7fd205d489e8d081000001","_type":"ExecutableExists","params":{"data":"apt-get"},"description":"File apt-get exists and is executable"}],"executions":[{"body":" # \n # configure_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n apt-get update\n apt-get install -y ufw\n","_id":"4c7fd205d489e8d081000006","description":"Install Ufw"},{"body":" # \n # install_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n ufw default deny\n <% ports.split(\",\").each do |port| %>\n ufw allow <%= port %> \n <% end %>\n","_id":"4c7fd205d489e8d081000007","description":"Configure Ufw"}],"description":"Set up a firewall for your server to improve security.\n Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Praesent eget erat libero, id malesuada tortor.\n Donec pharetra sapien et nulla ultricies ac pharetra neque vulputate."},{"slug":"mongodb","name":"mongodb","controls":[{"name":"Ports","label":"Ports","_id":"4c7fd205d489e8d081000009","_type":"Textbox","type":"Textbox","hint":"separate by comma's"}],"created_at":"2010-09-02T13:34:13-03:00","updated_at":"2010-09-02T13:34:13-03:00","_id":"4c7fd205d489e8d08100000c","validations":[{"body":"test -x ufw","_id":"4c7fd205d489e8d081000002","_type":"ExecutableExists","params":{"data":"ufw"},"description":"File ufw exists and is executable"}],"user_id":"4c7fd205d489e8d081000003","requirements":[{"body":"test -x apt-get","_id":"4c7fd205d489e8d081000001","_type":"ExecutableExists","params":{"data":"apt-get"},"description":"File apt-get exists and is executable"}],"executions":[{"body":" # \n # configure_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n apt-get update\n apt-get install -y ufw\n","_id":"4c7fd205d489e8d08100000a","description":"Install Ufw"},{"body":" # \n # install_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n ufw default deny\n <% ports.split(\",\").each do |port| %>\n ufw allow <%= port %> \n <% end %>\n","_id":"4c7fd205d489e8d08100000b","description":"Configure Ufw"}],"description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n In et erat sem, at pellentesque augue. Aliquam tincidunt viverra mauris,\n non auctor nibh pretium ac. Phasellus dapibus interdum rutrum. Nunc eget\n felis sed nulla porta elementum. Proin nec tortor sit amet turpis scelerisque ullamcorper."},{"slug":"memcached","name":"memcached","controls":[{"name":"Ports","label":"Ports","_id":"4c7fd205d489e8d08100000d","_type":"Textbox","type":"Textbox","hint":"separate by comma's"}],"created_at":"2010-09-02T13:34:13-03:00","updated_at":"2010-09-02T13:34:13-03:00","_id":"4c7fd205d489e8d081000010","validations":[{"body":"test -x ufw","_id":"4c7fd205d489e8d081000002","_type":"ExecutableExists","params":{"data":"ufw"},"description":"File ufw exists and is executable"}],"user_id":"4c7fd205d489e8d081000003","requirements":[{"body":"test -x apt-get","_id":"4c7fd205d489e8d081000001","_type":"ExecutableExists","params":{"data":"apt-get"},"description":"File apt-get exists and is executable"}],"executions":[{"body":" # \n # configure_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n apt-get update\n apt-get install -y ufw\n","_id":"4c7fd205d489e8d08100000e","description":"Install Ufw"},{"body":" # \n # install_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n ufw default deny\n <% ports.split(\",\").each do |port| %>\n ufw allow <%= port %> \n <% end %>\n","_id":"4c7fd205d489e8d08100000f","description":"Configure Ufw"}],"description":"Etiam id odio lorem, vitae consectetur nunc. Nunc a libero vel\n tortor malesuada mattis. Integer euismod libero vel tellus tristique a dapibus\n neque consequat. Mauris metus tellus, pellentesque hendrerit aliquam vitae,\n tristique at quam. Cras posuere imperdiet nunc in semper."},{"slug":"mysql","name":"mysql","controls":[{"name":"Ports","label":"Ports","_id":"4c7fd205d489e8d081000011","_type":"Textbox","type":"Textbox","hint":"separate by comma's"}],"created_at":"2010-09-02T13:34:13-03:00","updated_at":"2010-09-02T13:34:13-03:00","_id":"4c7fd205d489e8d081000014","validations":[{"body":"test -x ufw","_id":"4c7fd205d489e8d081000002","_type":"ExecutableExists","params":{"data":"ufw"},"description":"File ufw exists and is executable"}],"user_id":"4c7fd205d489e8d081000004","requirements":[{"body":"test -x apt-get","_id":"4c7fd205d489e8d081000001","_type":"ExecutableExists","params":{"data":"apt-get"},"description":"File apt-get exists and is executable"}],"executions":[{"body":" # \n # configure_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n apt-get update\n apt-get install -y ufw\n","_id":"4c7fd205d489e8d081000012","description":"Install Ufw"},{"body":" # \n # install_ufw.sh\n # Tue Dec 01 15:06:48 -0200 2009\n #\n\n ufw default deny\n <% ports.split(\",\").each do |port| %>\n ufw allow <%= port %> \n <% end %>\n","_id":"4c7fd205d489e8d081000013","description":"Configure Ufw"}],"description":"Duis nulla ipsum, malesuada nec facilisis vel, blandit ac ante.\n Nullam sollicitudin tortor sit amet enim tincidunt consequat accumsan ipsum vestibulum.\n Sed et congue enim. Morbi venenatis orci quis nisi vulputate lacinia. Morbi pharetra\n adipiscing velit nec cursus. Vivamus consequat nunc nec diam vulputate euismod. Proin\n sit amet nunc tortor. Nam non mi eu velit pulvinar fermentum aliquam vitae felis."}]
|
@@ -0,0 +1,10 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Connection: Keep-Alive
|
3
|
+
Content-Type: application/json; charset=utf-8
|
4
|
+
Date: Mon, 06 Sep 2010 16:03:21 GMT
|
5
|
+
Server: WEBrick/1.3.1 (Ruby/1.8.7/2009-12-24)
|
6
|
+
X-Runtime: 0.405559
|
7
|
+
Content-Length: 1
|
8
|
+
Cache-Control: no-cache
|
9
|
+
Set-Cookie: _stackfu_session=BAh7ByIPc2Vzc2lvbl9pZCIlNjkwZDkzNDkwYjBjYmE2NWU3NWNhOTI5NmYwYTM3NjQiGXdhcmRlbi51c2VyLnVzZXIua2V5WwciCVVzZXJvOhNCU09OOjpPYmplY3RJRAY6CkBkYXRhWxFpUWkBg2kB%2FmkkaQHUaQGJaQHoaQGTaQGPaQBpAGkJ--823a781c29cb181fa20038701dd325f47e6e457d; path=/; HttpOnly
|
10
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Connection: Keep-Alive
|
3
|
+
Content-Type: application/json; charset=utf-8
|
4
|
+
Date: Mon, 06 Sep 2010 16:03:21 GMT
|
5
|
+
Server: WEBrick/1.3.1 (Ruby/1.8.7/2009-12-24)
|
6
|
+
X-Runtime: 0.405559
|
7
|
+
Content-Length: 1
|
8
|
+
Cache-Control: no-cache
|
9
|
+
Set-Cookie: _stackfu_session=BAh7ByIPc2Vzc2lvbl9pZCIlNjkwZDkzNDkwYjBjYmE2NWU3NWNhOTI5NmYwYTM3NjQiGXdhcmRlbi51c2VyLnVzZXIua2V5WwciCVVzZXJvOhNCU09OOjpPYmplY3RJRAY6CkBkYXRhWxFpUWkBg2kB%2FmkkaQHUaQGJaQHoaQGTaQGPaQBpAGkJ--823a781c29cb181fa20038701dd325f47e6e457d; path=/; HttpOnly
|
10
|
+
|