engineyard 0.2.7

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 (52) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +7 -0
  3. data/bin/ey +14 -0
  4. data/lib/engineyard.rb +44 -0
  5. data/lib/engineyard/account.rb +78 -0
  6. data/lib/engineyard/api.rb +104 -0
  7. data/lib/engineyard/cli.rb +169 -0
  8. data/lib/engineyard/cli/api.rb +42 -0
  9. data/lib/engineyard/cli/error.rb +44 -0
  10. data/lib/engineyard/cli/ui.rb +96 -0
  11. data/lib/engineyard/config.rb +86 -0
  12. data/lib/engineyard/repo.rb +24 -0
  13. data/lib/vendor/thor.rb +244 -0
  14. data/lib/vendor/thor/actions.rb +275 -0
  15. data/lib/vendor/thor/actions/create_file.rb +103 -0
  16. data/lib/vendor/thor/actions/directory.rb +91 -0
  17. data/lib/vendor/thor/actions/empty_directory.rb +134 -0
  18. data/lib/vendor/thor/actions/file_manipulation.rb +223 -0
  19. data/lib/vendor/thor/actions/inject_into_file.rb +104 -0
  20. data/lib/vendor/thor/base.rb +540 -0
  21. data/lib/vendor/thor/core_ext/file_binary_read.rb +9 -0
  22. data/lib/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  23. data/lib/vendor/thor/core_ext/ordered_hash.rb +100 -0
  24. data/lib/vendor/thor/error.rb +30 -0
  25. data/lib/vendor/thor/group.rb +271 -0
  26. data/lib/vendor/thor/invocation.rb +180 -0
  27. data/lib/vendor/thor/parser.rb +4 -0
  28. data/lib/vendor/thor/parser/argument.rb +67 -0
  29. data/lib/vendor/thor/parser/arguments.rb +150 -0
  30. data/lib/vendor/thor/parser/option.rb +128 -0
  31. data/lib/vendor/thor/parser/options.rb +169 -0
  32. data/lib/vendor/thor/rake_compat.rb +66 -0
  33. data/lib/vendor/thor/runner.rb +314 -0
  34. data/lib/vendor/thor/shell.rb +83 -0
  35. data/lib/vendor/thor/shell/basic.rb +239 -0
  36. data/lib/vendor/thor/shell/color.rb +108 -0
  37. data/lib/vendor/thor/task.rb +102 -0
  38. data/lib/vendor/thor/util.rb +230 -0
  39. data/lib/vendor/thor/version.rb +3 -0
  40. data/spec/engineyard/api_spec.rb +56 -0
  41. data/spec/engineyard/cli/api_spec.rb +44 -0
  42. data/spec/engineyard/cli_spec.rb +20 -0
  43. data/spec/engineyard/config_spec.rb +57 -0
  44. data/spec/engineyard/repo_spec.rb +52 -0
  45. data/spec/engineyard_spec.rb +7 -0
  46. data/spec/ey/deploy_spec.rb +65 -0
  47. data/spec/ey/ey_spec.rb +16 -0
  48. data/spec/spec.opts +2 -0
  49. data/spec/spec_helper.rb +40 -0
  50. data/spec/support/bundled_ey +10 -0
  51. data/spec/support/helpers.rb +46 -0
  52. metadata +231 -0
@@ -0,0 +1,44 @@
1
+ module EY
2
+ class CLI < Thor
3
+ class NoAppError < EY::Error
4
+ def initialize(repo)
5
+ @repo = repo
6
+ end
7
+
8
+ def message
9
+ error = [%|There is no application configured for any of the following remotes:|]
10
+ @repo.urls.each{|url| error << %|\t#{url}| }
11
+ error << %|You can add this application at #{EY.config.endpoint}|
12
+ error.join("\n")
13
+ end
14
+ end
15
+
16
+ class EnvironmentError < EY::Error
17
+ end
18
+
19
+ class NoEnvironmentError < EnvironmentError
20
+ def message
21
+ "No environment named '#{env_name}'\nYou can create one at #{EY.config.endpoint}"
22
+ end
23
+ end
24
+
25
+ class BranchMismatch < EY::Error
26
+ def initialize(default_branch, branch)
27
+ super(nil)
28
+ @default_branch, @branch = default_branch, branch
29
+ end
30
+
31
+ def message
32
+ %|Your deploy branch is set to "#{@default_branch}".\n| +
33
+ %|If you want to deploy branch "#{@branch}", use --force.|
34
+ end
35
+ end
36
+
37
+ class DeployArgumentError < EY::Error
38
+ def message
39
+ %|"deploy" was called incorrectly. Call as "deploy [ENVIRONMENT] [BRANCH]"\n| +
40
+ %|You can set default environments and branches in ey.yml|
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,96 @@
1
+ module EY
2
+ class CLI
3
+ class UI < Thor::Base.shell
4
+
5
+ def error(name, message = nil)
6
+ begin
7
+ orig_out, $stdout = $stdout, $stderr
8
+ if message
9
+ say_status name, message, :red
10
+ elsif name
11
+ say name, :red
12
+ end
13
+ ensure
14
+ $stdout = orig_out
15
+ end
16
+ end
17
+
18
+ def warn(name, message = nil)
19
+ if message
20
+ say_status name, message, :yellow
21
+ elsif name
22
+ say name, :yellow
23
+ end
24
+ end
25
+
26
+ def info(name, message = nil)
27
+ if message
28
+ say_status name, message, :green
29
+ elsif name
30
+ say name, :green
31
+ end
32
+ end
33
+
34
+ def debug(name, message = nil)
35
+ return unless ENV["DEBUG"]
36
+
37
+ if message
38
+ message = message.inspect unless message.is_a?(String)
39
+ say_status name, message, :blue
40
+ elsif name
41
+ name = name.inspect unless name.is_a?(String)
42
+ say name, :cyan
43
+ end
44
+ end
45
+
46
+ def ask(message, password = false)
47
+ begin
48
+ EY.library 'highline'
49
+ @hl ||= HighLine.new($stdin)
50
+ if not $stdin.tty?
51
+ @hl.ask(message)
52
+ elsif password
53
+ @hl.ask(message) {|q| q.echo = "*" }
54
+ else
55
+ @hl.ask(message) {|q| q.readline = true }
56
+ end
57
+ rescue EOFError
58
+ return ''
59
+ end
60
+ end
61
+
62
+ def print_envs(envs, default_env = nil)
63
+ printable_envs = envs.map do |e|
64
+ icount = e.instances_count
65
+ iname = (icount == 1) ? "instance" : "instances"
66
+
67
+ e.name << " (default)" if e.name == default_env
68
+ env = [e.name]
69
+ env << "#{icount} #{iname}"
70
+ env << e.apps.map{|a| a.name }.join(", ")
71
+ end
72
+ print_table(printable_envs, :ident => 2)
73
+ end
74
+
75
+ def print_exception(e)
76
+ if e.message.empty? || (e.message == e.class.to_s)
77
+ message = nil
78
+ else
79
+ message = e.message
80
+ end
81
+
82
+ if ENV["DEBUG"]
83
+ error(e.class, message)
84
+ e.backtrace.each{|l| say(" "*3 + l) }
85
+ else
86
+ error(message || e.class.to_s)
87
+ end
88
+ end
89
+
90
+ def set_color(string, color, bold=false)
91
+ $stdout.tty? ? super : string
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,86 @@
1
+ require 'uri'
2
+
3
+ module EY
4
+ class Config
5
+ CONFIG_FILES = ["config/ey.yml", "ey.yml"]
6
+
7
+ def initialize(file = nil)
8
+ require 'yaml'
9
+ @file = file || CONFIG_FILES.find{|f| File.exists?(f) }
10
+ @config = @file ? YAML.load_file(@file) : {}
11
+ @config.merge!("environments" => {}) unless @config["environments"]
12
+ end
13
+
14
+ def method_missing(meth, *args, &blk)
15
+ key = meth.to_s.downcase
16
+ if @config.key?(key)
17
+ @config[key]
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def respond_to?(meth)
24
+ key = meth.to_s.downcase
25
+ @config.key?(key) || super
26
+ end
27
+
28
+ def endpoint
29
+ @endpoint ||= env_var_endpoint ||
30
+ config_file_endpoint ||
31
+ default_endpoint
32
+ end
33
+
34
+ def config_file_endpoint
35
+ if endpoint = @config["endpoint"]
36
+ assert_valid_endpoint endpoint, @file
37
+ end
38
+ end
39
+
40
+ def env_var_endpoint
41
+ if endpoint = ENV["CLOUD_URL"]
42
+ assert_valid_endpoint endpoint, "CLOUD_URL"
43
+ end
44
+ end
45
+
46
+ def default_endpoint
47
+ URI.parse("https://cloud.engineyard.com/")
48
+ end
49
+
50
+ def default_endpoint?
51
+ default_endpoint == endpoint
52
+ end
53
+
54
+ def default_environment
55
+ d = environments.find do |name, env|
56
+ env["default"]
57
+ end
58
+ d && d.first
59
+ end
60
+
61
+ def default_branch(environment = default_environment)
62
+ env = environments[environment]
63
+ env && env["branch"]
64
+ end
65
+
66
+ private
67
+
68
+ def assert_valid_endpoint(endpoint, source)
69
+ endpoint = URI.parse(endpoint) if endpoint.is_a?(String)
70
+ return endpoint if endpoint.absolute?
71
+
72
+ raise ConfigurationError.new('endpoint', endpoint.to_s, source, "endpoint must be an absolute URI")
73
+ end
74
+
75
+ class ConfigurationError < EY::Error
76
+ def initialize(key, value, source, message=nil)
77
+ super(nil)
78
+ @key, @value, @source, @message = key, value, source, message
79
+ end
80
+
81
+ def message
82
+ %|"#{@key}" from #{@source} has invalid value: #{@value.inspect}#{": #{@message}" if @message}|
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,24 @@
1
+ module EY
2
+ class Repo
3
+
4
+ def initialize(path=File.expand_path('.'))
5
+ @path = path
6
+ end
7
+
8
+ def current_branch
9
+ head = File.read(File.join(@path, ".git/HEAD")).chomp
10
+ if head.gsub!("ref: refs/heads/", "")
11
+ head
12
+ else
13
+ nil
14
+ end
15
+ end
16
+
17
+ def urls
18
+ `git config -f #{@path}/.git/config --get-regexp 'remote.*.url'`.split(/\n/).map do |c|
19
+ c.split.last
20
+ end
21
+ end
22
+
23
+ end # Repo
24
+ end # EY
@@ -0,0 +1,244 @@
1
+ require 'thor/base'
2
+
3
+ # TODO: Update thor to allow for git-style CLI (git bisect run)
4
+ class Thor
5
+ class << self
6
+ # Sets the default task when thor is executed without an explicit task to be called.
7
+ #
8
+ # ==== Parameters
9
+ # meth<Symbol>:: name of the defaut task
10
+ #
11
+ def default_task(meth=nil)
12
+ case meth
13
+ when :none
14
+ @default_task = 'help'
15
+ when nil
16
+ @default_task ||= from_superclass(:default_task, 'help')
17
+ else
18
+ @default_task = meth.to_s
19
+ end
20
+ end
21
+
22
+ # Defines the usage and the description of the next task.
23
+ #
24
+ # ==== Parameters
25
+ # usage<String>
26
+ # description<String>
27
+ #
28
+ def desc(usage, description, options={})
29
+ if options[:for]
30
+ task = find_and_refresh_task(options[:for])
31
+ task.usage = usage if usage
32
+ task.description = description if description
33
+ else
34
+ @usage, @desc = usage, description
35
+ end
36
+ end
37
+
38
+ # Maps an input to a task. If you define:
39
+ #
40
+ # map "-T" => "list"
41
+ #
42
+ # Running:
43
+ #
44
+ # thor -T
45
+ #
46
+ # Will invoke the list task.
47
+ #
48
+ # ==== Parameters
49
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
50
+ #
51
+ def map(mappings=nil)
52
+ @map ||= from_superclass(:map, {})
53
+
54
+ if mappings
55
+ mappings.each do |key, value|
56
+ if key.respond_to?(:each)
57
+ key.each {|subkey| @map[subkey] = value}
58
+ else
59
+ @map[key] = value
60
+ end
61
+ end
62
+ end
63
+
64
+ @map
65
+ end
66
+
67
+ # Declares the options for the next task to be declared.
68
+ #
69
+ # ==== Parameters
70
+ # Hash[Symbol => Object]:: The hash key is the name of the option and the value
71
+ # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
72
+ # or :required (string). If you give a value, the type of the value is used.
73
+ #
74
+ def method_options(options=nil)
75
+ @method_options ||= {}
76
+ build_options(options, @method_options) if options
77
+ @method_options
78
+ end
79
+
80
+ # Adds an option to the set of method options. If :for is given as option,
81
+ # it allows you to change the options from a previous defined task.
82
+ #
83
+ # def previous_task
84
+ # # magic
85
+ # end
86
+ #
87
+ # method_option :foo => :bar, :for => :previous_task
88
+ #
89
+ # def next_task
90
+ # # magic
91
+ # end
92
+ #
93
+ # ==== Parameters
94
+ # name<Symbol>:: The name of the argument.
95
+ # options<Hash>:: Described below.
96
+ #
97
+ # ==== Options
98
+ # :desc - Description for the argument.
99
+ # :required - If the argument is required or not.
100
+ # :default - Default value for this argument. It cannot be required and have default values.
101
+ # :aliases - Aliases for this option.
102
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
103
+ # :banner - String to show on usage notes.
104
+ #
105
+ def method_option(name, options={})
106
+ scope = if options[:for]
107
+ find_and_refresh_task(options[:for]).options
108
+ else
109
+ method_options
110
+ end
111
+
112
+ build_option(name, options, scope)
113
+ end
114
+
115
+ # Parses the task and options from the given args, instantiate the class
116
+ # and invoke the task. This method is used when the arguments must be parsed
117
+ # from an array. If you are inside Ruby and want to use a Thor class, you
118
+ # can simply initialize it:
119
+ #
120
+ # script = MyScript.new(args, options, config)
121
+ # script.invoke(:task, first_arg, second_arg, third_arg)
122
+ #
123
+ def start(original_args=ARGV, config={})
124
+ super do |given_args|
125
+ meth = normalize_task_name(given_args.shift)
126
+ task = all_tasks[meth]
127
+
128
+ if task
129
+ args, opts = Thor::Options.split(given_args)
130
+ config.merge!(:task_options => task.options)
131
+ else
132
+ args, opts = given_args, {}
133
+ end
134
+
135
+ task ||= Thor::Task::Dynamic.new(meth)
136
+ trailing = args[Range.new(arguments.size, -1)]
137
+ new(args, opts, config).invoke(task, trailing || [])
138
+ end
139
+ end
140
+
141
+ # Prints help information for the given task.
142
+ #
143
+ # ==== Parameters
144
+ # shell<Thor::Shell>
145
+ # task_name<String>
146
+ #
147
+ def task_help(shell, task_name)
148
+ meth = normalize_task_name(task_name)
149
+ task = all_tasks[meth]
150
+ handle_no_task_error(meth) unless task
151
+
152
+ shell.say "Usage:"
153
+ shell.say " #{banner(task)}"
154
+ shell.say
155
+ class_options_help(shell, nil => task.options.map { |_, o| o })
156
+ shell.say task.description
157
+ end
158
+
159
+ # Prints help information for this class.
160
+ #
161
+ # ==== Parameters
162
+ # shell<Thor::Shell>
163
+ #
164
+ def help(shell)
165
+ list = printable_tasks
166
+ Thor::Util.thor_classes_in(self).each do |klass|
167
+ list += klass.printable_tasks(false)
168
+ end
169
+ list.sort!{ |a,b| a[0] <=> b[0] }
170
+
171
+ shell.say "Tasks:"
172
+ shell.print_table(list, :ident => 2, :truncate => true)
173
+ shell.say
174
+ class_options_help(shell)
175
+ end
176
+
177
+ # Returns tasks ready to be printed.
178
+ def printable_tasks(all=true)
179
+ (all ? all_tasks : tasks).map do |_, task|
180
+ item = []
181
+ item << banner(task)
182
+ item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
183
+ item
184
+ end
185
+ end
186
+
187
+ def handle_argument_error(task, error) #:nodoc:
188
+ raise InvocationError, "#{task.name.inspect} was called incorrectly. Call as #{task.formatted_usage(self, banner_base == "thor").inspect}."
189
+ end
190
+
191
+ protected
192
+
193
+ # The banner for this class. You can customize it if you are invoking the
194
+ # thor class by another ways which is not the Thor::Runner. It receives
195
+ # the task that is going to be invoked and a boolean which indicates if
196
+ # the namespace should be displayed as arguments.
197
+ #
198
+ def banner(task)
199
+ "#{banner_base} #{task.formatted_usage(self, banner_base == "thor")}"
200
+ end
201
+
202
+ def baseclass #:nodoc:
203
+ Thor
204
+ end
205
+
206
+ def create_task(meth) #:nodoc:
207
+ if @usage && @desc
208
+ tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
209
+ @usage, @desc, @method_options = nil
210
+ true
211
+ elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
212
+ true
213
+ else
214
+ puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
215
+ "Call desc if you want this method to be available as task or declare it inside a " <<
216
+ "no_tasks{} block. Invoked from #{caller[1].inspect}."
217
+ false
218
+ end
219
+ end
220
+
221
+ def initialize_added #:nodoc:
222
+ class_options.merge!(method_options)
223
+ @method_options = nil
224
+ end
225
+
226
+ # Receives a task name (can be nil), and try to get a map from it.
227
+ # If a map can't be found use the sent name or the default task.
228
+ #
229
+ def normalize_task_name(meth) #:nodoc:
230
+ mapping = map[meth.to_s]
231
+ meth = mapping || meth || default_task
232
+ meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
233
+ end
234
+ end
235
+
236
+ include Thor::Base
237
+
238
+ map HELP_MAPPINGS => :help
239
+
240
+ desc "help [TASK]", "Describe available tasks or one specific task"
241
+ def help(task=nil)
242
+ task ? self.class.task_help(shell, task) : self.class.help(shell)
243
+ end
244
+ end