stackfu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/CHANGELOG +24 -0
  2. data/Manifest +118 -0
  3. data/README +0 -0
  4. data/README.md +218 -0
  5. data/Rakefile +73 -0
  6. data/bin/stackfu +10 -0
  7. data/lib/stackfu/api_hooks.rb +17 -0
  8. data/lib/stackfu/app.rb +69 -0
  9. data/lib/stackfu/commands/command.rb +127 -0
  10. data/lib/stackfu/commands/config_command.rb +43 -0
  11. data/lib/stackfu/commands/deploy_command.rb +144 -0
  12. data/lib/stackfu/commands/dump_command.rb +101 -0
  13. data/lib/stackfu/commands/generate_command.rb +107 -0
  14. data/lib/stackfu/commands/help_command.rb +28 -0
  15. data/lib/stackfu/commands/list_command.rb +26 -0
  16. data/lib/stackfu/commands/publish_command.rb +108 -0
  17. data/lib/stackfu/commands/server_command.rb +124 -0
  18. data/lib/stackfu/helpers/providers_credentials.rb +82 -0
  19. data/lib/stackfu/helpers/rendering.rb +201 -0
  20. data/lib/stackfu/operating_systems.rb +30 -0
  21. data/lib/stackfu.rb +88 -0
  22. data/stackfu-installer/config/01-controls.yml +17 -0
  23. data/stackfu-installer/config/02-requirements.yml +3 -0
  24. data/stackfu-installer/config/03-scripts.yml +16 -0
  25. data/stackfu-installer/config/04-validations.yml +3 -0
  26. data/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
  27. data/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
  28. data/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
  29. data/stackfu-installer/script/redis_installation.sh.erb +33 -0
  30. data/stackfu-installer/script/resque_installation.sh.erb +7 -0
  31. data/stackfu-installer/script/ruby_environment.sh.erb +20 -0
  32. data/stackfu-installer/script/stackfu.sh.erb +114 -0
  33. data/stackfu-installer/stack.yml +5 -0
  34. data/stackfu.gemspec +60 -0
  35. data/templates/01-controls.yml.erb +31 -0
  36. data/templates/02-requirements.yml.erb +26 -0
  37. data/templates/03-scripts.yml.erb +34 -0
  38. data/templates/04-validations.yml.erb +17 -0
  39. data/templates/script.sh.erb +7 -0
  40. data/templates/stack.yml.erb +17 -0
  41. data/test/fixtures/add_server_error +7 -0
  42. data/test/fixtures/deployment_add +7 -0
  43. data/test/fixtures/deployment_add_error +8 -0
  44. data/test/fixtures/deployments +7 -0
  45. data/test/fixtures/logs +7 -0
  46. data/test/fixtures/logs_partial +7 -0
  47. data/test/fixtures/plugin_add +6 -0
  48. data/test/fixtures/plugin_add_error +8 -0
  49. data/test/fixtures/plugin_deployment_add +7 -0
  50. data/test/fixtures/plugin_not_found +7 -0
  51. data/test/fixtures/plugin_unauthorized +8 -0
  52. data/test/fixtures/plugins +7 -0
  53. data/test/fixtures/plugins_by_name +7 -0
  54. data/test/fixtures/plugins_by_name_other +7 -0
  55. data/test/fixtures/plugins_empty +7 -0
  56. data/test/fixtures/plugins_multiple +7 -0
  57. data/test/fixtures/providers +7 -0
  58. data/test/fixtures/providers_servers +7 -0
  59. data/test/fixtures/server_add +6 -0
  60. data/test/fixtures/server_add_dupe +7 -0
  61. data/test/fixtures/server_add_error +7 -0
  62. data/test/fixtures/server_delete +7 -0
  63. data/test/fixtures/server_delete_error +7 -0
  64. data/test/fixtures/servers +7 -0
  65. data/test/fixtures/servers_by_name +7 -0
  66. data/test/fixtures/servers_empty +8 -0
  67. data/test/fixtures/servers_not_found +26 -0
  68. data/test/fixtures/servers_unauthorized +8 -0
  69. data/test/fixtures/servers_webbynode +7 -0
  70. data/test/fixtures/stack/stackfu-installer/config/01-controls.yml +22 -0
  71. data/test/fixtures/stack/stackfu-installer/config/02-requirements.yml +1 -0
  72. data/test/fixtures/stack/stackfu-installer/config/03-scripts.yml +23 -0
  73. data/test/fixtures/stack/stackfu-installer/config/04-validations.yml +1 -0
  74. data/test/fixtures/stack/stackfu-installer/script/dotfiles_installation.sh.erb +22 -0
  75. data/test/fixtures/stack/stackfu-installer/script/github_credentials_setup.sh.erb +31 -0
  76. data/test/fixtures/stack/stackfu-installer/script/nginx_and_passenger.sh.erb +83 -0
  77. data/test/fixtures/stack/stackfu-installer/script/redis_installation.sh.erb +33 -0
  78. data/test/fixtures/stack/stackfu-installer/script/resque_installation.sh.erb +7 -0
  79. data/test/fixtures/stack/stackfu-installer/script/ruby_environment.sh.erb +20 -0
  80. data/test/fixtures/stack/stackfu-installer/script/stackfu.sh.erb +76 -0
  81. data/test/fixtures/stack/stackfu-installer/stack.yml +5 -0
  82. data/test/fixtures/stack_add +6 -0
  83. data/test/fixtures/stack_add_error +8 -0
  84. data/test/fixtures/stack_add_error_dupe +8 -0
  85. data/test/fixtures/stack_adds_by_name +7 -0
  86. data/test/fixtures/stack_delete_not_found +7 -0
  87. data/test/fixtures/stacks +7 -0
  88. data/test/fixtures/stacks_by_name +7 -0
  89. data/test/fixtures/stacks_by_name_other +7 -0
  90. data/test/fixtures/stacks_empty +7 -0
  91. data/test/fixtures/stacks_multiple +7 -0
  92. data/test/fixtures/stacks_not_found +7 -0
  93. data/test/fixtures/stacks_realworld +7 -0
  94. data/test/fixtures/stacks_stackfu-installer +7 -0
  95. data/test/fixtures/stacks_unauthorized +8 -0
  96. data/test/fixtures/stacks_with_controls +0 -0
  97. data/test/fixtures/users +7 -0
  98. data/test/fixtures/users_no_credentials +7 -0
  99. data/test/fixtures/users_update +6 -0
  100. data/test/stack.yml +26 -0
  101. data/test/support/custom_matchers.rb +69 -0
  102. data/test/support/fixtures.rb +98 -0
  103. data/test/support/io_stub.rb +10 -0
  104. data/test/support/web_fixtures.rb +91 -0
  105. data/test/test_helper.rb +186 -0
  106. data/test/unit/commands/test_command.rb +112 -0
  107. data/test/unit/commands/test_config_command.rb +92 -0
  108. data/test/unit/commands/test_deploy_command.rb +303 -0
  109. data/test/unit/commands/test_dump_command.rb +155 -0
  110. data/test/unit/commands/test_generate_command.rb +112 -0
  111. data/test/unit/commands/test_help_command.rb +33 -0
  112. data/test/unit/commands/test_list_command.rb +63 -0
  113. data/test/unit/commands/test_publish_command.rb +265 -0
  114. data/test/unit/commands/test_server_command.rb +259 -0
  115. data/test/unit/helpers/test_rendering.rb +141 -0
  116. data/test/unit/test_array.rb +26 -0
  117. data/test/unit/test_provider.rb +14 -0
  118. data/test/unit/test_stackfu.rb +27 -0
  119. 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