deployml 0.3.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 (51) hide show
  1. data/.document +3 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +1 -0
  4. data/ChangeLog.md +22 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +107 -0
  7. data/Rakefile +35 -0
  8. data/bin/deployml +5 -0
  9. data/deployml.gemspec +10 -0
  10. data/gemspec.yml +21 -0
  11. data/lib/deployml/cli.rb +192 -0
  12. data/lib/deployml/configuration.rb +149 -0
  13. data/lib/deployml/environment.rb +273 -0
  14. data/lib/deployml/exceptions/config_not_found.rb +4 -0
  15. data/lib/deployml/exceptions/invalid_config.rb +4 -0
  16. data/lib/deployml/exceptions/missing_option.rb +6 -0
  17. data/lib/deployml/exceptions/unknown_environment.rb +4 -0
  18. data/lib/deployml/exceptions/unknown_framework.rb +6 -0
  19. data/lib/deployml/exceptions/unknown_server.rb +6 -0
  20. data/lib/deployml/frameworks/rails2.rb +9 -0
  21. data/lib/deployml/frameworks/rails3.rb +20 -0
  22. data/lib/deployml/frameworks.rb +2 -0
  23. data/lib/deployml/local_shell.rb +54 -0
  24. data/lib/deployml/options/mongrel.rb +46 -0
  25. data/lib/deployml/options/thin.rb +72 -0
  26. data/lib/deployml/options.rb +1 -0
  27. data/lib/deployml/project.rb +304 -0
  28. data/lib/deployml/remote_shell.rb +130 -0
  29. data/lib/deployml/servers/apache.rb +19 -0
  30. data/lib/deployml/servers/mongrel.rb +41 -0
  31. data/lib/deployml/servers/thin.rb +41 -0
  32. data/lib/deployml/servers.rb +3 -0
  33. data/lib/deployml/shell.rb +46 -0
  34. data/lib/deployml/version.rb +4 -0
  35. data/lib/deployml.rb +2 -0
  36. data/spec/configuration_spec.rb +73 -0
  37. data/spec/deployml_spec.rb +11 -0
  38. data/spec/environment_spec.rb +34 -0
  39. data/spec/helpers/projects/bad_config/config/deploy.yml +1 -0
  40. data/spec/helpers/projects/basic/config/deploy.yml +3 -0
  41. data/spec/helpers/projects/invalid_server/config/deploy.yml +4 -0
  42. data/spec/helpers/projects/missing_config/.gitkeep +0 -0
  43. data/spec/helpers/projects/missing_dest/config/deploy.yml +2 -0
  44. data/spec/helpers/projects/missing_source/config/deploy.yml +2 -0
  45. data/spec/helpers/projects/rails/config/deploy/production.yml +10 -0
  46. data/spec/helpers/projects/rails/config/deploy/staging.yml +10 -0
  47. data/spec/helpers/projects/rails/config/deploy.yml +3 -0
  48. data/spec/helpers/projects.rb +11 -0
  49. data/spec/project_spec.rb +66 -0
  50. data/spec/spec_helper.rb +8 -0
  51. metadata +207 -0
@@ -0,0 +1,304 @@
1
+ require 'deployml/exceptions/config_not_found'
2
+ require 'deployml/exceptions/invalid_config'
3
+ require 'deployml/exceptions/unknown_environment'
4
+ require 'deployml/environment'
5
+ require 'deployml/remote_shell'
6
+
7
+ require 'yaml'
8
+
9
+ module DeploYML
10
+ class Project
11
+
12
+ # The general configuration directory.
13
+ CONFIG_DIR = 'config'
14
+
15
+ # The configuration file name.
16
+ CONFIG_FILE = 'deploy.yml'
17
+
18
+ # The configuration directory.
19
+ ENVIRONMENTS_DIR = 'deploy'
20
+
21
+ # The name of the directory to stage deployments in.
22
+ STAGING_DIR = '.deploy'
23
+
24
+ # The root directory of the project
25
+ attr_reader :root
26
+
27
+ # The deployment environments of the project
28
+ attr_reader :environments
29
+
30
+ #
31
+ # Creates a new project using the given configuration file.
32
+ #
33
+ # @param [String] root
34
+ # The root directory of the project.
35
+ #
36
+ # @raise [ConfigNotFound]
37
+ # The configuration file for the project could not be found
38
+ # in any of the common directories.
39
+ #
40
+ def initialize(root)
41
+ @root = File.expand_path(root)
42
+ @config_file = File.join(@root,CONFIG_DIR,CONFIG_FILE)
43
+ @environments_dir = File.join(@root,CONFIG_DIR,ENVIRONMENTS_DIR)
44
+
45
+ unless (File.file?(@config_file) || File.directory?(@environments_dir))
46
+ raise(ConfigNotFound,"could not find '#{CONFIG_FILE}' or '#{ENVIRONMENTS_DIR}' in #{root}")
47
+ end
48
+
49
+ load_environments!
50
+ end
51
+
52
+ #
53
+ # @param [Symbol, String] name
54
+ # The name of the environment to use.
55
+ #
56
+ # @return [Environment]
57
+ # The environment with the given name.
58
+ #
59
+ # @raise [UnknownEnvironment]
60
+ # No environment was configured with the given name.
61
+ #
62
+ # @since 0.3.0
63
+ #
64
+ def environment(name=:production)
65
+ name = name.to_sym
66
+
67
+ unless @environments[name]
68
+ raise(UnknownEnvironment,"unknown environment: #{name}")
69
+ end
70
+
71
+ return @environments[name]
72
+ end
73
+
74
+ #
75
+ # Conveniance method for accessing the development environment.
76
+ #
77
+ # @return [Environment]
78
+ # The development environment.
79
+ #
80
+ # @since 0.3.0
81
+ #
82
+ def development
83
+ environment(:development)
84
+ end
85
+
86
+ #
87
+ # Conveniance method for accessing the staging environment.
88
+ #
89
+ # @return [Environment]
90
+ # The staging environment.
91
+ #
92
+ # @since 0.3.0
93
+ #
94
+ def staging
95
+ environment(:staging)
96
+ end
97
+
98
+ #
99
+ # Conveniance method for accessing the production environment.
100
+ #
101
+ # @return [Environment]
102
+ # The production environment.
103
+ #
104
+ # @since 0.3.0
105
+ #
106
+ def production
107
+ environment(:production)
108
+ end
109
+
110
+ #
111
+ # Deploys the project.
112
+ #
113
+ # @param [Array<Symbol>] tasks
114
+ # The tasks to run during the deployment.
115
+ #
116
+ # @param [Symbol, String] env
117
+ # The environment to deploy to.
118
+ #
119
+ # @return [true]
120
+ #
121
+ # @since 0.2.0
122
+ #
123
+ def invoke(tasks,env=:production)
124
+ env = environment(env)
125
+
126
+ env.remote_shell do |shell|
127
+ # setup the deployment repository
128
+ env.setup(shell) if tasks.include?(:setup)
129
+
130
+ # cd into the deployment repository
131
+ shell.cd env.dest.path
132
+
133
+ # update the deployment repository
134
+ env.update(shell) if tasks.include?(:update)
135
+
136
+ # framework tasks
137
+ env.install(shell) if tasks.include?(:install)
138
+ env.migrate(shell) if tasks.include?(:migrate)
139
+
140
+ # server tasks
141
+ if tasks.include?(:config)
142
+ env.server_config(shell)
143
+ elsif tasks.include?(:start)
144
+ env.server_start(shell)
145
+ elsif tasks.include?(:stop)
146
+ env.server_stop(shell)
147
+ elsif tasks.include?(:restart)
148
+ env.server_restart(shell)
149
+ end
150
+ end
151
+
152
+ return true
153
+ end
154
+
155
+ #
156
+ # Sets up the deployment repository for the project.
157
+ #
158
+ # @param [Symbol, String] env
159
+ # The environment to deploy to.
160
+ #
161
+ def setup!(env=:production)
162
+ invoke [:setup], env
163
+ end
164
+
165
+ #
166
+ # Updates the deployed repository of the project.
167
+ #
168
+ # @param [Symbol, String] env
169
+ # The environment to deploy to.
170
+ #
171
+ def update!(env=:production)
172
+ invoke [:update], env
173
+ end
174
+
175
+ #
176
+ # Installs the project on the destination server.
177
+ #
178
+ # @param [Symbol, String] env
179
+ # The environment to deploy to.
180
+ #
181
+ def install!(env=:production)
182
+ invoke [:install], env
183
+ end
184
+
185
+ #
186
+ # Migrates the database used by the project.
187
+ #
188
+ # @param [Symbol, String] env
189
+ # The environment to deploy to.
190
+ #
191
+ def migrate!(env=:production)
192
+ invoke [:migrate], env
193
+ end
194
+
195
+ #
196
+ # Configures the Web server to be ran on the destination server.
197
+ #
198
+ # @param [Symbol, String] env
199
+ # The environment to deploy to.
200
+ #
201
+ def config!(env=:production)
202
+ invoke [:config], env
203
+ end
204
+
205
+ #
206
+ # Starts the Web server for the project.
207
+ #
208
+ # @param [Symbol, String] env
209
+ # The environment to deploy to.
210
+ #
211
+ def start!(env=:production)
212
+ invoke [:start], env
213
+ end
214
+
215
+ #
216
+ # Stops the Web server for the project.
217
+ #
218
+ # @param [Symbol, String] env
219
+ # The environment to deploy to.
220
+ #
221
+ def stop!(env=:production)
222
+ invoke [:stop], env
223
+ end
224
+
225
+ #
226
+ # Restarts the Web server for the project.
227
+ #
228
+ # @param [Symbol, String] env
229
+ # The environment to deploy to.
230
+ #
231
+ def restart!(env=:production)
232
+ invoke [:restart], env
233
+ end
234
+
235
+ #
236
+ # Deploys a new project.
237
+ #
238
+ # @param [Symbol, String] env
239
+ # The environment to deploy to.
240
+ #
241
+ # @since 0.2.0
242
+ #
243
+ def deploy!(env=:production)
244
+ invoke [:setup, :install, :migrate, :config, :start], env
245
+ end
246
+
247
+ #
248
+ # Redeploys a project.
249
+ #
250
+ # @param [Symbol, String] env
251
+ # The environment to deploy to.
252
+ #
253
+ # @since 0.2.0
254
+ #
255
+ def redeploy!(env=:production)
256
+ invoke [:update, :install, :migrate, :restart], env
257
+ end
258
+
259
+ protected
260
+
261
+ #
262
+ # Loads the project configuration.
263
+ #
264
+ # @raise [InvalidConfig]
265
+ # The YAML configuration file did not contain a Hash.
266
+ #
267
+ # @raise [MissingOption]
268
+ # The `source` or `dest` options were not specified.
269
+ #
270
+ # @since 0.3.0
271
+ #
272
+ def load_environments!
273
+ base_config = {}
274
+
275
+ load_config_data = lambda { |path|
276
+ config_data = YAML.load_file(path)
277
+
278
+ unless config_data.kind_of?(Hash)
279
+ raise(InvalidConfig,"DeploYML file '#{path}' does not contain a Hash")
280
+ end
281
+
282
+ config_data
283
+ }
284
+
285
+ if File.file?(@config_file)
286
+ base_config.merge!(load_config_data[@config_file])
287
+ end
288
+
289
+ @environments = {}
290
+
291
+ if File.directory?(@environments_dir)
292
+ Dir.glob(File.join(@environments_dir,'*.yml')) do |path|
293
+ config_data = base_config.merge(load_config_data[path])
294
+ name = File.basename(path).sub(/\.yml$/,'').to_sym
295
+
296
+ @environments[name] = Environment.new(name,config_data)
297
+ end
298
+ else
299
+ @environments[:production] = Environment.new(:production,base_config)
300
+ end
301
+ end
302
+
303
+ end
304
+ end
@@ -0,0 +1,130 @@
1
+ require 'deployml/shell'
2
+
3
+ module DeploYML
4
+ class RemoteShell
5
+
6
+ include Shell
7
+
8
+ #
9
+ # Initializes a remote shell session.
10
+ #
11
+ # @param [Addressable::URI, String] uri
12
+ # The URI of the host to connect to.
13
+ #
14
+ # @yield [session]
15
+ # If a block is given, it will be passed the new remote shell session.
16
+ #
17
+ # @yieldparam [ShellSession] session
18
+ # The remote shell session.
19
+ #
20
+ def initialize(uri,&block)
21
+ case uri
22
+ when Addressable::URI
23
+ @uri = uri
24
+ else
25
+ @uri = Addressable::URI.parse(uri.to_s)
26
+ end
27
+
28
+ @history = []
29
+
30
+ super(&block)
31
+
32
+ replay if block
33
+ end
34
+
35
+ #
36
+ # Enqueues a program to be ran in the session.
37
+ #
38
+ # @param [String] program
39
+ # The name or path of the program to run.
40
+ #
41
+ # @param [Array<String>] args
42
+ # Additional arguments for the program.
43
+ #
44
+ def run(program,*args)
45
+ @history << [program, *args]
46
+ end
47
+
48
+ #
49
+ # Enqueues an `echo` command to be ran in the session.
50
+ #
51
+ # @param [String] message
52
+ # The message to echo.
53
+ #
54
+ def each(message)
55
+ run 'echo', message
56
+ end
57
+
58
+ #
59
+ # Enqueues a directory change for the session.
60
+ #
61
+ # @param [String] path
62
+ # The path of the new current working directory to use.
63
+ #
64
+ # @yield []
65
+ # If a block is given, then the directory will be changed back after
66
+ # the block has returned.
67
+ #
68
+ def cd(path,&block)
69
+ @history << ['cd', path]
70
+
71
+ if block
72
+ block.call() if block
73
+
74
+ @history << ['cd', '-']
75
+ end
76
+ end
77
+
78
+ #
79
+ # Joins the command history together with ` && `, to form a
80
+ # single command.
81
+ #
82
+ # @return [String]
83
+ # A single command string.
84
+ #
85
+ def join
86
+ @history.map { |command| command.join(' ') }.join(' && ')
87
+ end
88
+
89
+ #
90
+ # Converts the URI to one compatible with SSH.
91
+ #
92
+ # @return [String]
93
+ # The SSH compatible URI.
94
+ #
95
+ def ssh_uri
96
+ new_uri = @uri.host
97
+ new_uri = "#{@uri.user}@#{new_uri}" if @uri.user
98
+
99
+ return new_uri
100
+ end
101
+
102
+ #
103
+ # Starts a SSH session with the destination server.
104
+ #
105
+ # @param [Array] args
106
+ # Additional arguments to pass to SSH.
107
+ #
108
+ def ssh(*args)
109
+ options = []
110
+
111
+ # Add the -p option if an alternate destination port is given
112
+ if @uri.port
113
+ options += ['-p', @uri.port.to_s]
114
+ end
115
+
116
+ options << ssh_uri
117
+ options += args
118
+
119
+ return system('ssh',*options)
120
+ end
121
+
122
+ #
123
+ # Replays the command history on the remote server.
124
+ #
125
+ def replay
126
+ ssh(self.join) unless @history.empty?
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,19 @@
1
+ require 'deployml/exceptions/invalid_config'
2
+
3
+ module DeploYML
4
+ module Servers
5
+ module Apache
6
+ def server_start(shell)
7
+ shell.run 'apachectl', 'start'
8
+ end
9
+
10
+ def server_restart(shell)
11
+ shell.run 'apachectl', 'restart'
12
+ end
13
+
14
+ def server_stop(shell)
15
+ shell.run 'apachectl', 'stop'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ require 'deployml/exceptions/missing_option'
2
+ require 'deployml/options/mongrel'
3
+
4
+ module DeploYML
5
+ module Servers
6
+ module Mongrel
7
+ def initialize_server
8
+ @mongrel = Options::Mongrel.new(@server_options)
9
+ @mongrel.environment ||= @name
10
+ end
11
+
12
+ def mongrel_cluster(shell,*args)
13
+ options = args + ['-c', @mongrel.config]
14
+
15
+ shell.run 'mongrel_rails', *options
16
+ end
17
+
18
+ def server_config(shell)
19
+ unless @mongrel.config
20
+ raise(MissingOption,"No 'config' option specified under server options",caller)
21
+ end
22
+
23
+ options = ['-c', dest.path] + @mongrel.arguments
24
+
25
+ shell.run 'mongrel_rails', 'cluster::configure', *options
26
+ end
27
+
28
+ def server_start(shell)
29
+ mongrel_cluster 'cluster::start'
30
+ end
31
+
32
+ def server_stop(shell)
33
+ mongrel_cluster 'cluster::stop'
34
+ end
35
+
36
+ def server_restart(shell)
37
+ mongrel_cluster 'cluster::restart'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ require 'deployml/exceptions/missing_option'
2
+ require 'deployml/options/thin'
3
+
4
+ module DeploYML
5
+ module Servers
6
+ module Thin
7
+ def initialize_server
8
+ @thin = Options::Thin.new(@server_options)
9
+ @thin.environment ||= @name
10
+ end
11
+
12
+ def thin(shell,*args)
13
+ options = args + ['-C', @thin.config, '-s', @thin.servers]
14
+
15
+ shell.run 'thin', *options
16
+ end
17
+
18
+ def server_config(shell)
19
+ unless @thin.config
20
+ raise(MissingOption,"No 'config' option specified under the server options",caller)
21
+ end
22
+
23
+ options = ['-c', dest.path] + @thin.arguments
24
+
25
+ shell.run 'thin', 'config', *options
26
+ end
27
+
28
+ def server_start(shell)
29
+ thin shell, 'start'
30
+ end
31
+
32
+ def server_stop(shell)
33
+ thin shell, 'stop'
34
+ end
35
+
36
+ def server_restart(shell)
37
+ thin shell, 'restart'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ require 'deployml/servers/apache'
2
+ require 'deployml/servers/mongrel'
3
+ require 'deployml/servers/thin'
@@ -0,0 +1,46 @@
1
+ module DeploYML
2
+ module Shell
3
+
4
+ def initialize(&block)
5
+ block.call(self) if block
6
+ end
7
+
8
+ #
9
+ # Place-holder method.
10
+ #
11
+ # @param [String] program
12
+ # The name or path of the program to run.
13
+ #
14
+ # @param [Array<String>] args
15
+ # Additional arguments for the program.
16
+ #
17
+ def run(program,*args)
18
+ end
19
+
20
+ #
21
+ # Executes a Rake task.
22
+ #
23
+ # @param [Symbol, String] task
24
+ # Name of the Rake task to run.
25
+ #
26
+ # @param [Array<String>] args
27
+ # Additional arguments for the Rake task.
28
+ #
29
+ def rake(task,*args)
30
+ run 'rake', rake_task(task,*args)
31
+ end
32
+
33
+ protected
34
+
35
+ def rake_task(name,*args)
36
+ name = name.to_s
37
+
38
+ unless args.empty?
39
+ name += ('[' + args.join(',') + ']')
40
+ end
41
+
42
+ return name
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ module DeploYML
2
+ # deploYML version
3
+ VERSION = '0.3.0'
4
+ end
data/lib/deployml.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'deployml/project'
2
+ require 'deployml/version'
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ require 'deployml/configuration'
4
+
5
+ describe Configuration do
6
+ it "should accept String keys" do
7
+ config = Configuration.new('orm' => :datamapper)
8
+
9
+ config.orm.should == :datamapper
10
+ end
11
+
12
+ it "should accept Symbol keys" do
13
+ config = Configuration.new(:orm => :datamapper)
14
+
15
+ config.orm.should == :datamapper
16
+ end
17
+
18
+ it "should parse 'dest' String URIs" do
19
+ config = Configuration.new(
20
+ :dest => 'ssh://user@www.example.com/srv/project'
21
+ )
22
+
23
+ config.dest.scheme.should == 'ssh'
24
+ config.dest.user.should == 'user'
25
+ config.dest.host.should == 'www.example.com'
26
+ config.dest.path.should == '/srv/project'
27
+ end
28
+
29
+ it "should parse 'dest' Hash URIs" do
30
+ config = Configuration.new(:dest => {
31
+ 'scheme' => 'ssh',
32
+ 'user' => 'user',
33
+ 'host' => 'www.example.com',
34
+ 'path' => '/srv/project'
35
+ })
36
+
37
+ config.dest.scheme.should == 'ssh'
38
+ config.dest.user.should == 'user'
39
+ config.dest.host.should == 'www.example.com'
40
+ config.dest.path.should == '/srv/project'
41
+ end
42
+
43
+ it "should default the 'debug' option to false" do
44
+ config = Configuration.new
45
+
46
+ config.debug.should == false
47
+ end
48
+
49
+ it "should default the environment to nil" do
50
+ config = Configuration.new
51
+
52
+ config.environment.should be_nil
53
+ end
54
+
55
+ it "should accept a Symbol for the 'server' option" do
56
+ config = Configuration.new(:server => :thin)
57
+
58
+ config.server_name.should == :thin
59
+ config.server_options.should be_empty
60
+ end
61
+
62
+ it "should accept a Hash for the 'server' option" do
63
+ config = Configuration.new(
64
+ :server => {
65
+ :name => :thin,
66
+ :options => {:address => '127.0.0.1'}
67
+ }
68
+ )
69
+
70
+ config.server_name.should == :thin
71
+ config.server_options.should == {:address => '127.0.0.1'}
72
+ end
73
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ require 'deployml/version'
4
+
5
+ describe DeploYML do
6
+ it "should have a version" do
7
+ @version = DeploYML.const_get('VERSION')
8
+ @version.should_not be_nil
9
+ @version.should_not be_empty
10
+ end
11
+ end