conjure 0.0.2 → 0.1.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 +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
|
+
[](http://badge.fury.io/rb/conjure)
|
3
|
+
[](https://travis-ci.org/brianauton/conjure)
|
4
|
+
[](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
|