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.
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