engineyard 1.4.29 → 1.7.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/README.rdoc +139 -4
  2. data/bin/ey +1 -7
  3. data/lib/engineyard.rb +1 -22
  4. data/lib/engineyard/cli.rb +192 -94
  5. data/lib/engineyard/cli/#recipes.rb# +32 -0
  6. data/lib/engineyard/cli/api.rb +42 -28
  7. data/lib/engineyard/cli/recipes.rb +13 -6
  8. data/lib/engineyard/cli/ui.rb +103 -42
  9. data/lib/engineyard/cli/web.rb +16 -10
  10. data/lib/engineyard/config.rb +92 -18
  11. data/lib/engineyard/deploy_config.rb +66 -0
  12. data/lib/engineyard/deploy_config/migrate.rb +125 -0
  13. data/lib/engineyard/deploy_config/ref.rb +56 -0
  14. data/lib/engineyard/error.rb +38 -78
  15. data/lib/engineyard/repo.rb +75 -27
  16. data/lib/engineyard/serverside_runner.rb +133 -0
  17. data/lib/engineyard/thor.rb +110 -18
  18. data/lib/engineyard/version.rb +1 -1
  19. data/spec/engineyard/cli/api_spec.rb +10 -16
  20. data/spec/engineyard/cli_spec.rb +0 -11
  21. data/spec/engineyard/config_spec.rb +1 -8
  22. data/spec/engineyard/deploy_config_spec.rb +203 -0
  23. data/spec/engineyard/eyrc_spec.rb +2 -0
  24. data/spec/engineyard/repo_spec.rb +57 -34
  25. data/spec/ey/deploy_spec.rb +102 -52
  26. data/spec/ey/list_environments_spec.rb +69 -14
  27. data/spec/ey/login_spec.rb +11 -7
  28. data/spec/ey/logout_spec.rb +4 -4
  29. data/spec/ey/logs_spec.rb +6 -6
  30. data/spec/ey/recipes/apply_spec.rb +1 -1
  31. data/spec/ey/recipes/download_spec.rb +1 -1
  32. data/spec/ey/recipes/upload_spec.rb +6 -6
  33. data/spec/ey/rollback_spec.rb +3 -3
  34. data/spec/ey/ssh_spec.rb +9 -9
  35. data/spec/ey/status_spec.rb +2 -2
  36. data/spec/ey/whoami_spec.rb +9 -8
  37. data/spec/spec_helper.rb +18 -15
  38. data/spec/support/{fake_awsm.rb → git_repos.rb} +0 -14
  39. data/spec/support/helpers.rb +84 -28
  40. data/spec/support/matchers.rb +0 -16
  41. data/spec/support/shared_behavior.rb +83 -103
  42. metadata +65 -51
  43. data/lib/engineyard/api.rb +0 -117
  44. data/lib/engineyard/collection.rb +0 -7
  45. data/lib/engineyard/collection/abstract.rb +0 -71
  46. data/lib/engineyard/collection/apps.rb +0 -8
  47. data/lib/engineyard/collection/environments.rb +0 -8
  48. data/lib/engineyard/model.rb +0 -12
  49. data/lib/engineyard/model/account.rb +0 -8
  50. data/lib/engineyard/model/api_struct.rb +0 -33
  51. data/lib/engineyard/model/app.rb +0 -32
  52. data/lib/engineyard/model/deployment.rb +0 -90
  53. data/lib/engineyard/model/environment.rb +0 -194
  54. data/lib/engineyard/model/instance.rb +0 -166
  55. data/lib/engineyard/model/log.rb +0 -9
  56. data/lib/engineyard/model/user.rb +0 -6
  57. data/lib/engineyard/resolver.rb +0 -134
  58. data/lib/engineyard/rest_client_ext.rb +0 -9
  59. data/lib/engineyard/ruby_ext.rb +0 -9
  60. data/spec/engineyard/api_spec.rb +0 -39
  61. data/spec/engineyard/collection/apps_spec.rb +0 -16
  62. data/spec/engineyard/collection/environments_spec.rb +0 -16
  63. data/spec/engineyard/model/api_struct_spec.rb +0 -41
  64. data/spec/engineyard/model/environment_spec.rb +0 -198
  65. data/spec/engineyard/model/instance_spec.rb +0 -27
  66. data/spec/engineyard/resolver_spec.rb +0 -112
  67. data/spec/support/fake_awsm.ru +0 -245
  68. data/spec/support/scenarios.rb +0 -417
@@ -1,8 +0,0 @@
1
- require 'escape'
2
-
3
- module EY
4
- module Model
5
- class Account < ApiStruct.new(:id, :name)
6
- end
7
- end
8
- end
@@ -1,33 +0,0 @@
1
- module EY
2
- module Model
3
- class ApiStruct < Struct
4
-
5
- def self.new(*args, &block)
6
- super(*args) do |*block_args|
7
- block.call(*block_args) if block
8
-
9
- def self.from_array(array, common_values = {})
10
- array.map do |values|
11
- from_hash(values.merge(common_values))
12
- end if array
13
- end
14
-
15
- def self.from_hash(hash)
16
- return nil unless hash
17
- # in ruby 1.8, #members is an array of strings
18
- # in ruby 1.9, #members is an array of symbols
19
- members = new.members.map {|m| m.to_sym}
20
- values = members.map{|a| hash.has_key?(a) ? hash[a] : hash[a.to_s] }
21
- new(*values)
22
- end
23
-
24
- end
25
- end
26
-
27
- def api_get(uri, options = {})
28
- api.request(uri, options.merge(:method => :get))
29
- end
30
-
31
- end
32
- end
33
- end
@@ -1,32 +0,0 @@
1
- module EY
2
- module Model
3
- class App < ApiStruct.new(:id, :account, :name, :repository_uri, :environments, :api)
4
-
5
- def self.from_hash(hash)
6
- super.tap do |app|
7
- app.environments = Environment.from_array(app.environments, :api => app.api)
8
- app.account = Account.from_hash(app.account)
9
- end
10
- end
11
-
12
- def self.from_array(*)
13
- Collection::Apps.new(super)
14
- end
15
-
16
- def sole_environment
17
- if environments.size == 1
18
- environments.first
19
- end
20
- end
21
-
22
- def sole_environment!
23
- sole_environment or raise NoSingleEnvironmentError.new(self)
24
- end
25
-
26
- def last_deployment_on(environment)
27
- Deployment.last(self, environment, api)
28
- end
29
-
30
- end
31
- end
32
- end
@@ -1,90 +0,0 @@
1
- require 'escape'
2
-
3
- module EY
4
- module Model
5
- class Deployment < ApiStruct.new(:id, :app, :created_at, :commit, :environment, :finished_at, :migrate_command, :output, :ref, :resolved_ref, :successful, :user_name)
6
- def self.api_root(app_id, environment_id)
7
- "/apps/#{app_id}/environments/#{environment_id}/deployments"
8
- end
9
-
10
- def self.last(app, environment, api)
11
- get(app, environment, 'last', api)
12
- end
13
-
14
- def self.get(app, environment, id, api)
15
- response = api.request(api_root(app.id, environment.id) + "/#{id}", :method => :get)
16
- load_from_response app, environment, response
17
- rescue EY::API::ResourceNotFound
18
- nil
19
- end
20
-
21
- def self.load_from_response(app, environment, response)
22
- dep = new
23
- dep.app = app
24
- dep.environment = environment
25
- dep.update_with_response(response)
26
- dep
27
- end
28
-
29
- def self.started(environment, app, ref, migrate_command)
30
- deployment = from_hash({
31
- :app => app,
32
- :environment => environment,
33
- :migrate_command => migrate_command,
34
- :ref => ref,
35
- })
36
- deployment.start
37
- deployment
38
- end
39
-
40
- def migrate
41
- !!migrate_command
42
- end
43
-
44
- alias successful? successful
45
-
46
- def start
47
- post_to_api({
48
- :migrate => migrate,
49
- :migrate_command => migrate_command,
50
- :output => output,
51
- :ref => ref,
52
- })
53
- end
54
-
55
- def finished(successful, output)
56
- self.successful = successful
57
- self.output = output
58
- put_to_api({:successful => successful, :output => output})
59
- end
60
-
61
- def update_with_response(response)
62
- response['deployment'].each do |key,val|
63
- send("#{key}=", val) if respond_to?("#{key}=")
64
- end
65
- end
66
-
67
- private
68
-
69
- def post_to_api(params)
70
- update_with_response api.request(collection_uri, :method => :post, :params => {:deployment => params})
71
- end
72
-
73
- def put_to_api(params)
74
- update_with_response api.request(member_uri("/finished"), :method => :put, :params => {:deployment => params})
75
- end
76
-
77
- def collection_uri
78
- self.class.api_root(app.id, environment.id)
79
- end
80
-
81
- def member_uri(path = nil)
82
- collection_uri + "/#{id}#{path}"
83
- end
84
-
85
- def api
86
- app.api
87
- end
88
- end
89
- end
90
- end
@@ -1,194 +0,0 @@
1
- module EY
2
- module Model
3
- class Environment < ApiStruct.new(:id, :account, :name, :framework_env, :instances, :instances_count,
4
- :apps, :app_master, :username, :app_server_stack_name, :deployment_configurations,
5
- :load_balancer_ip_address, :api)
6
- require 'launchy'
7
-
8
- attr_accessor :ignore_bad_master
9
-
10
- def self.from_hash(hash)
11
- super.tap do |env|
12
- env.username = hash['ssh_username']
13
- env.apps = App.from_array(env.apps, :api => env.api)
14
- env.account = Account.from_hash(env.account)
15
- env.instances = Instance.from_array(hash['instances'], :environment => env)
16
- env.app_master = Instance.from_hash(env.app_master.merge(:environment => env)) if env.app_master
17
- end
18
- end
19
-
20
- def self.from_array(*)
21
- Collection::Environments.new(super)
22
- end
23
-
24
- def logs
25
- Log.from_array(api_get("/environments/#{id}/logs")["logs"])
26
- end
27
-
28
- def app_master!
29
- master = app_master
30
- if master.nil?
31
- raise NoAppMasterError.new(name)
32
- elsif !ignore_bad_master && master.status != "running"
33
- raise BadAppMasterStatusError.new(master.status)
34
- end
35
- master
36
- end
37
-
38
- def deploy(app, ref, deploy_options={})
39
- app_master!.deploy(app,
40
- ref,
41
- migration_command(app, deploy_options),
42
- config.merge(deploy_options['extras']),
43
- deploy_options['verbose'])
44
- end
45
-
46
- def rollback(app, extra_deploy_hook_options={}, verbose=false)
47
- app_master!.rollback(app,
48
- config.merge(extra_deploy_hook_options),
49
- verbose)
50
- end
51
-
52
- def take_down_maintenance_page(app, verbose=false)
53
- app_master!.take_down_maintenance_page(app, verbose)
54
- end
55
-
56
- def put_up_maintenance_page(app, verbose=false)
57
- app_master!.put_up_maintenance_page(app, verbose)
58
- end
59
-
60
- def rebuild
61
- api.request("/environments/#{id}/update_instances", :method => :put)
62
- end
63
-
64
- def run_custom_recipes
65
- api.request("/environments/#{id}/run_custom_recipes", :method => :put)
66
- end
67
-
68
- def download_recipes
69
- if File.exist?('cookbooks')
70
- raise EY::Error, "Could not download, cookbooks already exists"
71
- end
72
-
73
- require 'tempfile'
74
- tmp = Tempfile.new("recipes")
75
- tmp.write(api.request("/environments/#{id}/recipes"))
76
- tmp.flush
77
- tmp.close
78
-
79
- cmd = "tar xzf '#{tmp.path}' cookbooks"
80
-
81
- unless system(cmd)
82
- raise EY::Error, "Could not unarchive recipes.\nCommand `#{cmd}` exited with an error."
83
- end
84
- end
85
-
86
- def upload_recipes_at_path(recipes_path)
87
- recipes_path = Pathname.new(recipes_path)
88
- if recipes_path.exist?
89
- upload_recipes recipes_path.open('rb')
90
- else
91
- raise EY::Error, "Recipes file not found: #{recipes_path}"
92
- end
93
- end
94
-
95
- def tar_and_upload_recipes_in_cookbooks_dir
96
- require 'tempfile'
97
- unless File.exist?("cookbooks")
98
- raise EY::Error, "Could not find chef recipes. Please run from the root of your recipes repo."
99
- end
100
-
101
- recipes_file = Tempfile.new("recipes")
102
- cmd = "tar czf '#{recipes_file.path}' cookbooks/"
103
-
104
- unless system(cmd)
105
- raise EY::Error, "Could not archive recipes.\nCommand `#{cmd}` exited with an error."
106
- end
107
-
108
- upload_recipes(recipes_file)
109
- end
110
-
111
- def upload_recipes(file_to_upload)
112
- api.request("/environments/#{id}/recipes", {
113
- :method => :post,
114
- :params => {:file => file_to_upload}
115
- })
116
- end
117
-
118
- # If force_ref is a string, use it as the ref, otherwise use it as a boolean.
119
- def resolve_branch(ref, force_ref=false)
120
- if String === force_ref
121
- ref, force_ref = force_ref, true
122
- end
123
-
124
- if !ref
125
- default_branch
126
- elsif force_ref || !default_branch || ref == default_branch
127
- ref
128
- else
129
- raise BranchMismatchError.new(default_branch, ref)
130
- end
131
- end
132
-
133
- def configuration
134
- EY.config.environments[self.name] || {}
135
- end
136
- alias_method :config, :configuration
137
-
138
- def default_branch
139
- EY.config.default_branch(name)
140
- end
141
-
142
- def shorten_name_for(app)
143
- name.gsub(/^#{Regexp.quote(app.name)}_/, '')
144
- end
145
-
146
- def launch
147
- Launchy.open(app_master!.hostname_url)
148
- end
149
-
150
- def migration_command(app, deploy_options)
151
- # regarding deploy_options['migrate']:
152
- #
153
- # missing means migrate how the yaml file says to
154
- # nil means don't migrate
155
- # true means migrate w/custom command (if present) or default
156
- # a string means migrate with this specific command
157
- return nil if no_migrate?(deploy_options)
158
- command = migration_command_from_command_line(deploy_options)
159
- unless command
160
- return nil if no_migrate?(config)
161
- command = migration_command_from_config
162
- end
163
- command = migration_command_from_environment(app) unless command
164
- command
165
- end
166
-
167
- private
168
-
169
- def no_migrate?(hash)
170
- hash.key?('migrate') && hash['migrate'] == false
171
- end
172
-
173
- def migration_command_from_config
174
- config['migration_command'] if config['migrate'] || config['migration_command']
175
- end
176
-
177
- def migration_command_from_command_line(deploy_options)
178
- if migrate = deploy_options['migrate']
179
- migrate.respond_to?(:to_str) ? migrate.to_str : (config['migration_command'] || default_migration_command)
180
- end
181
- end
182
-
183
- def migration_command_from_environment(app)
184
- if deploy_config = deployment_configurations[app.name]
185
- deploy_config['migrate']['command'] if deploy_config['migrate']['perform']
186
- end
187
- end
188
-
189
- def default_migration_command
190
- 'rake db:migrate'
191
- end
192
- end
193
- end
194
- end
@@ -1,166 +0,0 @@
1
- require 'escape'
2
- require 'net/ssh'
3
-
4
- module EY
5
- module Model
6
- class Instance < ApiStruct.new(:id, :role, :name, :status, :amazon_id, :public_hostname, :environment)
7
- alias :hostname :public_hostname
8
-
9
- def adapter(app, verbose)
10
- require 'engineyard-serverside-adapter'
11
- EY::Serverside::Adapter.new("/usr/local/ey_resin/ruby/bin") do |args|
12
- args.app = app.name
13
- args.repo = app.repository_uri
14
- args.instances = instances_data
15
- args.verbose = verbose || ENV['DEBUG']
16
- args.stack = environment.app_server_stack_name
17
- args.framework_env = environment.framework_env
18
- end
19
- end
20
- private :adapter
21
-
22
- def deploy(app, ref, migration_command=nil, extra_configuration=nil, verbose=false)
23
- successful, output = false, "Deploy initiated.\n"
24
- deployment = Deployment.started(environment, app, ref, migration_command)
25
-
26
- deploy_command = adapter(app, verbose).deploy do |args|
27
- args.config = extra_configuration if extra_configuration
28
- args.migrate = migration_command if migration_command
29
- args.ref = deployment.resolved_ref
30
- end
31
-
32
- successful = invoke(deploy_command) { |chunk| output << chunk }
33
- rescue Interrupt
34
- output << "Interrupted. Deployment halted.\n"
35
- EY.ui.warn "Interrupted."
36
- EY.ui.warn "Recording canceled deployment and exiting..."
37
- EY.ui.warn "WARNING: Interrupting again may result in a never-finished deployment in the deployment history on EY Cloud."
38
- raise
39
- rescue StandardError => e
40
- EY.ui.info "Error encountered during deploy."
41
- output << "Error encountered during deploy.\n#{e.class} #{e}\n"
42
- raise
43
- ensure
44
- if deployment
45
- deployment.finished(successful, output)
46
- EY.ui.info "#{successful ? 'Successful' : 'Failed'} deployment recorded in EY Cloud"
47
- end
48
- end
49
-
50
- def rollback(app, extra_configuration=nil, verbose=false)
51
- rollback = adapter(app, verbose).rollback do |args|
52
- args.config = extra_configuration if extra_configuration
53
- end
54
- invoke rollback
55
- end
56
-
57
- def put_up_maintenance_page(app, verbose=false)
58
- invoke adapter(app, verbose).enable_maintenance_page
59
- end
60
-
61
- def take_down_maintenance_page(app, verbose=false)
62
- invoke adapter(app, verbose).disable_maintenance_page
63
- end
64
-
65
- def has_app_code?
66
- !["db_master", "db_slave"].include?(role.to_s)
67
- end
68
-
69
- def hostname_url
70
- "http://#{hostname}" if hostname
71
- end
72
-
73
- protected
74
-
75
- def engineyard_serverside_hostname
76
- # If we tell engineyard-serverside to use 'localhost', it'll run
77
- # commands on the instance directly (#system). If we give it the
78
- # instance's actual hostname, it'll SSH to itself.
79
- #
80
- # Using 'localhost' instead of its EC2 hostname speeds up
81
- # deploys on solos and single-app-server clusters significantly.
82
- app_master? ? 'localhost' : hostname
83
- end
84
-
85
- private
86
-
87
- def ssh(remote_command, verbose, &block)
88
- raise(ArgumentError, "Block required!") unless block
89
-
90
- exit_code = nil
91
- cmd = Escape.shell_command(['bash', '-lc', remote_command])
92
- block.call("Running command on #{environment.username}@#{hostname}.\n")
93
- if cmd.respond_to?(:encoding) && cmd.respond_to?(:force_encoding)
94
- block.call("Encoding: #{cmd.encoding.name}") if verbose
95
- cmd.force_encoding('binary')
96
- block.call(" => #{cmd.encoding.name}; __ENCODING__: #{__ENCODING__.name}; LANG: #{ENV['LANG']}; LC_CTYPE: #{ENV['LC_CTYPE']}\n") if verbose
97
- end
98
- EY.ui.debug(cmd)
99
- block.call("Command: #{cmd}\n") if verbose
100
- if ENV["NO_SSH"]
101
- block.call("NO_SSH is set. No output.")
102
- true
103
- else
104
- begin
105
- options_for_ssh = {:paranoid => false}
106
- options_for_ssh[:verbose] = ENV["DEBUG"].downcase.to_sym if ENV["DEBUG"]
107
- Net::SSH.start(hostname, environment.username, options_for_ssh) do |net_ssh|
108
- net_ssh.open_channel do |channel|
109
- channel.exec cmd do |_, success|
110
- unless success
111
- block.call "Remote command execution failed"
112
- return false
113
- end
114
-
115
- channel.on_data do |_, data|
116
- block.call data
117
- end
118
-
119
- channel.on_extended_data do |_, _, data|
120
- block.call data
121
- end
122
-
123
- channel.on_request("exit-status") do |_, data|
124
- exit_code = data.read_long
125
- end
126
-
127
- channel.on_request("exit-signal") do |_, data|
128
- exit_code = 255
129
- end
130
- end
131
- end
132
-
133
- net_ssh.loop
134
- end
135
- exit_code.zero?
136
- rescue Net::SSH::AuthenticationFailed
137
- raise EY::Error, "Authentication Failed: Please add your environment's ssh key with: ssh-add path/to/key"
138
- end
139
- end
140
- end
141
-
142
- def invoke(action, &block)
143
- action.call do |cmd|
144
- ssh cmd, action.verbose do |chunk|
145
- $stdout << chunk
146
- block.call(chunk) if block
147
- end
148
- end
149
- end
150
-
151
- def instances_data
152
- environment.instances.select { |inst| inst.has_app_code? }.map do |i|
153
- {
154
- :hostname => i.engineyard_serverside_hostname,
155
- :roles => [i.role],
156
- :name => i.name,
157
- }
158
- end
159
- end
160
-
161
- def app_master?
162
- environment.app_master == self
163
- end
164
- end
165
- end
166
- end