conjure 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +8 -0
- data/README.md +36 -6
- data/bin/conjure +0 -1
- data/lib/conjure.rb +5 -1
- data/lib/conjure/command.rb +37 -28
- data/lib/conjure/config.rb +11 -0
- data/lib/conjure/service.rb +8 -2
- data/lib/conjure/service/cloud_server.rb +17 -14
- data/lib/conjure/service/docker_host.rb +105 -41
- data/lib/conjure/service/postgres_database.rb +69 -0
- data/lib/conjure/service/rails_application.rb +36 -9
- data/lib/conjure/service/rails_codebase.rb +71 -0
- data/lib/conjure/service/rails_server.rb +78 -35
- data/lib/conjure/service/remote_shell.rb +61 -0
- metadata +38 -4
- data/lib/conjure/service/postgres_client.rb +0 -32
- data/lib/conjure/service/postgres_server.rb +0 -31
data/History.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
## Conjure
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/conjure.png)](http://badge.fury.io/rb/conjure)
|
3
|
+
[![Build Status](https://travis-ci.org/brianauton/conjure.png?branch=master)](https://travis-ci.org/brianauton/conjure)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/brianauton/conjure.png)](https://codeclimate.com/github/brianauton/conjure)
|
2
5
|
|
3
6
|
Magically powerful deployment for Rails applications.
|
4
7
|
|
@@ -70,19 +73,46 @@ Finally, tell Conjure to deploy your app:
|
|
70
73
|
|
71
74
|
The last line of the output will tell you the IP address of the
|
72
75
|
deployed server. Repeating the command will reuse the existing server
|
73
|
-
rather than deploying a new one.
|
76
|
+
rather than deploying a new one. Specify a branch to deploy with
|
77
|
+
`--branch` or `-b` (default is `master`):
|
78
|
+
|
79
|
+
conjure deploy --branch=mybranch
|
74
80
|
|
75
81
|
### Additional Commands
|
76
82
|
|
77
|
-
|
83
|
+
Additional commands are available after you've deployed with `conjure
|
78
84
|
deploy`.
|
79
85
|
|
86
|
+
#### Export
|
87
|
+
|
88
|
+
Produce a Postgres SQL dump of the currently-deployed server's
|
89
|
+
production database, and save it to the local file `FILE`.
|
90
|
+
|
80
91
|
conjure export FILE
|
81
92
|
|
82
|
-
|
83
|
-
|
93
|
+
#### Import
|
94
|
+
|
95
|
+
Overwrite the production database on the currently-deployed server
|
96
|
+
with a Postgres SQL dump from the local file `FILE`.
|
84
97
|
|
85
98
|
conjure import FILE
|
86
99
|
|
87
|
-
|
88
|
-
|
100
|
+
#### Log
|
101
|
+
|
102
|
+
Show logs from the deployed application. Optionally specify the number
|
103
|
+
of lines with `-n`, and use --tail to continue streaming new lines as
|
104
|
+
they are added to the log.
|
105
|
+
|
106
|
+
conjure log [-n=NUM] [--tail|-t]
|
107
|
+
|
108
|
+
#### Console
|
109
|
+
|
110
|
+
Open a console on the deployed application.
|
111
|
+
|
112
|
+
conjure console
|
113
|
+
|
114
|
+
#### Rake
|
115
|
+
|
116
|
+
Run a rake task on the deployed application and show the output.
|
117
|
+
|
118
|
+
conjure rake [ARGUMENTS...]
|
data/bin/conjure
CHANGED
data/lib/conjure.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module Conjure
|
2
2
|
|
3
|
-
VERSION = "0.0
|
3
|
+
VERSION = "0.1.0" unless defined?(VERSION)
|
4
4
|
autoload :Command, "conjure/command"
|
5
|
+
autoload :Config, "conjure/config"
|
5
6
|
autoload :Service, "conjure/service"
|
6
7
|
|
8
|
+
def self.config
|
9
|
+
@config ||= Config.load Dir.pwd
|
10
|
+
end
|
7
11
|
end
|
data/lib/conjure/command.rb
CHANGED
@@ -1,44 +1,57 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
1
3
|
module Conjure
|
2
4
|
class Command < Thor
|
3
|
-
|
5
|
+
attr_accessor :application_options
|
6
|
+
|
7
|
+
desc "deploy", "Deploy the app"
|
8
|
+
method_option :branch, :aliases => "-b", :type => :string, :desc => "Specify branch to deploy"
|
9
|
+
method_option :test, :type => :boolean, :desc => "Describe the deploy settings but don't deploy"
|
10
|
+
method_option :origin, :type => :string, :desc => "Specify git URL to deploy from"
|
4
11
|
def deploy
|
5
|
-
|
12
|
+
self.application_options = {
|
13
|
+
:branch => options[:branch],
|
14
|
+
:test => options[:test],
|
15
|
+
:origin => options[:origin],
|
16
|
+
}
|
17
|
+
application.deploy
|
6
18
|
end
|
7
19
|
|
8
|
-
desc "import FILE", "
|
20
|
+
desc "import FILE", "Import the production database from a postgres SQL dump"
|
9
21
|
def import(file)
|
10
|
-
|
11
|
-
puts "[export] #{File.size file} bytes imported from #{file}"
|
22
|
+
application.database.import file
|
12
23
|
end
|
13
24
|
|
14
|
-
desc "export FILE", "
|
25
|
+
desc "export FILE", "Export the production database to a postgres SQL dump"
|
15
26
|
def export(file)
|
16
|
-
|
17
|
-
puts "[export] #{File.size file} bytes exported to #{file}"
|
27
|
+
application.database.export file
|
18
28
|
end
|
19
29
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
"production"
|
30
|
+
desc "log", "Display the Rails log from the deployed application"
|
31
|
+
method_option :num, :aliases => "-n", :type => :numeric, :default => 10, :desc => "Show N lines of output"
|
32
|
+
method_option :tail, :aliases => "-t", :type => :boolean, :desc => "Continue streaming new log entries"
|
33
|
+
def log
|
34
|
+
application.rails.log :lines => options[:num], :tail => options[:tail]
|
26
35
|
end
|
27
36
|
|
28
|
-
|
29
|
-
|
37
|
+
desc "rake [ARGUMENTS...]", "Run the specified rake task on the deployed application"
|
38
|
+
def rake(*arguments)
|
39
|
+
application.rails.rake arguments.join(" ")
|
30
40
|
end
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
data = YAML.load_file config_path
|
36
|
-
data["config_path"] = File.dirname config_path
|
37
|
-
OpenStruct.new data
|
42
|
+
desc "console", "Start a console on the deployed application"
|
43
|
+
def console
|
44
|
+
application.rails.console
|
38
45
|
end
|
39
46
|
|
40
|
-
|
41
|
-
|
47
|
+
default_task :help
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def application
|
52
|
+
self.application_options ||= {}
|
53
|
+
self.application_options[:origin] ||= github_url
|
54
|
+
Service::RailsApplication.create self.application_options
|
42
55
|
end
|
43
56
|
|
44
57
|
def github_url
|
@@ -49,9 +62,5 @@ module Conjure
|
|
49
62
|
remote_info = `cd #{source_path}; git remote -v |grep origin`
|
50
63
|
remote_info.match(/(git@github.com[^ ]+)/)[1]
|
51
64
|
end
|
52
|
-
|
53
|
-
def name_from_github_url(github_url)
|
54
|
-
github_url.match(/\/([^.]+)\.git$/)[1]
|
55
|
-
end
|
56
65
|
end
|
57
66
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Conjure
|
2
|
+
class Config
|
3
|
+
def self.load(root_path)
|
4
|
+
require "ostruct"
|
5
|
+
config_path = File.join root_path, "config", "conjure.yml"
|
6
|
+
data = YAML.load_file config_path
|
7
|
+
data["config_path"] = File.dirname config_path
|
8
|
+
OpenStruct.new data
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/conjure/service.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
module Conjure
|
2
2
|
module Service
|
3
3
|
autoload :RailsApplication, "conjure/service/rails_application"
|
4
|
+
autoload :RailsCodebase, "conjure/service/rails_codebase"
|
4
5
|
autoload :RailsServer, "conjure/service/rails_server"
|
5
6
|
autoload :MachineInstance, "conjure/service/machine_instance"
|
6
7
|
autoload :DockerHost, "conjure/service/docker_host"
|
7
8
|
autoload :CloudServer, "conjure/service/cloud_server"
|
8
|
-
autoload :
|
9
|
-
autoload :
|
9
|
+
autoload :PostgresDatabase, "conjure/service/postgres_database"
|
10
|
+
autoload :RemoteShell, "conjure/service/remote_shell"
|
10
11
|
end
|
11
12
|
|
12
13
|
class Basic
|
13
14
|
def self.create(*args)
|
14
15
|
new(*args)
|
15
16
|
end
|
17
|
+
|
18
|
+
def file_contents(file_path)
|
19
|
+
file_path = File.join Conjure.config.config_path, file_path
|
20
|
+
`cat #{file_path}`
|
21
|
+
end
|
16
22
|
end
|
17
23
|
end
|
@@ -3,15 +3,14 @@ module Conjure
|
|
3
3
|
class CloudServer < Basic
|
4
4
|
require "fog"
|
5
5
|
|
6
|
-
def initialize(name
|
6
|
+
def initialize(name)
|
7
7
|
@name = name
|
8
|
-
@config = config
|
9
8
|
end
|
10
9
|
|
11
|
-
def run(command, options = {})
|
10
|
+
def run(command, options = {}, &block)
|
12
11
|
set_fog_credentials
|
13
12
|
upload_files options[:files].to_a
|
14
|
-
result =
|
13
|
+
result = remote_shell.run(command, :stream_stdin => options[:stream_stdin], &block)
|
15
14
|
remove_files options[:files].to_a
|
16
15
|
result
|
17
16
|
end
|
@@ -23,7 +22,7 @@ module Conjure
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def remove_files(files)
|
26
|
-
files.each{|local_path, remote_path|
|
25
|
+
files.each{|local_path, remote_path| run "rm -f #{remote_path}"}
|
27
26
|
end
|
28
27
|
|
29
28
|
def server
|
@@ -64,8 +63,8 @@ module Conjure
|
|
64
63
|
def compute_options
|
65
64
|
{
|
66
65
|
provider: :digitalocean,
|
67
|
-
digitalocean_api_key: config.digitalocean_api_key,
|
68
|
-
digitalocean_client_id: config.digitalocean_client_id,
|
66
|
+
digitalocean_api_key: Conjure.config.digitalocean_api_key,
|
67
|
+
digitalocean_client_id: Conjure.config.digitalocean_client_id,
|
69
68
|
}
|
70
69
|
end
|
71
70
|
|
@@ -74,27 +73,23 @@ module Conjure
|
|
74
73
|
end
|
75
74
|
|
76
75
|
def region_id
|
77
|
-
@region_id ||= connection.regions.find{|r| r.name == config.digitalocean_region}.id
|
76
|
+
@region_id ||= connection.regions.find{|r| r.name == Conjure.config.digitalocean_region}.id
|
78
77
|
end
|
79
78
|
|
80
79
|
def image_id
|
81
80
|
@image_id ||= connection.images.find{|r| r.name == "Ubuntu 13.04 x64"}.id
|
82
81
|
end
|
83
82
|
|
84
|
-
def config
|
85
|
-
@config
|
86
|
-
end
|
87
|
-
|
88
83
|
def set_fog_credentials
|
89
84
|
Fog.credentials.merge! fog_credentials
|
90
85
|
end
|
91
86
|
|
92
87
|
def private_key_file
|
93
|
-
Pathname.new(config.config_path).join config.private_key_file
|
88
|
+
Pathname.new(Conjure.config.config_path).join Conjure.config.private_key_file
|
94
89
|
end
|
95
90
|
|
96
91
|
def public_key_file
|
97
|
-
Pathname.new(config.config_path).join config.public_key_file
|
92
|
+
Pathname.new(Conjure.config.config_path).join Conjure.config.public_key_file
|
98
93
|
end
|
99
94
|
|
100
95
|
def fog_credentials
|
@@ -103,6 +98,14 @@ module Conjure
|
|
103
98
|
public_key_path: public_key_file,
|
104
99
|
}
|
105
100
|
end
|
101
|
+
|
102
|
+
def remote_shell
|
103
|
+
@remote_shell ||= RemoteShell.new(
|
104
|
+
:ip_address => server.public_ip_address,
|
105
|
+
:username => "root",
|
106
|
+
:private_key_path => private_key_file,
|
107
|
+
)
|
108
|
+
end
|
106
109
|
end
|
107
110
|
end
|
108
111
|
end
|
@@ -3,17 +3,12 @@ module Conjure
|
|
3
3
|
class DockerHost < Basic
|
4
4
|
VERBOSE = false
|
5
5
|
|
6
|
-
def initialize(server_name
|
6
|
+
def initialize(server_name)
|
7
7
|
@server_name = server_name
|
8
|
-
@config = config
|
9
8
|
end
|
10
9
|
|
11
10
|
def server
|
12
|
-
@server ||= Service::CloudServer.create @server_name
|
13
|
-
end
|
14
|
-
|
15
|
-
def config
|
16
|
-
@config
|
11
|
+
@server ||= Service::CloudServer.create @server_name
|
17
12
|
end
|
18
13
|
|
19
14
|
def ip_address
|
@@ -22,6 +17,8 @@ module Conjure
|
|
22
17
|
|
23
18
|
def new_docker_path
|
24
19
|
puts "[docker] Installing docker"
|
20
|
+
server.run "dd if=/dev/zero of=/root/swapfile bs=1024 count=524288"
|
21
|
+
server.run "mkswap /root/swapfile; swapon /root/swapfile"
|
25
22
|
server.run "curl https://get.docker.io/gpg | apt-key add -"
|
26
23
|
server.run "echo 'deb https://get.docker.io/ubuntu docker main' >/etc/apt/sources.list.d/docker.list"
|
27
24
|
server.run "apt-get update"
|
@@ -41,47 +38,52 @@ module Conjure
|
|
41
38
|
@docker_path ||= new_docker_path
|
42
39
|
end
|
43
40
|
|
44
|
-
def command(command, options = {})
|
41
|
+
def command(command, options = {}, &block)
|
45
42
|
full_command = "#{docker_path} #{command}"
|
46
43
|
full_command = "nohup #{full_command}" if options[:nohup]
|
47
44
|
full_command = "echo '#{shell_escape options[:stdin]}' | #{full_command}" if options[:stdin]
|
48
45
|
puts " [scp] #{options[:files].inspect}" if VERBOSE and options[:files]
|
49
46
|
puts " [ssh] #{full_command}" if VERBOSE
|
50
|
-
result = server.run full_command,
|
47
|
+
result = server.run full_command, :stream_stdin => options[:stream_stdin], :files => options[:files], &block
|
51
48
|
raise "Docker error: #{result.stdout} #{result.stderr}" unless result.status == 0
|
52
49
|
result.stdout
|
53
50
|
end
|
54
51
|
|
55
|
-
def
|
56
|
-
|
52
|
+
def ensure_host_directory(dir)
|
53
|
+
server.run "mkdir -p #{dir}"
|
57
54
|
end
|
58
55
|
|
59
56
|
def shell_escape(text)
|
60
57
|
text.gsub "'", "'\"'\"'"
|
61
58
|
end
|
62
59
|
|
60
|
+
def images
|
61
|
+
ImageSet.new self
|
62
|
+
end
|
63
|
+
|
63
64
|
def containers
|
64
|
-
ContainerSet.new self
|
65
|
+
ContainerSet.new :host => self
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
68
|
-
class
|
69
|
+
class ImageSet
|
69
70
|
def initialize(host)
|
70
71
|
@host = host
|
71
72
|
end
|
72
73
|
|
73
74
|
def create(options)
|
74
|
-
|
75
|
+
Image.new @host, options
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
78
|
-
class
|
79
|
+
class Image
|
79
80
|
def initialize(host, options)
|
80
81
|
@host = host
|
81
82
|
@label = options[:label]
|
82
83
|
@base_image = options[:base_image]
|
83
84
|
@ports = options[:ports].to_a
|
84
85
|
@volumes = options[:volumes].to_a
|
86
|
+
@host_volumes = options[:host_volumes]
|
85
87
|
@setup_commands = options[:setup_commands].to_a
|
86
88
|
@daemon_command = options[:daemon_command]
|
87
89
|
@environment = options[:environment]
|
@@ -89,25 +91,35 @@ module Conjure
|
|
89
91
|
end
|
90
92
|
|
91
93
|
def image_fingerprint
|
92
|
-
|
94
|
+
Digest::SHA1.hexdigest(dockerfile)[0..11]
|
95
|
+
end
|
96
|
+
|
97
|
+
def expected_image_name
|
98
|
+
"#{@label}:#{image_fingerprint}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def installed_image_name
|
102
|
+
build unless image_installed?
|
103
|
+
expected_image_name
|
93
104
|
end
|
94
105
|
|
95
|
-
def
|
96
|
-
|
97
|
-
"#{@label}_#{hash}"
|
106
|
+
def image_installed?
|
107
|
+
@host.command("history #{expected_image_name}") rescue false
|
98
108
|
end
|
99
109
|
|
100
|
-
def run
|
101
|
-
unless
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
110
|
+
def run(command = "")
|
111
|
+
unless running_container
|
112
|
+
puts "[docker] Starting #{@label}"
|
113
|
+
run_options = @host_volumes ? host_volume_options(@host_volumes) : ""
|
114
|
+
command = shell_command command if command != ""
|
115
|
+
container_id = @host.command("run #{run_options} -d #{installed_image_name} #{command}").strip
|
116
|
+
if(!running_container)
|
106
117
|
output = @host.command "logs #{container_id}"
|
107
118
|
raise "Docker: #{@label} daemon exited with: #{output}"
|
108
119
|
end
|
109
120
|
end
|
110
|
-
puts "[docker] #{@label} is running at #{ip_address}"
|
121
|
+
puts "[docker] #{@label} is running at #{running_container.ip_address}"
|
122
|
+
running_container
|
111
123
|
end
|
112
124
|
|
113
125
|
def raise_build_errors(build_output)
|
@@ -121,11 +133,8 @@ module Conjure
|
|
121
133
|
end
|
122
134
|
end
|
123
135
|
|
124
|
-
def raise_run_errors(run_output)
|
125
|
-
end
|
126
|
-
|
127
136
|
def dockerfile
|
128
|
-
lines = ["FROM #{
|
137
|
+
lines = ["FROM #{base_image_name}"]
|
129
138
|
lines += @environment.map{|k, v| "ENV #{k} #{v}"} if @environment
|
130
139
|
lines += @setup_commands.map{|c| "RUN #{c}"}
|
131
140
|
lines << "EXPOSE #{@ports.map{|p| "#{p}:#{p}"}.join ' '}" if @ports.to_a.any?
|
@@ -134,17 +143,30 @@ module Conjure
|
|
134
143
|
lines.join "\n"
|
135
144
|
end
|
136
145
|
|
146
|
+
def base_image_name
|
147
|
+
@base_image.respond_to?(:installed_image_name) ? @base_image.installed_image_name : @base_image
|
148
|
+
end
|
149
|
+
|
137
150
|
def build
|
151
|
+
destroy_instances
|
138
152
|
puts "[docker] Building #{@label} image"
|
139
|
-
raise_build_errors(@host.command "build -t #{
|
140
|
-
@host.
|
153
|
+
raise_build_errors(@host.command "build -t #{expected_image_name} -", stdin: dockerfile)
|
154
|
+
@host.containers.destroy_all_stopped
|
141
155
|
end
|
142
156
|
|
143
|
-
def command(command, options = {})
|
144
|
-
|
145
|
-
puts "[docker] Executing #{@label} image"
|
157
|
+
def command(command, options = {}, &block)
|
158
|
+
destroy_instances
|
146
159
|
file_options = options[:files] ? "-v /files:/files" : ""
|
147
|
-
|
160
|
+
file_options += " "+host_volume_options(@host_volumes) if @host_volumes
|
161
|
+
file_options += " -i" if options[:stream_stdin]
|
162
|
+
@host.command "run #{file_options} #{installed_image_name} #{shell_command command}", :stream_stdin => options[:stream_stdin], :files => files_hash(options[:files]), &block
|
163
|
+
end
|
164
|
+
|
165
|
+
def host_volume_options(host_volumes)
|
166
|
+
host_volumes.map do |host_path, container_path|
|
167
|
+
@host.ensure_host_directory host_path
|
168
|
+
"-v=#{host_path}:#{container_path}:rw"
|
169
|
+
end.join " "
|
148
170
|
end
|
149
171
|
|
150
172
|
def files_hash(files_array)
|
@@ -157,10 +179,52 @@ module Conjure
|
|
157
179
|
"bash -c '#{@host.shell_escape command}'"
|
158
180
|
end
|
159
181
|
|
160
|
-
def
|
161
|
-
@
|
162
|
-
|
163
|
-
|
182
|
+
def running_container
|
183
|
+
@runnning_container ||= @host.containers.find(:image_name => expected_image_name)
|
184
|
+
end
|
185
|
+
|
186
|
+
def destroy_instances
|
187
|
+
@host.containers.destroy_all :image_name => @label
|
188
|
+
end
|
189
|
+
|
190
|
+
def stop
|
191
|
+
destroy_instances
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class ContainerSet
|
196
|
+
attr_accessor :host
|
197
|
+
|
198
|
+
def initialize(options)
|
199
|
+
self.host = options[:host]
|
200
|
+
end
|
201
|
+
|
202
|
+
def find(options)
|
203
|
+
image_name = options[:image_name]
|
204
|
+
id = host.command("ps | grep #{image_name} ; true").strip.split("\n").first.to_s[0..11]
|
205
|
+
id = nil if id == ""
|
206
|
+
Container.new(:host => host, :id => id) if id
|
207
|
+
end
|
208
|
+
|
209
|
+
def destroy_all_stopped
|
210
|
+
host.command "rm `#{host.docker_path} ps -a -q`"
|
211
|
+
end
|
212
|
+
|
213
|
+
def destroy_all(options)
|
214
|
+
while container = find(:image_name => options[:image_name]) do
|
215
|
+
puts "[docker] Stopping #{options[:image_name]}"
|
216
|
+
host.command "stop #{container.id}"
|
217
|
+
host.command "rm #{container.id}"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class Container
|
223
|
+
attr_accessor :id, :host
|
224
|
+
|
225
|
+
def initialize(options)
|
226
|
+
self.id = options[:id]
|
227
|
+
self.host = options[:host]
|
164
228
|
end
|
165
229
|
|
166
230
|
def ip_address
|
@@ -169,7 +233,7 @@ module Conjure
|
|
169
233
|
|
170
234
|
def status
|
171
235
|
require "json"
|
172
|
-
JSON.parse(
|
236
|
+
JSON.parse(host.command "inspect #{id}").first
|
173
237
|
end
|
174
238
|
end
|
175
239
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class PostgresDatabase < Basic
|
4
|
+
def initialize(host, db_name)
|
5
|
+
@host = host
|
6
|
+
@db_name = db_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def base_image
|
10
|
+
@base_image ||= @host.images.create(
|
11
|
+
label: "postgres",
|
12
|
+
base_image: "ubuntu",
|
13
|
+
setup_commands: [
|
14
|
+
"apt-get install -y python-software-properties software-properties-common",
|
15
|
+
"add-apt-repository -y ppa:pitti/postgresql",
|
16
|
+
"apt-get update",
|
17
|
+
"apt-get install -y postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2",
|
18
|
+
],
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def server_image
|
23
|
+
@server_image ||= @host.images.create(
|
24
|
+
label: "pgserver",
|
25
|
+
base_image: base_image,
|
26
|
+
setup_commands: [
|
27
|
+
"service postgresql start; su postgres -c 'createuser -d -r -s root; createdb -O root root'; service postgresql stop",
|
28
|
+
"echo 'host all all 0.0.0.0/0 trust' >>/etc/postgresql/9.2/main/pg_hba.conf",
|
29
|
+
"echo \"listen_addresses='*'\" >>/etc/postgresql/9.2/main/postgresql.conf",
|
30
|
+
],
|
31
|
+
daemon_command: "su postgres -c '#{bin_path}/postgres -c config_file=/etc/postgresql/9.2/main/postgresql.conf'",
|
32
|
+
volumes: ["/var/lib/postgresql/9.2/main"],
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
container
|
38
|
+
end
|
39
|
+
|
40
|
+
def container
|
41
|
+
@container ||= server_image.run
|
42
|
+
end
|
43
|
+
|
44
|
+
def ip_address
|
45
|
+
container.ip_address
|
46
|
+
end
|
47
|
+
|
48
|
+
def export(file)
|
49
|
+
File.open file, "w" do |f|
|
50
|
+
f.write base_image.command("#{bin_path}/pg_dump #{client_options} #{@db_name}")
|
51
|
+
end
|
52
|
+
puts "[export] #{File.size file} bytes exported to #{file}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def import(file)
|
56
|
+
base_image.command "#{bin_path}/psql #{client_options} -d #{@db_name} -f /files/#{File.basename file}", files: [file]
|
57
|
+
puts "[import] #{File.size file} bytes imported from #{file}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def client_options
|
61
|
+
"-U root -h #{ip_address}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def bin_path
|
65
|
+
"/usr/lib/postgresql/9.2/bin"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,15 +1,42 @@
|
|
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(options)
|
5
|
+
@origin = options[:origin]
|
6
|
+
@name = name_from_origin @origin
|
7
|
+
@branch = options[:branch] || "master"
|
8
|
+
@environment = "production"
|
9
|
+
@test = options[:test]
|
10
|
+
end
|
11
|
+
|
12
|
+
def deploy
|
13
|
+
puts "[deploy] Deploying #{@name}:#{@branch} to #{@environment}"
|
14
|
+
unless @test
|
15
|
+
database.run
|
16
|
+
codebase.install
|
17
|
+
rails.run
|
18
|
+
puts "[deploy] Application deployed to #{docker.ip_address}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def docker
|
23
|
+
@docker ||= Service::DockerHost.create "#{@name}-#{@environment}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def database
|
27
|
+
@database ||= Service::PostgresDatabase.create docker, "#{@name}_#{@environment}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def codebase
|
31
|
+
@codebase ||= Service::RailsCodebase.create docker, @origin, @branch, @name, database.ip_address, @environment
|
32
|
+
end
|
33
|
+
|
34
|
+
def rails
|
35
|
+
@rails ||= Service::RailsServer.create docker, @name, @environment
|
36
|
+
end
|
37
|
+
|
38
|
+
def name_from_origin(origin)
|
39
|
+
origin.match(/\/([^.]+)\.git$/)[1]
|
13
40
|
end
|
14
41
|
end
|
15
42
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class RailsCodebase < Basic
|
4
|
+
def initialize(host, github_url, branch, app_name, database_ip_address, rails_environment)
|
5
|
+
@github_url = github_url
|
6
|
+
@branch = branch
|
7
|
+
@app_name = app_name
|
8
|
+
@database_ip_address = database_ip_address
|
9
|
+
@rails_environment = rails_environment
|
10
|
+
github_private_key = file_contents(Conjure.config.private_key_file).gsub("\n", "\\n")
|
11
|
+
github_public_key = file_contents(Conjure.config.public_key_file).gsub("\n", "\\n")
|
12
|
+
@image = host.images.create(
|
13
|
+
label: "codebase",
|
14
|
+
base_image: "ubuntu",
|
15
|
+
setup_commands: [
|
16
|
+
"apt-get install -y git",
|
17
|
+
"mkdir -p /root/.ssh; echo '#{github_private_key}' > /root/.ssh/id_rsa",
|
18
|
+
"mkdir -p /root/.ssh; echo '#{github_public_key}' > /root/.ssh/id_rsa.pub",
|
19
|
+
"chmod -R go-rwx /root/.ssh",
|
20
|
+
"echo 'Host github.com\\n\\tStrictHostKeyChecking no\\n' >> /root/.ssh/config",
|
21
|
+
],
|
22
|
+
host_volumes: {"/rails_app" => "/#{app_name}"},
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def database_yml
|
27
|
+
{
|
28
|
+
@rails_environment => {
|
29
|
+
"adapter" => "postgresql",
|
30
|
+
"database" => "#{@app_name}_#{@rails_environment}",
|
31
|
+
"encoding" => "utf8",
|
32
|
+
"host" => @database_ip_address,
|
33
|
+
"username" => "root",
|
34
|
+
"template" => "template0",
|
35
|
+
}
|
36
|
+
}.to_yaml
|
37
|
+
end
|
38
|
+
|
39
|
+
def install
|
40
|
+
code_checked_out ? fetch_code_updates : checkout_code
|
41
|
+
configure_database
|
42
|
+
configure_logs
|
43
|
+
end
|
44
|
+
|
45
|
+
def code_checked_out
|
46
|
+
@image.command("[ -d #{@app_name}/.git ] && echo yes; true").strip == "yes"
|
47
|
+
end
|
48
|
+
|
49
|
+
def checkout_code
|
50
|
+
puts "[ repo] Checking out code from git"
|
51
|
+
@image.command "git clone -b #{@branch} #{@github_url}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_code_updates
|
55
|
+
puts "[ repo] Fetching code updates from git"
|
56
|
+
@image.command "cd #{@app_name}; git reset --hard; git checkout #{@branch}; git pull"
|
57
|
+
end
|
58
|
+
|
59
|
+
def configure_database
|
60
|
+
puts "[ repo] Generating database.yml"
|
61
|
+
@image.command "echo '#{database_yml}' >/#{@app_name}/config/database.yml"
|
62
|
+
end
|
63
|
+
|
64
|
+
def configure_logs
|
65
|
+
puts "[ repo] Configuring application logger"
|
66
|
+
setup = 'Rails.logger = Logger.new "#{Rails.root}/log/#{Rails.env}.log"'
|
67
|
+
@image.command "echo '#{setup}' >/#{@app_name}/config/initializers/z_conjure_logger.rb"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,68 +1,111 @@
|
|
1
1
|
module Conjure
|
2
2
|
module Service
|
3
3
|
class RailsServer < Basic
|
4
|
-
def
|
5
|
-
|
6
|
-
|
4
|
+
def initialize(host, app_name, rails_environment)
|
5
|
+
@host = host
|
6
|
+
@app_name = app_name
|
7
|
+
@rails_environment = rails_environment
|
7
8
|
end
|
8
9
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
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",
|
10
|
+
def base_image
|
11
|
+
@base_image ||= @host.images.create(
|
12
|
+
label: "rails_base",
|
16
13
|
base_image: "ubuntu",
|
17
14
|
setup_commands: [
|
18
|
-
|
15
|
+
"apt-get install -y curl git",
|
19
16
|
"curl -L https://get.rvm.io | bash -s stable",
|
20
17
|
"rvm install #{ruby_version}",
|
21
18
|
"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
19
|
"apt-get install -y #{apt_packages_required_for_gems.join ' '}",
|
28
|
-
"cd #{app_name}; bundle --deployment",
|
29
|
-
|
30
20
|
"echo 'deb http://us.archive.ubuntu.com/ubuntu/ precise universe' >>/etc/apt/sources.list",
|
31
21
|
"apt-get install -y python-software-properties software-properties-common",
|
32
22
|
"add-apt-repository -y ppa:chris-lea/node.js-legacy",
|
33
23
|
"apt-get update",
|
34
24
|
"apt-get install -y nodejs",
|
35
25
|
|
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
26
|
],
|
39
27
|
environment: {
|
40
28
|
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,
|
29
|
+
RAILS_ENV: @rails_environment,
|
42
30
|
GITHUB_TOKEN: ENV["GITHUB_TOKEN"],
|
43
31
|
FRECKLE_SUBDOMAIN: "neomind",
|
44
32
|
},
|
45
|
-
|
33
|
+
host_volumes: {"/rails_app" => "/#{@app_name}"},
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def server_image
|
38
|
+
@server_image ||= @host.images.create(
|
39
|
+
label: "rails_server",
|
40
|
+
base_image: base_image,
|
46
41
|
ports: [80],
|
47
|
-
|
42
|
+
environment: {
|
43
|
+
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",
|
44
|
+
},
|
45
|
+
host_volumes: {"/rails_app" => "/#{@app_name}"},
|
48
46
|
)
|
49
47
|
end
|
50
48
|
|
51
49
|
def run
|
52
|
-
|
50
|
+
install_gems
|
51
|
+
update_database
|
52
|
+
restart_server
|
53
|
+
end
|
54
|
+
|
55
|
+
def install_gems
|
56
|
+
puts "[ rails] Installing gems"
|
57
|
+
base_image.command "cd #{@app_name}; bundle --deployment"
|
58
|
+
end
|
59
|
+
|
60
|
+
def update_database
|
61
|
+
database_exists ? migrate_database : initialize_database
|
62
|
+
end
|
63
|
+
|
64
|
+
def database_exists
|
65
|
+
puts "[ rails] Checking the database status"
|
66
|
+
base_image.command("cd #{@app_name}; bundle exec rake db:version; true").include? "Current version:"
|
67
|
+
end
|
68
|
+
|
69
|
+
def migrate_database
|
70
|
+
puts "[ rails] Migrating the database"
|
71
|
+
base_image.command "cd #{@app_name}; bundle exec rake db:migrate"
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize_database
|
75
|
+
puts "[ rails] Setting up the database"
|
76
|
+
base_image.command "cd #{@app_name}; bundle exec rake db:setup"
|
77
|
+
end
|
78
|
+
|
79
|
+
def restart_server
|
80
|
+
server_image.stop
|
81
|
+
server_image.run "cd #{@app_name}; rm -f tmp/pids/server.pid; bundle exec rails server -p 80"
|
82
|
+
end
|
83
|
+
|
84
|
+
def log(options = {})
|
85
|
+
arguments = []
|
86
|
+
arguments << "-n #{options[:lines]}" if options[:lines]
|
87
|
+
arguments << "-f" if options[:tail]
|
88
|
+
log_file = "#{@app_name}/log/#{@rails_environment}.log"
|
89
|
+
base_image.command "tail #{arguments.join ' '} #{log_file}" do |stdout, stderr|
|
90
|
+
puts stdout
|
91
|
+
end
|
92
|
+
rescue Interrupt => e
|
93
|
+
end
|
94
|
+
|
95
|
+
def rake(command)
|
96
|
+
base_image.command "cd #{@app_name}; bundle exec rake #{command}" do |stdout, stderr|
|
97
|
+
print stdout
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def console
|
102
|
+
base_image.command "cd #{@app_name}; bundle exec rails console", :stream_stdin => true do |stdout, stderr|
|
103
|
+
print stdout
|
104
|
+
end
|
53
105
|
end
|
54
106
|
|
55
|
-
def
|
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")
|
107
|
+
def ruby_version
|
108
|
+
file_contents("../.ruby-version").strip
|
66
109
|
end
|
67
110
|
|
68
111
|
def apt_packages_required_for_gems
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Conjure
|
2
|
+
module Service
|
3
|
+
class RemoteShell
|
4
|
+
require "net/ssh"
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(command, options = {})
|
11
|
+
stdout, stderr, exit_status = ["", "", nil]
|
12
|
+
session.open_channel do |channel|
|
13
|
+
channel.request_pty
|
14
|
+
channel.exec command do |c, success|
|
15
|
+
raise "Failed to execute command via SSH" unless success
|
16
|
+
channel.on_data do |c, data|
|
17
|
+
yield data if block_given?
|
18
|
+
stdout << data
|
19
|
+
end
|
20
|
+
channel.on_extended_data { |c, type, data| stderr << data }
|
21
|
+
channel.on_request("exit-status") { |c, data| exit_status = data.read_long }
|
22
|
+
end
|
23
|
+
if options[:stream_stdin]
|
24
|
+
channel.on_process do
|
25
|
+
poll_stream(STDIN) { |data| channel.send_data data }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if options[:stream_stdin]
|
30
|
+
with_raw_tty { session.loop 0.01 }
|
31
|
+
else
|
32
|
+
session.loop
|
33
|
+
end
|
34
|
+
Result.new stdout, stderr, exit_status
|
35
|
+
end
|
36
|
+
|
37
|
+
def session
|
38
|
+
session_options = {
|
39
|
+
:auth_methods => ["publickey"],
|
40
|
+
:key_data => File.read(@options[:private_key_path]),
|
41
|
+
:keys_only => true,
|
42
|
+
:paranoid => false,
|
43
|
+
}
|
44
|
+
@session ||= Net::SSH.start @options[:ip_address], @options[:username], session_options
|
45
|
+
end
|
46
|
+
|
47
|
+
def poll_stream(stream, &block)
|
48
|
+
yield stream.sysread(1) if IO.select([stream], nil, nil, 0.01)
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_raw_tty
|
52
|
+
system "stty raw -echo"
|
53
|
+
yield
|
54
|
+
ensure
|
55
|
+
system "stty -raw echo"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Result = Struct.new(:stdout, :stderr, :status)
|
60
|
+
end
|
61
|
+
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.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-10-
|
12
|
+
date: 2013-10-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
@@ -59,6 +59,38 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: minitest
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
62
94
|
description:
|
63
95
|
email:
|
64
96
|
- brianauton@gmail.com
|
@@ -70,11 +102,13 @@ files:
|
|
70
102
|
- lib/conjure.rb
|
71
103
|
- lib/conjure/service/cloud_server.rb
|
72
104
|
- lib/conjure/service/rails_server.rb
|
73
|
-
- lib/conjure/service/
|
105
|
+
- lib/conjure/service/rails_codebase.rb
|
74
106
|
- lib/conjure/service/rails_application.rb
|
75
107
|
- lib/conjure/service/machine_instance.rb
|
76
|
-
- lib/conjure/service/
|
108
|
+
- lib/conjure/service/remote_shell.rb
|
77
109
|
- lib/conjure/service/docker_host.rb
|
110
|
+
- lib/conjure/service/postgres_database.rb
|
111
|
+
- lib/conjure/config.rb
|
78
112
|
- lib/conjure/service.rb
|
79
113
|
- lib/conjure/command.rb
|
80
114
|
- README.md
|
@@ -1,32 +0,0 @@
|
|
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
|
@@ -1,31 +0,0 @@
|
|
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
|