conjure 0.1.8 → 0.2.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.
- 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
|
[](http://badge.fury.io/rb/conjure)
|
3
3
|
[](https://travis-ci.org/brianauton/conjure)
|
4
4
|
[](https://codeclimate.com/github/brianauton/conjure)
|
5
|
+
[](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
|
-
|