engineyard 1.4.29 → 1.7.0.pre2

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