conjure 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +7 -0
- data/README.md +85 -2
- data/lib/conjure.rb +1 -1
- data/lib/conjure/command.rb +51 -4
- data/lib/conjure/service.rb +5 -19
- data/lib/conjure/service/cloud_server.rb +108 -0
- data/lib/conjure/service/docker_host.rb +171 -0
- data/lib/conjure/service/postgres_client.rb +32 -0
- data/lib/conjure/service/postgres_server.rb +31 -0
- data/lib/conjure/service/rails_application.rb +9 -9
- data/lib/conjure/service/rails_server.rb +58 -21
- metadata +22 -4
- data/lib/conjure/service/rvm_shell.rb +0 -30
- data/lib/conjure/service/source_tree.rb +0 -25
data/History.md
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,88 @@
|
|
1
|
-
|
1
|
+
## Conjure
|
2
2
|
|
3
3
|
Magically powerful deployment for Rails applications.
|
4
4
|
|
5
|
-
|
5
|
+
### Requirements
|
6
|
+
|
7
|
+
Deploying a Rails application with Conjure currently requires the
|
8
|
+
following:
|
9
|
+
|
10
|
+
* A DigitalOcean account
|
11
|
+
|
12
|
+
* A Public/private SSH keypair to use for bootstrapping new cloud
|
13
|
+
servers. Generating a new keypair for this purpose is recommended.
|
14
|
+
|
15
|
+
Also, your Rails application requires all of the following:
|
16
|
+
|
17
|
+
* It must have a `.ruby-version` file indicating which version of
|
18
|
+
Ruby to run
|
19
|
+
|
20
|
+
* It must be able to run in production mode with a single
|
21
|
+
Postgres database (any existing database.yml will be ignored)
|
22
|
+
|
23
|
+
* It must be checked out locally into a git repository with a valid
|
24
|
+
`origin` remote
|
25
|
+
|
26
|
+
* The public SSH key you're using must have permission to check out
|
27
|
+
the project from `origin`
|
28
|
+
|
29
|
+
### Getting Started
|
30
|
+
|
31
|
+
First, install the Conjure gem by either adding it to your Gemfile
|
32
|
+
|
33
|
+
group :development do
|
34
|
+
gem "conjure"
|
35
|
+
end
|
36
|
+
|
37
|
+
and then running `bundle`, OR by installing it directly:
|
38
|
+
|
39
|
+
gem install conjure
|
40
|
+
|
41
|
+
Then add a file to your Rails project called
|
42
|
+
`config/conjure.yml`. This should be a YAML file with the following
|
43
|
+
fields (all fields are required):
|
44
|
+
|
45
|
+
* `digitalocean_client_id` and `digitalocean_api_key`: These
|
46
|
+
credentials are available after logging in to your Digital Ocean
|
47
|
+
account.
|
48
|
+
|
49
|
+
* `digitalocean_region`: The geographic region for deploying new
|
50
|
+
cloud servers. If unsure, use "New York 1".
|
51
|
+
|
52
|
+
* `private_key_file` and `public_key_file`: Pathnames to local files
|
53
|
+
(relative to your project's `config` directory) that contain the
|
54
|
+
private and public SSH keys to use for deployment. It's
|
55
|
+
recommended to generate a new keypair rather than using your
|
56
|
+
personal SSH keys, since Conjure currently copies the specified
|
57
|
+
private key to the server during deployment.
|
58
|
+
|
59
|
+
Here's an example conjure.yml file:
|
60
|
+
|
61
|
+
digitalocean_client_id: XXXXXXXX
|
62
|
+
digitalocean_api_key: XXXXXXXX
|
63
|
+
digitalocean_region: New York 1
|
64
|
+
private_key_file: conjure_key
|
65
|
+
public_key_file: conjure_key.pub
|
66
|
+
|
67
|
+
Finally, tell Conjure to deploy your app:
|
68
|
+
|
69
|
+
conjure deploy
|
70
|
+
|
71
|
+
The last line of the output will tell you the IP address of the
|
72
|
+
deployed server. Repeating the command will reuse the existing server
|
73
|
+
rather than deploying a new one.
|
74
|
+
|
75
|
+
### Additional Commands
|
76
|
+
|
77
|
+
These commands are available after you've deployed with `conjure
|
78
|
+
deploy`.
|
79
|
+
|
80
|
+
conjure export FILE
|
81
|
+
|
82
|
+
This will produce a Postgres SQL dump of the currently-deployed
|
83
|
+
server's production database, and save it to the local file `FILE`.
|
84
|
+
|
85
|
+
conjure import FILE
|
86
|
+
|
87
|
+
This will overwrite the production database on the currently-deployed
|
88
|
+
server with a Postgres SQL dump from the local file `FILE`.
|
data/lib/conjure.rb
CHANGED
data/lib/conjure/command.rb
CHANGED
@@ -1,10 +1,57 @@
|
|
1
1
|
module Conjure
|
2
2
|
class Command < Thor
|
3
3
|
desc "deploy", "Deploys the app"
|
4
|
-
def deploy
|
5
|
-
Service::RailsApplication.create
|
4
|
+
def deploy
|
5
|
+
Service::RailsApplication.create github_url, app_name, rails_environment, config(Dir.pwd)
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "import FILE", "Imports the production database from a postgres SQL dump"
|
9
|
+
def import(file)
|
10
|
+
Service::PostgresClient.create(docker_host, "#{app_name}_#{rails_environment}").import file
|
11
|
+
puts "[export] #{File.size file} bytes imported from #{file}"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "export FILE", "Exports the production database to a postgres SQL dump"
|
15
|
+
def export(file)
|
16
|
+
Service::PostgresClient.create(docker_host, "#{app_name}_#{rails_environment}").export file
|
17
|
+
puts "[export] #{File.size file} bytes exported to #{file}"
|
18
|
+
end
|
19
|
+
|
20
|
+
default_task :help
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def rails_environment
|
25
|
+
"production"
|
26
|
+
end
|
27
|
+
|
28
|
+
def docker_host
|
29
|
+
Service::DockerHost.create "#{app_name}-#{rails_environment}", config(Dir.pwd)
|
30
|
+
end
|
31
|
+
|
32
|
+
def config(source_path)
|
33
|
+
require "ostruct"
|
34
|
+
config_path = File.join source_path, "config", "conjure.yml"
|
35
|
+
data = YAML.load_file config_path
|
36
|
+
data["config_path"] = File.dirname config_path
|
37
|
+
OpenStruct.new data
|
38
|
+
end
|
39
|
+
|
40
|
+
def app_name
|
41
|
+
name_from_github_url github_url
|
42
|
+
end
|
43
|
+
|
44
|
+
def github_url
|
45
|
+
git_origin_url Dir.pwd
|
46
|
+
end
|
47
|
+
|
48
|
+
def git_origin_url(source_path)
|
49
|
+
remote_info = `cd #{source_path}; git remote -v |grep origin`
|
50
|
+
remote_info.match(/(git@github.com[^ ]+)/)[1]
|
51
|
+
end
|
52
|
+
|
53
|
+
def name_from_github_url(github_url)
|
54
|
+
github_url.match(/\/([^.]+)\.git$/)[1]
|
6
55
|
end
|
7
|
-
default_task :deploy
|
8
56
|
end
|
9
57
|
end
|
10
|
-
|
data/lib/conjure/service.rb
CHANGED
@@ -2,30 +2,16 @@ module Conjure
|
|
2
2
|
module Service
|
3
3
|
autoload :RailsApplication, "conjure/service/rails_application"
|
4
4
|
autoload :RailsServer, "conjure/service/rails_server"
|
5
|
-
autoload :RvmShell, "conjure/service/rvm_shell"
|
6
5
|
autoload :MachineInstance, "conjure/service/machine_instance"
|
7
|
-
autoload :
|
6
|
+
autoload :DockerHost, "conjure/service/docker_host"
|
7
|
+
autoload :CloudServer, "conjure/service/cloud_server"
|
8
|
+
autoload :PostgresServer, "conjure/service/postgres_server"
|
9
|
+
autoload :PostgresClient, "conjure/service/postgres_client"
|
8
10
|
end
|
9
11
|
|
10
12
|
class Basic
|
11
|
-
def dependencies
|
12
|
-
[]
|
13
|
-
end
|
14
|
-
|
15
|
-
def started?
|
16
|
-
false
|
17
|
-
end
|
18
|
-
|
19
|
-
def start
|
20
|
-
end
|
21
|
-
|
22
|
-
def save
|
23
|
-
dependencies.each &:start
|
24
|
-
start unless started?
|
25
|
-
end
|
26
|
-
|
27
13
|
def self.create(*args)
|
28
|
-
new(*args)
|
14
|
+
new(*args)
|
29
15
|
end
|
30
16
|
end
|
31
17
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class CloudServer < Basic
|
4
|
+
require "fog"
|
5
|
+
|
6
|
+
def initialize(name, config = {})
|
7
|
+
@name = name
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(command, options = {})
|
12
|
+
set_fog_credentials
|
13
|
+
upload_files options[:files].to_a
|
14
|
+
result = server.ssh(command).first
|
15
|
+
remove_files options[:files].to_a
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def upload_files(files)
|
20
|
+
dir_names = files.map{|local_path, remote_path| File.dirname remote_path}.uniq
|
21
|
+
server.ssh "mkdir -p #{dir_names.join ' '}" if dir_names.any?
|
22
|
+
files.each{|local_path, remote_path| server.scp local_path, remote_path}
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove_files(files)
|
26
|
+
files.each{|local_path, remote_path| server.ssh "rm -f #{remote_path}"}
|
27
|
+
end
|
28
|
+
|
29
|
+
def server
|
30
|
+
@server ||= existing_server
|
31
|
+
@server ||= new_server
|
32
|
+
@server.wait_for { ready? }
|
33
|
+
@server
|
34
|
+
end
|
35
|
+
|
36
|
+
def ip_address
|
37
|
+
@server.public_ip_address
|
38
|
+
end
|
39
|
+
|
40
|
+
def existing_server
|
41
|
+
server = connection.servers.find{|s| s.name == @name }
|
42
|
+
puts " [cloud] Using existing server #{@name}" if server
|
43
|
+
server
|
44
|
+
end
|
45
|
+
|
46
|
+
def new_server
|
47
|
+
puts " [cloud] Launching new server #{@name}"
|
48
|
+
connection.servers.bootstrap bootstrap_options.merge(fog_credentials)
|
49
|
+
end
|
50
|
+
|
51
|
+
def connection
|
52
|
+
@connection ||= Fog::Compute.new compute_options
|
53
|
+
end
|
54
|
+
|
55
|
+
def bootstrap_options
|
56
|
+
{
|
57
|
+
name: @name,
|
58
|
+
flavor_id: flavor_id,
|
59
|
+
region_id: region_id,
|
60
|
+
image_id: image_id,
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def compute_options
|
65
|
+
{
|
66
|
+
provider: :digitalocean,
|
67
|
+
digitalocean_api_key: config.digitalocean_api_key,
|
68
|
+
digitalocean_client_id: config.digitalocean_client_id,
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def flavor_id
|
73
|
+
@flavor_id ||= connection.flavors.find{|f| f.name == "512MB"}.id
|
74
|
+
end
|
75
|
+
|
76
|
+
def region_id
|
77
|
+
@region_id ||= connection.regions.find{|r| r.name == config.digitalocean_region}.id
|
78
|
+
end
|
79
|
+
|
80
|
+
def image_id
|
81
|
+
@image_id ||= connection.images.find{|r| r.name == "Ubuntu 13.04 x64"}.id
|
82
|
+
end
|
83
|
+
|
84
|
+
def config
|
85
|
+
@config
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_fog_credentials
|
89
|
+
Fog.credentials.merge! fog_credentials
|
90
|
+
end
|
91
|
+
|
92
|
+
def private_key_file
|
93
|
+
Pathname.new(config.config_path).join config.private_key_file
|
94
|
+
end
|
95
|
+
|
96
|
+
def public_key_file
|
97
|
+
Pathname.new(config.config_path).join config.public_key_file
|
98
|
+
end
|
99
|
+
|
100
|
+
def fog_credentials
|
101
|
+
{
|
102
|
+
private_key_path: private_key_file,
|
103
|
+
public_key_path: public_key_file,
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class DockerHost < Basic
|
4
|
+
VERBOSE = false
|
5
|
+
|
6
|
+
def initialize(server_name, config = {})
|
7
|
+
@server_name = server_name
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def server
|
12
|
+
@server ||= Service::CloudServer.create @server_name, config
|
13
|
+
end
|
14
|
+
|
15
|
+
def config
|
16
|
+
@config
|
17
|
+
end
|
18
|
+
|
19
|
+
def ip_address
|
20
|
+
@server.ip_address
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_docker_path
|
24
|
+
puts "[docker] Installing docker"
|
25
|
+
server.run "curl https://get.docker.io/gpg | apt-key add -"
|
26
|
+
server.run "echo 'deb https://get.docker.io/ubuntu docker main' >/etc/apt/sources.list.d/docker.list"
|
27
|
+
server.run "apt-get update"
|
28
|
+
server.run "DEBIAN_FRONTEND=noninteractive apt-get install -y linux-image-extra-`uname -r` lxc-docker"
|
29
|
+
existing_docker_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def existing_docker_path
|
33
|
+
path = server.run("which docker").stdout.to_s.strip
|
34
|
+
path = nil if path == ""
|
35
|
+
puts "[docker] Using installed #{path}" if path
|
36
|
+
path
|
37
|
+
end
|
38
|
+
|
39
|
+
def docker_path
|
40
|
+
@docker_path ||= existing_docker_path
|
41
|
+
@docker_path ||= new_docker_path
|
42
|
+
end
|
43
|
+
|
44
|
+
def command(command, options = {})
|
45
|
+
full_command = "#{docker_path} #{command}"
|
46
|
+
full_command = "nohup #{full_command}" if options[:nohup]
|
47
|
+
full_command = "echo '#{shell_escape options[:stdin]}' | #{full_command}" if options[:stdin]
|
48
|
+
puts " [scp] #{options[:files].inspect}" if VERBOSE and options[:files]
|
49
|
+
puts " [ssh] #{full_command}" if VERBOSE
|
50
|
+
result = server.run full_command, files: options[:files]
|
51
|
+
raise "Docker error: #{result.stdout} #{result.stderr}" unless result.status == 0
|
52
|
+
result.stdout
|
53
|
+
end
|
54
|
+
|
55
|
+
def shell_escape(text)
|
56
|
+
text.gsub "'", "'\"'\"'"
|
57
|
+
end
|
58
|
+
|
59
|
+
def containers
|
60
|
+
ContainerSet.new self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ContainerSet
|
65
|
+
def initialize(host)
|
66
|
+
@host = host
|
67
|
+
end
|
68
|
+
|
69
|
+
def create(options)
|
70
|
+
Container.new @host, options
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Container
|
75
|
+
def initialize(host, options)
|
76
|
+
@host = host
|
77
|
+
@label = options[:label]
|
78
|
+
@base_image = options[:base_image]
|
79
|
+
@ports = options[:ports].to_a
|
80
|
+
@volumes = options[:volumes].to_a
|
81
|
+
@setup_commands = options[:setup_commands].to_a
|
82
|
+
@daemon_command = options[:daemon_command]
|
83
|
+
@environment = options[:environment]
|
84
|
+
@files = options[:files]
|
85
|
+
end
|
86
|
+
|
87
|
+
def image_fingerprint
|
88
|
+
{base_image: @base_image, setup_commands: @setup_commands}
|
89
|
+
end
|
90
|
+
|
91
|
+
def image_name
|
92
|
+
hash = Digest::SHA1.hexdigest(image_fingerprint.to_yaml).first(12)
|
93
|
+
"#{@label}_#{hash}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def run
|
97
|
+
unless id
|
98
|
+
build
|
99
|
+
puts "[docker] Starting #{@label} image"
|
100
|
+
container_id = @host.command("run -d #{@label}").strip
|
101
|
+
if(!id)
|
102
|
+
output = @host.command "logs #{container_id}"
|
103
|
+
raise "Docker: #{@label} daemon exited with: #{output}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
puts "[docker] #{@label} is running at #{ip_address}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def raise_build_errors(build_output)
|
110
|
+
match = build_output.match(/Error build: The command \[([^\]]*)\] returned a non-zero code:/)
|
111
|
+
if match
|
112
|
+
failed_command = match[1]
|
113
|
+
last_section = build_output.split("--->").last
|
114
|
+
last_section.gsub!(/Running in [0-9a-f]+/, "")
|
115
|
+
last_section.gsub!(/Error build: The command.*/m, "")
|
116
|
+
raise "Docker: build step '#{failed_command}' failed: #{last_section.strip}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def raise_run_errors(run_output)
|
121
|
+
end
|
122
|
+
|
123
|
+
def dockerfile
|
124
|
+
lines = ["FROM #{@base_image}"]
|
125
|
+
lines += @environment.map{|k, v| "ENV #{k} #{v}"} if @environment
|
126
|
+
lines += @setup_commands.map{|c| "RUN #{c}"}
|
127
|
+
lines << "EXPOSE #{@ports.map{|p| "#{p}:#{p}"}.join ' '}" if @ports.to_a.any?
|
128
|
+
lines << "VOLUME #{@volumes.inspect}" if @volumes.to_a.any?
|
129
|
+
lines << "ENTRYPOINT #{@daemon_command}" if @daemon_command
|
130
|
+
lines.join "\n"
|
131
|
+
end
|
132
|
+
|
133
|
+
def build
|
134
|
+
puts "[docker] Building #{@label} image"
|
135
|
+
raise_build_errors(@host.command "build -t #{@label} -", stdin: dockerfile)
|
136
|
+
end
|
137
|
+
|
138
|
+
def command(command, options = {})
|
139
|
+
build
|
140
|
+
puts "[docker] Executing #{@label} image"
|
141
|
+
file_options = options[:files] ? "-v /files:/files" : ""
|
142
|
+
@host.command "run #{file_options} #{@label} #{command}", files: files_hash(options[:files])
|
143
|
+
end
|
144
|
+
|
145
|
+
def files_hash(files_array)
|
146
|
+
files_array.to_a.inject({}) do |hash, local_file|
|
147
|
+
hash.merge local_file => "/files/#{File.basename local_file}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def shell_command(command)
|
152
|
+
"bash -c '#{@host.shell_escape command}'"
|
153
|
+
end
|
154
|
+
|
155
|
+
def id
|
156
|
+
@id ||= @host.command("ps | grep #{@label}: ; true").strip.split("\n").first.to_s[0..11]
|
157
|
+
@id = nil if @id == ""
|
158
|
+
@id
|
159
|
+
end
|
160
|
+
|
161
|
+
def ip_address
|
162
|
+
status["NetworkSettings"]["IPAddress"]
|
163
|
+
end
|
164
|
+
|
165
|
+
def status
|
166
|
+
require "json"
|
167
|
+
JSON.parse(@host.command "inspect #{id}").first
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class PostgresClient < Basic
|
4
|
+
def initialize(host, db_name)
|
5
|
+
server = PostgresServer.create host
|
6
|
+
server.run
|
7
|
+
@server_ip = server.ip_address
|
8
|
+
@db_name = db_name
|
9
|
+
@container = host.containers.create(
|
10
|
+
label: "pgclient",
|
11
|
+
base_image: "ubuntu",
|
12
|
+
setup_commands: [
|
13
|
+
"apt-get install -y python-software-properties software-properties-common",
|
14
|
+
"add-apt-repository -y ppa:pitti/postgresql",
|
15
|
+
"apt-get update",
|
16
|
+
"apt-get install -y postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2",
|
17
|
+
],
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def export(file)
|
22
|
+
File.open file, "w" do |f|
|
23
|
+
f.write @container.command("/usr/lib/postgresql/9.2/bin/pg_dump -U root -h #{@server_ip} #{@db_name}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def import(file)
|
28
|
+
@container.command "/usr/lib/postgresql/9.2/bin/psql -U root -h #{@server_ip} -d #{@db_name} -f /files/#{File.basename file}", files: [file]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class PostgresServer < Basic
|
4
|
+
def initialize(host)
|
5
|
+
@container = host.containers.create(
|
6
|
+
label: "postgres",
|
7
|
+
base_image: "ubuntu",
|
8
|
+
setup_commands: [
|
9
|
+
"apt-get install -y python-software-properties software-properties-common",
|
10
|
+
"add-apt-repository -y ppa:pitti/postgresql",
|
11
|
+
"apt-get update",
|
12
|
+
"apt-get install -y postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2",
|
13
|
+
"service postgresql start; su postgres -c 'createuser -d -r -s root; createdb -O root root'; service postgresql stop",
|
14
|
+
"echo 'host all all 0.0.0.0/0 trust' >>/etc/postgresql/9.2/main/pg_hba.conf",
|
15
|
+
"echo \"listen_addresses='*'\" >>/etc/postgresql/9.2/main/postgresql.conf",
|
16
|
+
],
|
17
|
+
daemon_command: "su postgres -c '/usr/lib/postgresql/9.2/bin/postgres -c config_file=/etc/postgresql/9.2/main/postgresql.conf'",
|
18
|
+
volumes: ["/var/lib/postgresql/9.2/main"],
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
@container.run
|
24
|
+
end
|
25
|
+
|
26
|
+
def ip_address
|
27
|
+
@container.ip_address
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module Conjure
|
2
2
|
module Service
|
3
3
|
class RailsApplication < Basic
|
4
|
-
def initialize(
|
5
|
-
|
6
|
-
@
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
[
|
4
|
+
def initialize(github_url, name = "myapp", environment = "production", config = {})
|
5
|
+
@name = name
|
6
|
+
@environment = environment
|
7
|
+
docker = Service::DockerHost.create "#{name}-#{environment}", config
|
8
|
+
postgres = Service::PostgresServer.create docker
|
9
|
+
postgres.run
|
10
|
+
rails = Service::RailsServer.create docker, github_url, name, postgres.ip_address, environment
|
11
|
+
rails.run
|
12
|
+
puts "[deploy] Application deployed to #{docker.ip_address}"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,35 +1,72 @@
|
|
1
1
|
module Conjure
|
2
2
|
module Service
|
3
3
|
class RailsServer < Basic
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
@source_tree = source_tree
|
8
|
-
@rvm_shell = Service::RvmShell.new instance, "1.9.3", "codebase"
|
4
|
+
def file_contents(config, file_path)
|
5
|
+
file_path = File.join config.config_path, file_path
|
6
|
+
`cat #{file_path}`
|
9
7
|
end
|
10
8
|
|
11
|
-
def
|
12
|
-
|
9
|
+
def initialize(host, github_url, app_name, database_ip_address, rails_environment = "production")
|
10
|
+
config = host.config
|
11
|
+
ruby_version = file_contents(config, "../.ruby-version").strip
|
12
|
+
github_private_key = file_contents(config, config.private_key_file).gsub("\n", "\\n")
|
13
|
+
github_public_key = file_contents(config, config.public_key_file).gsub("\n", "\\n")
|
14
|
+
@container = host.containers.create(
|
15
|
+
label: "rails",
|
16
|
+
base_image: "ubuntu",
|
17
|
+
setup_commands: [
|
18
|
+
"apt-get install -y curl git",
|
19
|
+
"curl -L https://get.rvm.io | bash -s stable",
|
20
|
+
"rvm install #{ruby_version}",
|
21
|
+
"bash -c 'source /usr/local/rvm/scripts/rvm; rvm use #{ruby_version}@global --default'",
|
22
|
+
"mkdir -p /root/.ssh; echo '#{github_private_key}' > /root/.ssh/id_rsa",
|
23
|
+
"mkdir -p /root/.ssh; echo '#{github_public_key}' > /root/.ssh/id_rsa.pub",
|
24
|
+
"chmod -R go-rwx /root/.ssh",
|
25
|
+
"echo 'Host github.com\\n\\tStrictHostKeyChecking no\\n' >> /root/.ssh/config",
|
26
|
+
"git clone #{github_url}",
|
27
|
+
"apt-get install -y #{apt_packages_required_for_gems.join ' '}",
|
28
|
+
"cd #{app_name}; bundle --deployment",
|
29
|
+
|
30
|
+
"echo 'deb http://us.archive.ubuntu.com/ubuntu/ precise universe' >>/etc/apt/sources.list",
|
31
|
+
"apt-get install -y python-software-properties software-properties-common",
|
32
|
+
"add-apt-repository -y ppa:chris-lea/node.js-legacy",
|
33
|
+
"apt-get update",
|
34
|
+
"apt-get install -y nodejs",
|
35
|
+
|
36
|
+
"echo '#{database_yml database_ip_address, app_name, rails_environment}' >/#{app_name}/config/database.yml",
|
37
|
+
"cd #{app_name}; bundle exec rake db:setup",
|
38
|
+
],
|
39
|
+
environment: {
|
40
|
+
PATH:"/usr/local/rvm/gems/ruby-1.9.3-p448@global/bin:/usr/local/rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
41
|
+
RAILS_ENV: rails_environment,
|
42
|
+
GITHUB_TOKEN: ENV["GITHUB_TOKEN"],
|
43
|
+
FRECKLE_SUBDOMAIN: "neomind",
|
44
|
+
},
|
45
|
+
daemon_command: "cd #{app_name}; bundle exec rails server -p 80",
|
46
|
+
ports: [80],
|
47
|
+
volumes: ["/#{app_name}/log"],
|
48
|
+
)
|
13
49
|
end
|
14
50
|
|
15
|
-
def
|
16
|
-
|
51
|
+
def run
|
52
|
+
@container.run
|
17
53
|
end
|
18
54
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
55
|
+
def database_yml(database_ip_address, app_name, rails_environment)
|
56
|
+
{
|
57
|
+
rails_environment => {
|
58
|
+
"adapter" => "postgresql",
|
59
|
+
"database" => "#{app_name}_#{rails_environment}",
|
60
|
+
"encoding" => "utf8",
|
61
|
+
"host" => database_ip_address,
|
62
|
+
"username" => "root",
|
63
|
+
"template" => "template0",
|
64
|
+
}
|
65
|
+
}.to_yaml.gsub("\n", "\\n")
|
28
66
|
end
|
29
67
|
|
30
|
-
def
|
31
|
-
|
32
|
-
@rvm_shell.execute command
|
68
|
+
def apt_packages_required_for_gems
|
69
|
+
["libpq-dev"]
|
33
70
|
end
|
34
71
|
end
|
35
72
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conjure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-10-
|
12
|
+
date: 2013-10-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fog
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.15.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.15.0
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: thor
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,11 +68,13 @@ extensions: []
|
|
52
68
|
extra_rdoc_files: []
|
53
69
|
files:
|
54
70
|
- lib/conjure.rb
|
71
|
+
- lib/conjure/service/cloud_server.rb
|
55
72
|
- lib/conjure/service/rails_server.rb
|
73
|
+
- lib/conjure/service/postgres_server.rb
|
56
74
|
- lib/conjure/service/rails_application.rb
|
57
75
|
- lib/conjure/service/machine_instance.rb
|
58
|
-
- lib/conjure/service/
|
59
|
-
- lib/conjure/service/
|
76
|
+
- lib/conjure/service/postgres_client.rb
|
77
|
+
- lib/conjure/service/docker_host.rb
|
60
78
|
- lib/conjure/service.rb
|
61
79
|
- lib/conjure/command.rb
|
62
80
|
- README.md
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Conjure
|
2
|
-
module Service
|
3
|
-
class RvmShell < Basic
|
4
|
-
def initialize(machine_instance, ruby_version, gemset)
|
5
|
-
@instance = machine_instance
|
6
|
-
@ruby_version = ruby_version
|
7
|
-
@gemset = gemset
|
8
|
-
end
|
9
|
-
|
10
|
-
def dependences
|
11
|
-
[@instance]
|
12
|
-
end
|
13
|
-
|
14
|
-
def start
|
15
|
-
shell "sudo apt-get -y install curl libyaml-dev build-essential libsqlite3-dev nodejs"
|
16
|
-
shell "curl -L https://get.rvm.io | bash -s -- --ignore-dotfiles"
|
17
|
-
shell "echo \"source $HOME/.rvm/scripts/rvm\" >> ~/.bash_profile"
|
18
|
-
shell "source $HOME/.rvm/scripts/rvm"
|
19
|
-
shell "rvm install #{@ruby_version}"
|
20
|
-
shell "rvm use #{@ruby_version}@#{@gemset} --create --default"
|
21
|
-
end
|
22
|
-
|
23
|
-
def execute(command)
|
24
|
-
command.gsub! "'", "'\\\\''"
|
25
|
-
command = "bash --login -c '#{command}' 2>&1"
|
26
|
-
@instance.remote_command_output command
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module Conjure
|
2
|
-
module Service
|
3
|
-
class SourceTree < Basic
|
4
|
-
def initialize(source_path, machine_instance)
|
5
|
-
@source_path = source_path
|
6
|
-
@instance = machine_instance
|
7
|
-
end
|
8
|
-
|
9
|
-
def copy_to(instance, dest_path)
|
10
|
-
options = "-a -e \"ssh #{instance.ssh_options}\""
|
11
|
-
source = "#{@source_path}/"
|
12
|
-
dest = "#{instance.ssh_address}:#{dest_path}"
|
13
|
-
system "rsync #{options} #{source} #{dest}"
|
14
|
-
end
|
15
|
-
|
16
|
-
def start(dest_path="codebase")
|
17
|
-
puts "Transferring source code..."
|
18
|
-
copy_to @instance, dest_path
|
19
|
-
size = "#{@instance.remote_command_output "du -hs #{dest_path}"}"
|
20
|
-
size = size.split(/\s/).first
|
21
|
-
puts "Installed codebase size is #{size}."
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|