conjure 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +6 -0
- data/README.md +2 -0
- data/lib/conjure/application.rb +2 -2
- data/lib/conjure/command.rb +18 -23
- data/lib/conjure/command_target.rb +25 -0
- data/lib/conjure/instance.rb +74 -13
- data/lib/conjure/notes.txt +99 -5
- data/lib/conjure/provision.rb +1 -0
- data/lib/conjure/provision/docker_image.rb +20 -0
- data/lib/conjure/provision/dockerfile.rb +62 -0
- data/lib/conjure/provision/instance.rb +72 -0
- data/lib/conjure/provision/server.rb +64 -0
- data/lib/conjure/service/cloud_server.rb +25 -0
- data/lib/conjure/service/rails_codebase.rb +4 -0
- data/lib/conjure/service/rails_log_view.rb +1 -1
- data/lib/conjure/service/repository_link.rb +4 -0
- data/lib/conjure/version.rb +1 -1
- data/lib/conjure/view/application_view.rb +2 -2
- metadata +49 -30
- checksums.yaml +0 -7
- data/lib/conjure/service/rails_deployment.rb +0 -39
data/History.md
CHANGED
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/conjure.png)](http://badge.fury.io/rb/conjure)
|
3
3
|
[![Build Status](https://travis-ci.org/brianauton/conjure.png?branch=master)](https://travis-ci.org/brianauton/conjure)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/brianauton/conjure.png)](https://codeclimate.com/github/brianauton/conjure)
|
5
|
+
[![Dependency Status](https://gemnasium.com/brianauton/conjure.png)](https://gemnasium.com/brianauton/conjure)
|
6
|
+
|
5
7
|
|
6
8
|
Magically powerful deployment for Rails applications.
|
7
9
|
|
data/lib/conjure/application.rb
CHANGED
data/lib/conjure/command.rb
CHANGED
@@ -8,29 +8,37 @@ module Conjure
|
|
8
8
|
Log.level = :debug if options[:verbose]
|
9
9
|
end
|
10
10
|
|
11
|
+
desc "create", "Create and deploy a new instance of the application"
|
12
|
+
method_option :branch, :aliases => "-b", :type => :string, :desc => "Specify branch to deploy, default 'master'"
|
13
|
+
method_option :origin, :type => :string, :desc => "Specify git URL to deploy from"
|
14
|
+
method_option :rails_env, :type => :string, :desc => "Specify the Rails environment, default 'production'"
|
15
|
+
def create
|
16
|
+
target.new_instance.create
|
17
|
+
end
|
18
|
+
|
11
19
|
desc "deploy", "Deploy the app"
|
12
|
-
method_option :branch, :aliases => "-b", :type => :string, :desc => "Specify branch to deploy"
|
13
|
-
method_option :test, :type => :boolean, :desc => "Describe the deploy settings but don't deploy"
|
20
|
+
method_option :branch, :aliases => "-b", :type => :string, :desc => "Specify branch to deploy, default 'master'"
|
14
21
|
method_option :origin, :type => :string, :desc => "Specify git URL to deploy from"
|
22
|
+
method_option :rails_env, :type => :string, :desc => "Specify the Rails environment, default 'production'"
|
15
23
|
def deploy
|
16
|
-
|
24
|
+
(target.existing_instance || target.new_instance).deploy
|
17
25
|
end
|
18
26
|
|
19
27
|
desc "import FILE", "Import the production database from a postgres SQL dump"
|
20
28
|
def import(file)
|
21
|
-
|
29
|
+
target.existing_instance.database.import file
|
22
30
|
end
|
23
31
|
|
24
32
|
desc "export FILE", "Export the production database to a postgres SQL dump"
|
25
33
|
def export(file)
|
26
|
-
|
34
|
+
target.existing_instance.database.export file
|
27
35
|
end
|
28
36
|
|
29
37
|
desc "log", "Display the Rails log from the deployed application"
|
30
38
|
method_option :num, :aliases => "-n", :type => :numeric, :default => 10, :desc => "Show N lines of output"
|
31
39
|
method_option :tail, :aliases => "-t", :type => :boolean, :desc => "Continue streaming new log entries"
|
32
40
|
def log
|
33
|
-
Service::RailsLogView.new(:shell =>
|
41
|
+
Service::RailsLogView.new(:shell => target.existing_instance.shell, :rails_env => target.existing_instance.rails_env, :lines => options[:num], :tail => options[:tail]) do |stdout|
|
34
42
|
print stdout
|
35
43
|
end
|
36
44
|
end
|
@@ -38,42 +46,29 @@ module Conjure
|
|
38
46
|
desc "rake [ARGUMENTS...]", "Run the specified rake task on the deployed application"
|
39
47
|
def rake(*arguments)
|
40
48
|
task = arguments.join(" ")
|
41
|
-
Service::RakeTask.new(:task => task, :shell =>
|
49
|
+
Service::RakeTask.new(:task => task, :shell => target.existing_instance.shell) do |stdout|
|
42
50
|
print stdout
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
46
54
|
desc "console", "Start a console on the deployed application"
|
47
55
|
def console
|
48
|
-
Service::RailsConsole.new(:shell =>
|
56
|
+
Service::RailsConsole.new(:shell => target.existing_instance.shell) do |stdout|
|
49
57
|
print stdout
|
50
58
|
end
|
51
59
|
end
|
52
60
|
|
53
61
|
desc "show", "Show info on deployed instances"
|
54
62
|
def show
|
55
|
-
puts View::ApplicationView.new(application).render
|
63
|
+
puts View::ApplicationView.new(target.application).render
|
56
64
|
end
|
57
65
|
|
58
66
|
default_task :help
|
59
67
|
|
60
68
|
private
|
61
69
|
|
62
|
-
def application
|
63
|
-
@application ||= Application.find(:path => Dir.pwd, :origin => options[:origin])
|
64
|
-
end
|
65
|
-
|
66
|
-
def deployment
|
67
|
-
@deployment ||= Service::RailsDeployment.new({
|
68
|
-
:branch => options[:branch],
|
69
|
-
:origin => application.origin,
|
70
|
-
:target => target,
|
71
|
-
:test => options[:test],
|
72
|
-
})
|
73
|
-
end
|
74
|
-
|
75
70
|
def target
|
76
|
-
|
71
|
+
CommandTarget.new(options)
|
77
72
|
end
|
78
73
|
end
|
79
74
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Conjure
|
2
|
+
class CommandTarget
|
3
|
+
def initialize(options = {})
|
4
|
+
@origin = options[:origin]
|
5
|
+
@branch = options[:branch] || "master"
|
6
|
+
@rails_env = options[:rails_env] || "production"
|
7
|
+
end
|
8
|
+
|
9
|
+
def application
|
10
|
+
@application ||= Application.find(:path => Dir.pwd, :origin => @origin)
|
11
|
+
end
|
12
|
+
|
13
|
+
def existing_instance
|
14
|
+
@existing_instance ||= application.instances.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_instance
|
18
|
+
@new_instance ||= Instance.new(
|
19
|
+
:origin => application.origin,
|
20
|
+
:branch => @branch,
|
21
|
+
:rails_environment => @rails_env,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/conjure/instance.rb
CHANGED
@@ -1,39 +1,100 @@
|
|
1
1
|
module Conjure
|
2
2
|
class Instance
|
3
|
-
attr_reader :application, :ip_address, :rails_environment
|
4
|
-
|
5
3
|
def initialize(options)
|
6
|
-
@
|
7
|
-
@
|
4
|
+
@origin = options[:origin]
|
5
|
+
@branch = options[:branch]
|
8
6
|
@rails_environment = options[:rails_environment]
|
7
|
+
@server = options[:server]
|
9
8
|
end
|
10
9
|
|
11
10
|
def self.where(options = {})
|
12
11
|
Collection.new(options)
|
13
12
|
end
|
14
13
|
|
14
|
+
def origin
|
15
|
+
@origin ||= @server.name.split("-")[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
def rails_environment
|
19
|
+
@rails_environment ||= @server.name.split("-")[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
def ip_address
|
23
|
+
@server.ip_address
|
24
|
+
end
|
25
|
+
|
26
|
+
def shell
|
27
|
+
rails_server.base_image
|
28
|
+
end
|
29
|
+
|
30
|
+
def branch
|
31
|
+
@branch ||= codebase.branch
|
32
|
+
end
|
33
|
+
|
34
|
+
def database
|
35
|
+
codebase.database
|
36
|
+
end
|
37
|
+
|
38
|
+
def create
|
39
|
+
@server_name = Service::CloudServer.ensure_unique_name(server_name)
|
40
|
+
deploy
|
41
|
+
end
|
42
|
+
|
43
|
+
def deploy
|
44
|
+
Log.info "[deploy] Deploying #{branch} to #{rails_environment}"
|
45
|
+
codebase.install
|
46
|
+
rails_server.run
|
47
|
+
Log.info "[deploy] Application deployed to #{ip_address}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def codebase
|
51
|
+
@codebase ||= Service::RailsCodebase.new target, origin, @branch, rails_environment
|
52
|
+
end
|
53
|
+
|
54
|
+
def rails_server
|
55
|
+
@rails_server ||= Service::RailsServer.new target, rails_environment
|
56
|
+
end
|
57
|
+
|
58
|
+
def server_name
|
59
|
+
@server_name ||= "#{application_name}-#{rails_environment}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def server
|
63
|
+
@server ||= Service::CloudServer.new(server_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def target
|
67
|
+
@target ||= Target.new(:machine_name => server.name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def application_name
|
71
|
+
Application.new(:origin => @origin).name
|
72
|
+
end
|
73
|
+
|
15
74
|
def status
|
16
75
|
"running"
|
17
76
|
end
|
18
77
|
|
78
|
+
def name
|
79
|
+
server.name
|
80
|
+
end
|
81
|
+
|
19
82
|
class Collection
|
20
83
|
include Enumerable
|
21
84
|
|
22
85
|
def initialize(options)
|
23
|
-
@
|
86
|
+
@origin = options[:origin]
|
24
87
|
end
|
25
88
|
|
26
|
-
def
|
27
|
-
|
89
|
+
def application_name
|
90
|
+
Application.new(:origin => @origin).name
|
28
91
|
end
|
29
92
|
|
30
93
|
def each(&block)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:ip_address => server.ip_address
|
36
|
-
)
|
94
|
+
return unless @origin
|
95
|
+
Service::CloudServer.each_with_name_prefix("#{application_name}-") do |server|
|
96
|
+
match = server.name.match(/^#{application_name}-([^-]+)(-[0-9]+)?$/)
|
97
|
+
yield Instance.new(:server => server) if match
|
37
98
|
end
|
38
99
|
end
|
39
100
|
end
|
data/lib/conjure/notes.txt
CHANGED
@@ -1,9 +1,61 @@
|
|
1
|
+
==== New hook-based architecture:
|
2
|
+
1. Proxy app, binds to machine's ports and proxies traffic to other containers
|
3
|
+
2. Deployer app, builds and starts revisions of the app in other containers
|
4
|
+
- Recieves github push notification, builds a new container with the new app revision
|
5
|
+
- Uses a custom-built Dockerfile for the app, designed to rebuild with minimal i/o
|
6
|
+
- After new revision is verified up and running, notifies the proxy to redirect
|
7
|
+
- Could run a small capybara smoke test against the production app
|
8
|
+
- Stops old container. Destroys it? (Rebuilding it should be fast if needed)
|
9
|
+
3. The application itself
|
10
|
+
====
|
11
|
+
|
12
|
+
1. Add area in conjure repo to build and publish docker images
|
13
|
+
- publish "conjure/passenger-ruby21"
|
14
|
+
- publish "conjure/postgres"
|
15
|
+
2. Add isolated conjure commands to work with custom docker images:
|
16
|
+
- "provision [rails-env]"
|
17
|
+
- Creates DO server using DIGITAL_OCEAN_* env vars
|
18
|
+
- Starts conjure/postgres
|
19
|
+
- Installs shared database.yml
|
20
|
+
- Adds 'passenger_app_env ENV' to http{} section of nginx.conf
|
21
|
+
- Starts conjure/passenger-ruby21 linked to postgres
|
22
|
+
- Displays needed Cap info: ip, ssh port, deploy user, app path, rails_env
|
23
|
+
- "addkey [ip] [sshport]"
|
24
|
+
3. Add conjure to client app & run "provision staging"
|
25
|
+
4. Add Cap to client app and configure staging settings
|
26
|
+
5. "cap staging deploy" from client app
|
27
|
+
|
28
|
+
==== Steps to deploy w/ Cap:
|
29
|
+
|
30
|
+
Dockerfile (webserver):
|
31
|
+
Install passenger/nginx
|
32
|
+
Install likely gem dependencies incl. node
|
33
|
+
Install required ruby version
|
34
|
+
Dockerfile (postgres):
|
35
|
+
|
36
|
+
Conjure:
|
37
|
+
Provision DigitalOcean Docker instance
|
38
|
+
|
39
|
+
Postgres:
|
40
|
+
Start postgres container
|
41
|
+
|
42
|
+
Passenger:
|
43
|
+
Start webserver container linked to postgres container
|
44
|
+
Install public key in webserver container
|
45
|
+
Install nginx config for app
|
46
|
+
Install shared database.yml
|
47
|
+
|
48
|
+
Script in app:
|
49
|
+
Capistrano:
|
50
|
+
Deploy current app version
|
51
|
+
|
52
|
+
|
1
53
|
TASKS:
|
2
54
|
Remove "label" from prepared shell creation
|
3
55
|
Alternate way to show per-service progress output
|
4
56
|
Log creation and status from each service
|
5
57
|
Log name and IP from docker container creation in verbose mode only
|
6
|
-
Separate
|
58
|
+
Separate platforms vs services
|
7
59
|
Deployment.deploy should become Deployment.create
|
8
60
|
|
9
61
|
Instead of passing DockerHost objects around, pass something that
|
@@ -51,10 +103,10 @@ But where do the database, logger, cron jobs, and other required
|
|
51
103
|
services get identified and instantiated?
|
52
104
|
|
53
105
|
Conjure's basic job is: deploy [something] [somewhere] using
|
54
|
-
[
|
106
|
+
[platforms]. "Something" is a Service, of which Instance and DataSet
|
55
107
|
are both types. "Somewhere" is the desired interface over which the
|
56
|
-
new Service will be accessible. "
|
57
|
-
VMs, and other combined
|
108
|
+
new Service will be accessible. "Platform" is the cloud service, local
|
109
|
+
VMs, and other combined platforms that will be used to do the
|
58
110
|
deployment.
|
59
111
|
|
60
112
|
Need a concept for something that has both stdin and stdout (and
|
@@ -93,7 +145,6 @@ supported), or rails_env. Need "deploy" to be a shorthand for either
|
|
93
145
|
already a staging instance. Maybe "deploy" should be deprecated?
|
94
146
|
|
95
147
|
Needs:
|
96
|
-
Route all current commands through instances
|
97
148
|
Separate commands to "create" and "update" instances
|
98
149
|
Support specific env/branch when creating
|
99
150
|
Preserve existing env/branch when updating
|
@@ -104,4 +155,47 @@ Needs:
|
|
104
155
|
Deploy to local docker
|
105
156
|
Deploy to vagrant (?)
|
106
157
|
Progress display
|
158
|
+
Allow specifying SHA for deployment (pull out into object with repo+branch?)
|
107
159
|
Used stored docker images for faster deployment
|
160
|
+
|
161
|
+
|
162
|
+
New Refactor (POODR)
|
163
|
+
|
164
|
+
Application-related models:
|
165
|
+
Application - All revisions of the app in abstract (a repo URL for now)
|
166
|
+
ApplicationVersion - Refers to a specific repo/branch/revision
|
167
|
+
ApplicationSource - Unpacked into a file system for reading/writing
|
168
|
+
|
169
|
+
UserPreferredPlatform - used by Command. Takes all user
|
170
|
+
configuration (command line switches, config file, etc) and produces
|
171
|
+
a Platform used to provision services.
|
172
|
+
|
173
|
+
InstanceFactory - takes a Platform and an ApplicationRevision to deploy on it.
|
174
|
+
queries [something] to ask which services the ApplicationRevision needs.
|
175
|
+
queries Instance to create it with the services
|
176
|
+
Instance - associates an ApplicationVersion with multiple Services that support it.
|
177
|
+
traverses the services to find certain kinds (web service, database)
|
178
|
+
|
179
|
+
|
180
|
+
Acceptance specs that drive Command and watch for specific output
|
181
|
+
Unit specs for Command that expect messages to collaborators
|
182
|
+
|
183
|
+
|
184
|
+
[binary]
|
185
|
+
constructs Command with a CommandResultView
|
186
|
+
|
187
|
+
Command
|
188
|
+
takes status_view (via constant)
|
189
|
+
sends output to status_view
|
190
|
+
|
191
|
+
StatusView
|
192
|
+
takes various *View classes for rendering different types
|
193
|
+
takes output_stream
|
194
|
+
sends output_stream and object for each object rendered
|
195
|
+
sends text output directly to output_stream
|
196
|
+
|
197
|
+
ApplicationView
|
198
|
+
takes output_stream and application
|
199
|
+
|
200
|
+
Cloud server infrastructure tips:
|
201
|
+
http://wblinks.com/notes/aws-tips-i-wish-id-known-before-i-started/
|
@@ -0,0 +1 @@
|
|
1
|
+
require "conjure/provision/instance"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Provision
|
3
|
+
class DockerImage
|
4
|
+
attr_reader :image_name
|
5
|
+
|
6
|
+
def initialize(server, base_image_name)
|
7
|
+
@server = server
|
8
|
+
@image_name = base_image_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def start(shell_command, options = {})
|
12
|
+
container_id = @server.run("docker run -d #{options[:run_options].to_s} #{image_name} #{shell_command}").strip
|
13
|
+
sleep 2
|
14
|
+
ip_address = @server.run("docker inspect -format '{{ .NetworkSettings.IPAddress }}' #{container_id}").strip
|
15
|
+
raise "Container failed to start" unless ip_address.present?
|
16
|
+
ip_address
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "conjure/provision/docker_image"
|
2
|
+
|
3
|
+
module Conjure
|
4
|
+
module Provision
|
5
|
+
class Dockerfile
|
6
|
+
def initialize(base_image_name)
|
7
|
+
@commands = ["FROM #{base_image_name}"]
|
8
|
+
@file_data = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_file(filename, remote_name)
|
12
|
+
add_file_data File.read(filename), remote_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_file_data(data, remote_name)
|
16
|
+
local_name = "file#{@file_data.length+1}"
|
17
|
+
@file_data[local_name] = data
|
18
|
+
@commands << "ADD #{local_name} #{remote_name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(command)
|
22
|
+
@commands << "RUN #{command}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def source
|
26
|
+
@commands.join "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepare_build_directory(&block)
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
@file_data.merge("Dockerfile" => source).each do |filename, data|
|
32
|
+
File.write "#{dir}/#{filename}", data
|
33
|
+
end
|
34
|
+
yield dir
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def upload_build_directory(server, &block)
|
39
|
+
archive = "/tmp/dockerfile.tar.gz"
|
40
|
+
build_dir = "/tmp/docker_build"
|
41
|
+
prepare_build_directory do |dir|
|
42
|
+
`cd #{dir}; tar czf #{archive} *`
|
43
|
+
server.send_file archive, "dockerfile.tar.gz"
|
44
|
+
server.run "mkdir #{build_dir}; cd #{build_dir}; tar xzf ~/dockerfile.tar.gz"
|
45
|
+
result = yield "/tmp/docker_build"
|
46
|
+
server.run "rm -Rf #{build_dir} ~/dockerfile.tar.gz"
|
47
|
+
`rm #{archive}`
|
48
|
+
result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def build(server)
|
53
|
+
result = upload_build_directory(server) { |dir| server.run "docker build #{dir}" }
|
54
|
+
if match = result.match(/Successfully built ([0-9a-z]+)/)
|
55
|
+
DockerImage.new server, match[1]
|
56
|
+
else
|
57
|
+
raise "Failed to build Docker image, output was #{result}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "conjure/provision/dockerfile"
|
2
|
+
|
3
|
+
module Conjure
|
4
|
+
module Provision
|
5
|
+
class Instance
|
6
|
+
def initialize(app_name, rails_env)
|
7
|
+
@app_name = app_name
|
8
|
+
@rails_env = rails_env
|
9
|
+
end
|
10
|
+
|
11
|
+
def provision
|
12
|
+
server = Server.create "#{@app_name}-#{@rails_env}"
|
13
|
+
|
14
|
+
db_password = new_db_password
|
15
|
+
postgres_image = postgres_dockerfile(db_password).build(server)
|
16
|
+
db_ip_address = postgres_image.start("/sbin/my_init")
|
17
|
+
|
18
|
+
passenger_image = passenger_dockerfile(db_ip_address, db_password).build(server)
|
19
|
+
passenger_image.start("/sbin/my_init", :run_options => "-p 80:80 -p 2222:22")
|
20
|
+
|
21
|
+
host = "root@#{server.ip_address} -p 2222"
|
22
|
+
remote_command host, "/etc/init.d/nginx restart"
|
23
|
+
{
|
24
|
+
:ip_address => server.ip_address,
|
25
|
+
:port => "2222",
|
26
|
+
:user => "app",
|
27
|
+
:rails_env => @rails_env
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def postgres_dockerfile(db_password)
|
32
|
+
file = Dockerfile.new("conjure/postgres93:1.0.0")
|
33
|
+
file.run "echo \"ALTER USER db PASSWORD '#{db_password}'\" >/tmp/setpass"
|
34
|
+
file.run "/sbin/my_init -- /sbin/setuser postgres sh -c \"sleep 1; psql -f /tmp/setpass\""
|
35
|
+
file.run "rm /tmp/setpass"
|
36
|
+
file
|
37
|
+
end
|
38
|
+
|
39
|
+
def passenger_dockerfile(db_ip_address, db_password)
|
40
|
+
public_key = File.expand_path("~/.ssh/id_rsa.pub")
|
41
|
+
raise "Error: ~/.ssh/id_rsa.pub must exist." unless File.exist?(public_key)
|
42
|
+
file = Dockerfile.new("conjure/passenger-ruby21:1.0.0")
|
43
|
+
file.add_file public_key, "/root/.ssh/authorized_keys"
|
44
|
+
file.add_file public_key, "/home/app/.ssh/authorized_keys"
|
45
|
+
file.run "chown app.app /home/app/.ssh/authorized_keys"
|
46
|
+
file.run "chown root.root /root/.ssh/authorized_keys"
|
47
|
+
file.add_file_data application_conf, "/etc/nginx/sites-enabled/application.conf"
|
48
|
+
file.add_file_data database_yml(db_ip_address, db_password), "/home/app/application/shared/config/database.yml"
|
49
|
+
file
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_db_password
|
53
|
+
require "securerandom"
|
54
|
+
SecureRandom.urlsafe_base64 20
|
55
|
+
end
|
56
|
+
|
57
|
+
def database_yml(db_ip_address, db_password)
|
58
|
+
require "yaml"
|
59
|
+
{@rails_env => {"adapter" => "sqlite3", "database" => "db/#{@rails_env}.sqlite3", "host" => db_ip_address, "username" => "db", "password" => db_password}}.to_yaml
|
60
|
+
end
|
61
|
+
|
62
|
+
def application_conf
|
63
|
+
options = {listen: "80", root: "/home/app/application/current/public", passenger_enabled: "on", passenger_user: "app", passenger_ruby: "/usr/bin/ruby2.1", passenger_app_env: @rails_env}
|
64
|
+
"server {" + options.map{|k, v| " #{k} #{v};"}.join("\n") + "}\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
def remote_command(host, command)
|
68
|
+
`ssh -o StrictHostKeyChecking=no #{host} #{command}`
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Provision
|
3
|
+
class Server
|
4
|
+
def initialize(server)
|
5
|
+
@server = server
|
6
|
+
puts "Configuring droplet..."
|
7
|
+
install_swap
|
8
|
+
end
|
9
|
+
|
10
|
+
def ip_address
|
11
|
+
@server.public_ip_address
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(command)
|
15
|
+
`ssh #{ssh_options} root@#{ip_address} '#{shell_escape_single_quotes command}'`
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_file(local_name, remote_name)
|
19
|
+
`scp #{ssh_options} #{local_name} root@#{ip_address}:#{remote_name}`
|
20
|
+
end
|
21
|
+
|
22
|
+
def ssh_options
|
23
|
+
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
24
|
+
end
|
25
|
+
|
26
|
+
def shell_escape_single_quotes(command)
|
27
|
+
command.gsub("'", "'\"'\"'")
|
28
|
+
end
|
29
|
+
|
30
|
+
def install_swap
|
31
|
+
run "dd if=/dev/zero of=/root/swapfile bs=1024 count=524288"
|
32
|
+
run "mkswap /root/swapfile; swapon /root/swapfile"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create(name)
|
36
|
+
puts "Creating DigitalOcean droplet..."
|
37
|
+
connection = Fog::Compute.new compute_options
|
38
|
+
new connection.servers.bootstrap(bootstrap_options name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.compute_options
|
42
|
+
raise "Error: DIGITALOCEAN_API_KEY and DIGITALOCEAN_CLIENT_ID env vars must both be set." unless ENV["DIGITALOCEAN_API_KEY"] && ENV["DIGITALOCEAN_CLIENT_ID"]
|
43
|
+
{
|
44
|
+
:provider => :digitalocean,
|
45
|
+
:digitalocean_api_key => ENV["DIGITALOCEAN_API_KEY"],
|
46
|
+
:digitalocean_client_id => ENV["DIGITALOCEAN_CLIENT_ID"],
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.bootstrap_options(name)
|
51
|
+
ssh_dir = File.expand_path("~/.ssh")
|
52
|
+
raise "Error: ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub must exist." unless File.exist?(ssh_dir) && File.exist?("#{ssh_dir}/id_rsa") && File.exist?("#{ssh_dir}/id_rsa.pub")
|
53
|
+
{
|
54
|
+
:name => name,
|
55
|
+
:flavor_id => "66",
|
56
|
+
:region_id => "4",
|
57
|
+
:image_id => "2158507",
|
58
|
+
:private_key_path => "#{ssh_dir}/id_rsa",
|
59
|
+
:public_key_path => "#{ssh_dir}/id_rsa.pub",
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -3,6 +3,7 @@ module Conjure
|
|
3
3
|
class CloudServer
|
4
4
|
require "fog"
|
5
5
|
require "pathname"
|
6
|
+
attr_reader :name
|
6
7
|
|
7
8
|
def initialize(name)
|
8
9
|
@name = name
|
@@ -32,6 +33,30 @@ module Conjure
|
|
32
33
|
@existing_server ||= connection.servers.find{|s| s.name == @name } if connection
|
33
34
|
end
|
34
35
|
|
36
|
+
def self.connection
|
37
|
+
new("").connection
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.each_with_name_prefix(prefix, &block)
|
41
|
+
return unless connection
|
42
|
+
connection.servers.all.select{|s| s.name.match(/^#{prefix}/)}.each do |server|
|
43
|
+
block.call new(server.name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.ensure_unique_name(name)
|
48
|
+
return name unless connection
|
49
|
+
existing_names = connection.servers.all.map{ |s| s.name }
|
50
|
+
name = increment_numeric_suffix(name) while existing_names.include? name
|
51
|
+
name
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.increment_numeric_suffix(name)
|
55
|
+
parts = name.split("-")
|
56
|
+
parts[2] = parts[2] ? ((parts[2].to_i)+1).to_s : "2"
|
57
|
+
parts.join("-")
|
58
|
+
end
|
59
|
+
|
35
60
|
def new_server
|
36
61
|
Log.info " [cloud] Launching new server #{@name}"
|
37
62
|
bootstrap_options = account.bootstrap_options.merge(:name => @name)
|
@@ -5,7 +5,7 @@ module Conjure
|
|
5
5
|
arguments = []
|
6
6
|
arguments << "-n #{options[:lines]}" if options[:lines]
|
7
7
|
arguments << "-f" if options[:tail]
|
8
|
-
arguments << "application_root/log
|
8
|
+
arguments << "application_root/log/#{options[:rails_env]}.log"
|
9
9
|
options[:shell].command("tail #{arguments.join ' '}", &block)
|
10
10
|
rescue Interrupt
|
11
11
|
end
|
data/lib/conjure/version.rb
CHANGED
@@ -30,9 +30,9 @@ module Conjure
|
|
30
30
|
def instances_table
|
31
31
|
data = @instances.map do |instance|
|
32
32
|
{
|
33
|
-
"
|
34
|
-
"Environment" => instance.rails_environment,
|
33
|
+
"Name" => instance.name,
|
35
34
|
"Status" => instance.status,
|
35
|
+
"Address" => instance.ip_address,
|
36
36
|
}
|
37
37
|
end
|
38
38
|
TableView.new(data).render
|
metadata
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conjure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Brian Auton
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-03-19 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: fog
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
19
|
- - ">="
|
18
20
|
- !ruby/object:Gem::Version
|
@@ -20,6 +22,7 @@ dependencies:
|
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
@@ -27,6 +30,7 @@ dependencies:
|
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: thor
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
35
|
- - ">="
|
32
36
|
- !ruby/object:Gem::Version
|
@@ -34,6 +38,7 @@ dependencies:
|
|
34
38
|
type: :runtime
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
43
|
- - ">="
|
39
44
|
- !ruby/object:Gem::Version
|
@@ -41,6 +46,7 @@ dependencies:
|
|
41
46
|
- !ruby/object:Gem::Dependency
|
42
47
|
name: unf
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
44
50
|
requirements:
|
45
51
|
- - ">="
|
46
52
|
- !ruby/object:Gem::Version
|
@@ -48,6 +54,7 @@ dependencies:
|
|
48
54
|
type: :runtime
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
59
|
- - ">="
|
53
60
|
- !ruby/object:Gem::Version
|
@@ -55,6 +62,7 @@ dependencies:
|
|
55
62
|
- !ruby/object:Gem::Dependency
|
56
63
|
name: guard-rspec
|
57
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
58
66
|
requirements:
|
59
67
|
- - ">="
|
60
68
|
- !ruby/object:Gem::Version
|
@@ -62,6 +70,7 @@ dependencies:
|
|
62
70
|
type: :development
|
63
71
|
prerelease: false
|
64
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
65
74
|
requirements:
|
66
75
|
- - ">="
|
67
76
|
- !ruby/object:Gem::Version
|
@@ -69,20 +78,23 @@ dependencies:
|
|
69
78
|
- !ruby/object:Gem::Dependency
|
70
79
|
name: rspec
|
71
80
|
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
72
82
|
requirements:
|
73
83
|
- - ">="
|
74
84
|
- !ruby/object:Gem::Version
|
75
|
-
version: 3.0.0.
|
85
|
+
version: 3.0.0.beta2
|
76
86
|
type: :development
|
77
87
|
prerelease: false
|
78
88
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
79
90
|
requirements:
|
80
91
|
- - ">="
|
81
92
|
- !ruby/object:Gem::Version
|
82
|
-
version: 3.0.0.
|
93
|
+
version: 3.0.0.beta2
|
83
94
|
- !ruby/object:Gem::Dependency
|
84
95
|
name: rake
|
85
96
|
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
86
98
|
requirements:
|
87
99
|
- - ">="
|
88
100
|
- !ruby/object:Gem::Version
|
@@ -90,6 +102,7 @@ dependencies:
|
|
90
102
|
type: :development
|
91
103
|
prerelease: false
|
92
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
93
106
|
requirements:
|
94
107
|
- - ">="
|
95
108
|
- !ruby/object:Gem::Version
|
@@ -102,64 +115,70 @@ executables:
|
|
102
115
|
extensions: []
|
103
116
|
extra_rdoc_files: []
|
104
117
|
files:
|
105
|
-
- History.md
|
106
|
-
- License.txt
|
107
|
-
- README.md
|
108
|
-
- bin/conjure
|
109
118
|
- lib/conjure.rb
|
119
|
+
- lib/conjure/instance.rb
|
120
|
+
- lib/conjure/target.rb
|
121
|
+
- lib/conjure/provider.rb
|
110
122
|
- lib/conjure/application.rb
|
111
|
-
- lib/conjure/
|
123
|
+
- lib/conjure/command_target.rb
|
124
|
+
- lib/conjure/log.rb
|
125
|
+
- lib/conjure/version.rb
|
112
126
|
- lib/conjure/config.rb
|
113
|
-
- lib/conjure/data_set.rb
|
114
127
|
- lib/conjure/identity.rb
|
115
|
-
- lib/conjure/
|
116
|
-
- lib/conjure/
|
117
|
-
- lib/conjure/
|
118
|
-
- lib/conjure/
|
119
|
-
- lib/conjure/
|
120
|
-
- lib/conjure/
|
121
|
-
- lib/conjure/service/database/mysql.rb
|
122
|
-
- lib/conjure/service/database/postgres.rb
|
128
|
+
- lib/conjure/provision/server.rb
|
129
|
+
- lib/conjure/provision/instance.rb
|
130
|
+
- lib/conjure/provision/docker_image.rb
|
131
|
+
- lib/conjure/provision/dockerfile.rb
|
132
|
+
- lib/conjure/command.rb
|
133
|
+
- lib/conjure/data_set.rb
|
123
134
|
- lib/conjure/service/digital_ocean_account.rb
|
124
|
-
- lib/conjure/service/docker_host.rb
|
125
|
-
- lib/conjure/service/docker_shell.rb
|
126
|
-
- lib/conjure/service/forwarded_shell.rb
|
127
135
|
- lib/conjure/service/rails_codebase.rb
|
128
|
-
- lib/conjure/service/
|
129
|
-
- lib/conjure/service/
|
136
|
+
- lib/conjure/service/database/postgres.rb
|
137
|
+
- lib/conjure/service/database/mysql.rb
|
130
138
|
- lib/conjure/service/rails_log_view.rb
|
139
|
+
- lib/conjure/service/docker_host.rb
|
140
|
+
- lib/conjure/service/database.rb
|
141
|
+
- lib/conjure/service/cloud_server.rb
|
142
|
+
- lib/conjure/service/forwarded_shell.rb
|
131
143
|
- lib/conjure/service/rails_server.rb
|
132
|
-
- lib/conjure/service/rake_task.rb
|
133
144
|
- lib/conjure/service/remote_file_set.rb
|
134
145
|
- lib/conjure/service/remote_shell.rb
|
146
|
+
- lib/conjure/service/rails_console.rb
|
147
|
+
- lib/conjure/service/rake_task.rb
|
148
|
+
- lib/conjure/service/docker_shell.rb
|
135
149
|
- lib/conjure/service/repository_link.rb
|
136
150
|
- lib/conjure/service/volume.rb
|
137
|
-
- lib/conjure/
|
138
|
-
- lib/conjure/
|
139
|
-
- lib/conjure/view/application_view.rb
|
151
|
+
- lib/conjure/provision.rb
|
152
|
+
- lib/conjure/notes.txt
|
140
153
|
- lib/conjure/view/table_view.rb
|
154
|
+
- lib/conjure/view/application_view.rb
|
155
|
+
- README.md
|
156
|
+
- History.md
|
157
|
+
- License.txt
|
158
|
+
- bin/conjure
|
141
159
|
homepage: http://github.com/brianauton/conjure
|
142
160
|
licenses:
|
143
161
|
- MIT
|
144
|
-
metadata: {}
|
145
162
|
post_install_message:
|
146
163
|
rdoc_options: []
|
147
164
|
require_paths:
|
148
165
|
- lib
|
149
166
|
required_ruby_version: !ruby/object:Gem::Requirement
|
167
|
+
none: false
|
150
168
|
requirements:
|
151
169
|
- - ">="
|
152
170
|
- !ruby/object:Gem::Version
|
153
171
|
version: '0'
|
154
172
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
155
174
|
requirements:
|
156
175
|
- - ">="
|
157
176
|
- !ruby/object:Gem::Version
|
158
177
|
version: 1.3.6
|
159
178
|
requirements: []
|
160
179
|
rubyforge_project:
|
161
|
-
rubygems_version:
|
180
|
+
rubygems_version: 1.8.28
|
162
181
|
signing_key:
|
163
|
-
specification_version:
|
182
|
+
specification_version: 3
|
164
183
|
summary: Magically powerful deployment for Rails applications
|
165
184
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: f83195bdf15bdf55eaa8d3ce3bb10ac1dae0caad
|
4
|
-
data.tar.gz: 37098f28d23149e7aebb192adf286884e81b77e7
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 5200a75af33b8dc94bb5046cbfbd5bcdcb37b3d528006df08728561a2970bc405289b403df10431c71621223a71e5ad1f8bf5bbed4c298416384bf91b7b0e5ab
|
7
|
-
data.tar.gz: 792f7d9702ffb3215e078126b5992bacc01799b455e491fa17ce47ba7a31fc9fd7d5ca18ea3ba87a819cd0b448ae6a44d53f18a1b8237582fef0d69b99f8d311
|
@@ -1,39 +0,0 @@
|
|
1
|
-
module Conjure
|
2
|
-
module Service
|
3
|
-
class RailsDeployment
|
4
|
-
def initialize(options)
|
5
|
-
@origin = options[:origin]
|
6
|
-
@branch = options[:branch] || "master"
|
7
|
-
@environment = "production"
|
8
|
-
@test = options[:test]
|
9
|
-
@target = options[:target]
|
10
|
-
end
|
11
|
-
|
12
|
-
def deploy
|
13
|
-
Log.info "[deploy] Deploying #{@branch} to #{@environment}"
|
14
|
-
unless @test
|
15
|
-
codebase.install
|
16
|
-
rails.run
|
17
|
-
Log.info "[deploy] Application deployed to #{@target.ip_address}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def database
|
22
|
-
codebase.database
|
23
|
-
end
|
24
|
-
|
25
|
-
def codebase
|
26
|
-
@codebase ||= Service::RailsCodebase.new @target, @origin, @branch, @environment
|
27
|
-
end
|
28
|
-
|
29
|
-
def rails
|
30
|
-
@rails ||= Service::RailsServer.new @target, @environment
|
31
|
-
end
|
32
|
-
|
33
|
-
def shell
|
34
|
-
rails.base_image
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|