conjure 0.0.0 → 0.0.1
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 +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
|