deployto 0.9.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/README.md +27 -0
- data/bin/deploy +26 -0
- data/lib/deploytool/command.rb +108 -0
- data/lib/deploytool/config.rb +21 -0
- data/lib/deploytool/target/cloudfoundry.rb +31 -0
- data/lib/deploytool/target/efficientcloud/api_client.rb +146 -0
- data/lib/deploytool/target/efficientcloud.rb +68 -0
- data/lib/deploytool/target/heroku.rb +30 -0
- data/lib/deploytool/target.rb +64 -0
- data/lib/deploytool/version.rb +3 -0
- data/lib/deploytool.rb +4 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/target_spec.rb +43 -0
- metadata +177 -0
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# deploytool
|
2
|
+
|
3
|
+
Deployment tool for Platform-as-a-Service providers, with special support for multi-stage deploys (e.g. for staging environments).
|
4
|
+
|
5
|
+
Platforms currently supported:
|
6
|
+
* Efficient Cloud Platforms
|
7
|
+
|
8
|
+
Platform support planned:
|
9
|
+
* Heroku
|
10
|
+
* Cloud Foundry Platforms
|
11
|
+
|
12
|
+
## Deploying a Ruby on Rails app
|
13
|
+
|
14
|
+
gem install deploytool
|
15
|
+
deploy add production portfolio.heroku.com
|
16
|
+
deploy add staging portfolio.vcap.me
|
17
|
+
deploy production
|
18
|
+
|
19
|
+
## Config file
|
20
|
+
|
21
|
+
deploy keeps a local config file .deployrc within the top-level sourcecode directory (determined by location of .git directory).
|
22
|
+
|
23
|
+
## Legalese
|
24
|
+
|
25
|
+
_Copyright 2011, Efficient Cloud Ltd.
|
26
|
+
|
27
|
+
Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
|
data/bin/deploy
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
require 'rubygems'
|
8
|
+
require 'deploytool'
|
9
|
+
require 'deploytool/command'
|
10
|
+
|
11
|
+
$logger = Logger.new STDOUT
|
12
|
+
$logger.formatter = proc do |severity, datetime, progname, msg|
|
13
|
+
next if severity == "DEBUG"
|
14
|
+
if severity == "ERROR"
|
15
|
+
"ERROR: #{msg}\n"
|
16
|
+
else
|
17
|
+
"#{msg}\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
HighLine.track_eof = false
|
21
|
+
|
22
|
+
args = ARGV.dup
|
23
|
+
ARGV.clear
|
24
|
+
command = args.shift.strip rescue 'help'
|
25
|
+
|
26
|
+
DeployTool::Command.run(command, args)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class DeployTool::Command
|
2
|
+
COMMANDS = ["to", "logs", "import", "export", "config"]
|
3
|
+
|
4
|
+
def self.run(command, args)
|
5
|
+
change_to_toplevel_dir!
|
6
|
+
|
7
|
+
DeployTool::Config.load(".deployrc")
|
8
|
+
|
9
|
+
if command == "help"
|
10
|
+
puts "Deploytool Usage Instructions"
|
11
|
+
puts ""
|
12
|
+
puts "Add a target:"
|
13
|
+
puts " deploy add production app1@demo.efficientcloud.com"
|
14
|
+
puts " deploy add staging myapp.heroku.com"
|
15
|
+
puts " deploy add failover api.cloudfoundry.com"
|
16
|
+
puts ""
|
17
|
+
puts "Deploy the current directory to the target:"
|
18
|
+
puts " deploy to production"
|
19
|
+
elsif command == "add"
|
20
|
+
if args[0].nil?
|
21
|
+
puts "ERROR: Missing target name."
|
22
|
+
puts ""
|
23
|
+
puts "Use \"deploy help\" if you're lost."
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
if args[1].nil?
|
27
|
+
puts "ERROR: Missing target specification."
|
28
|
+
puts ""
|
29
|
+
puts "Use \"deploy help\" if you're lost."
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
unless target = DeployTool::Target.find(args[1])
|
33
|
+
puts "ERROR: Couldn't find provider for target \"#{args[1]}\""
|
34
|
+
puts ""
|
35
|
+
puts "Use \"deploy help\" if you're lost."
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
DeployTool::Config[args[0]] = target.to_h
|
39
|
+
elsif command == "list"
|
40
|
+
puts "Registered Targets:"
|
41
|
+
DeployTool::Config.all.each do |target_name, target|
|
42
|
+
target = DeployTool::Target.from_config(target)
|
43
|
+
puts " %s%s" % [target_name.ljust(15), target.to_s]
|
44
|
+
end
|
45
|
+
else
|
46
|
+
args.unshift command unless command == "to"
|
47
|
+
target_name = args[0]
|
48
|
+
|
49
|
+
unless (target = DeployTool::Config[target_name]) && !target.nil? && target.size > 0
|
50
|
+
puts "ERROR: Couldn't find target: #{target_name}"
|
51
|
+
puts ""
|
52
|
+
puts "Use \"deploy help\" if you're lost."
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
opts = {}
|
57
|
+
opts[:timing] = true if args.include?("--timing")
|
58
|
+
|
59
|
+
target = DeployTool::Target.from_config(target)
|
60
|
+
begin
|
61
|
+
target.push(opts)
|
62
|
+
rescue => e
|
63
|
+
puts e
|
64
|
+
exit 2
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
DeployTool::Config.save
|
69
|
+
rescue Net::HTTPServerException => e
|
70
|
+
$logger.info "ERROR: HTTP call returned %s %s" % [e.response.code, e.response.message]
|
71
|
+
if target
|
72
|
+
$logger.debug "\nTarget:"
|
73
|
+
target.to_h.each do |k, v|
|
74
|
+
next if k.to_sym == :password
|
75
|
+
$logger.debug " %s = %s" % [k, v]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
$logger.debug "\nBacktrace:"
|
79
|
+
$logger.debug " " + e.backtrace.join("\n ")
|
80
|
+
$logger.debug "\nResponse:"
|
81
|
+
e.response.each_header do |k, v|
|
82
|
+
$logger.debug " %s: %s" % [k, v]
|
83
|
+
end
|
84
|
+
$logger.debug "\n " + e.response.body.gsub("\n", "\n ")
|
85
|
+
$logger.info "\nPlease run again with \"--debug\" and report the output at http://bit.ly/deploytool-new-issue"
|
86
|
+
exit 2
|
87
|
+
end
|
88
|
+
|
89
|
+
# Tries to figure out if we're running in a subdirectory of the source,
|
90
|
+
# and switches to the top-level if that's the case
|
91
|
+
def self.change_to_toplevel_dir!
|
92
|
+
indicators = [".git", "Gemfile", "LICENSE", "test"]
|
93
|
+
|
94
|
+
timeout = 10
|
95
|
+
path = Dir.pwd
|
96
|
+
begin
|
97
|
+
indicators.each do |indicator|
|
98
|
+
next unless File.exists?(File.join(path, indicator))
|
99
|
+
|
100
|
+
$logger.debug "Found correct top-level directory %s, switching working directory." % [path] unless path == Dir.pwd
|
101
|
+
Dir.chdir path
|
102
|
+
return
|
103
|
+
end
|
104
|
+
end until (path = File.dirname(path)) == "/" || (timeout -= 1) == 0
|
105
|
+
|
106
|
+
$logger.debug "DEBUG: Couldn't locate top-level directory (traversed until %s), falling back to %s" % [path, Dir.pwd]
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
|
3
|
+
class DeployTool::Config
|
4
|
+
def self.all
|
5
|
+
@@configfile.to_h
|
6
|
+
end
|
7
|
+
def self.[](section)
|
8
|
+
@@configfile[section]
|
9
|
+
end
|
10
|
+
def self.[]=(section, value)
|
11
|
+
@@configfile[section] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load(filename)
|
15
|
+
@@configfile = IniFile.load(filename)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.save
|
19
|
+
@@configfile.save unless @@configfile.to_h.empty?
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class DeployTool::Target::CloudFoundry < DeployTool::Target
|
2
|
+
def self.parse_target_spec(target_spec)
|
3
|
+
app_name, server = target_spec.split('.', 2)
|
4
|
+
return false if server.nil? or app_name.nil?
|
5
|
+
# Test through multiple versions of API server URLs
|
6
|
+
[target_spec, 'api.' + target_spec, 'api.' + server].each do |api_server|
|
7
|
+
begin
|
8
|
+
if get_json_resource("http://%s/info" % api_server)['name'] == "vcap"
|
9
|
+
app_name = nil if server.gsub('api.') == api_server.gsub('api.')
|
10
|
+
return [app_name, api_server]
|
11
|
+
end
|
12
|
+
rescue => e
|
13
|
+
$logger.debug "Exception: %s\n%s" % [e.message, e.backtrace.join("\n")]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.matches?(target_spec)
|
20
|
+
return true if parse_target_spec(target_spec)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(options)
|
24
|
+
# FIXME
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create(target_spec)
|
28
|
+
app_name, api_server = parse_target_spec(target_spec)
|
29
|
+
CloudFoundry.new('api_server' => api_server, 'app_name' => app_name)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'addressable/uri'
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/http/post/multipart'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'tempfile'
|
7
|
+
require 'zip'
|
8
|
+
|
9
|
+
class DeployTool::Target::EfficientCloud
|
10
|
+
class ApiClient
|
11
|
+
attr_reader :server, :app_id, :email, :password
|
12
|
+
def initialize(server, app_id, email, password)
|
13
|
+
@app_id = app_id
|
14
|
+
@server = server
|
15
|
+
@email = email
|
16
|
+
@password = password
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(method, method_name, data = {})
|
20
|
+
url = Addressable::URI.parse("http://#{@server}/api/cli/v1/apps/#{@app_id}/#{method_name}")
|
21
|
+
data = data.merge(:email => @email, :password => @password)
|
22
|
+
if method == :post
|
23
|
+
res = Net::HTTP.start(url.host, url.port) do |http|
|
24
|
+
http.request Net::HTTP::Post::Multipart.new(url.path, data)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
url.query_values = data
|
28
|
+
res = Net::HTTP.get_response(url)
|
29
|
+
end
|
30
|
+
case res
|
31
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
32
|
+
res.body
|
33
|
+
else
|
34
|
+
res.error!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def upload
|
39
|
+
appfiles = Dir.glob('**/*', File::FNM_DOTMATCH).reject {|f| File.directory?(f) || f[/(^|\/).{1,2}$/] || f[/^.git\//] || f[/^.deployrc$/] || f[/(^|\/).DS_Store$/] }
|
40
|
+
|
41
|
+
# Construct a temporary zipfile
|
42
|
+
tempfile = Tempfile.open("ecli-upload.zip")
|
43
|
+
Zip::ZipOutputStream.open(tempfile.path) do |z|
|
44
|
+
appfiles.each do |appfile|
|
45
|
+
z.put_next_entry appfile
|
46
|
+
z.print IO.read(appfile)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
puts "-----> Uploading %s code tarball..." % human_filesize(tempfile.path)
|
51
|
+
initial_response = call :post, 'upload', {:code => UploadIO.new(tempfile, "application/zip", "ecli-upload.zip")}
|
52
|
+
doc = REXML::Document.new initial_response
|
53
|
+
doc.elements["code/code-token"].text
|
54
|
+
rescue Net::HTTPServerException => e
|
55
|
+
case e.response
|
56
|
+
when Net::HTTPNotFound
|
57
|
+
$logger.error "Application app%d.%s couldn't be found." % [@app_id, @server.gsub('api.', '')]
|
58
|
+
when Net::HTTPUnauthorized
|
59
|
+
$logger.error "You're not authorized to update app%d.%s." % [@app_id, @server.gsub('api.', '')]
|
60
|
+
else
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
$logger.info "\nPlease check the controlpanel for update instructions."
|
64
|
+
exit 2
|
65
|
+
end
|
66
|
+
|
67
|
+
def deploy(code_token)
|
68
|
+
initial_response = call :post, 'deploy', {:code_token => code_token}
|
69
|
+
doc = REXML::Document.new initial_response
|
70
|
+
deploy_token = doc.elements["deploy/token"].text
|
71
|
+
deploy_token
|
72
|
+
end
|
73
|
+
|
74
|
+
def save_timing_data(data)
|
75
|
+
File.open('deploytool-timingdata-%d.json' % (Time.now), 'w') do |f|
|
76
|
+
f.puts data.to_json
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def deploy_status(deploy_token, opts)
|
81
|
+
start = Time.now
|
82
|
+
timing = []
|
83
|
+
previous_status = nil
|
84
|
+
print "-----> Started deployment '%s'" % deploy_token
|
85
|
+
|
86
|
+
while true
|
87
|
+
sleep 1
|
88
|
+
resp = call :get, 'deploy_status', {:deploy_token => deploy_token}
|
89
|
+
doc = REXML::Document.new resp
|
90
|
+
|
91
|
+
if doc.elements["deploy/message"].nil?
|
92
|
+
puts resp
|
93
|
+
puts "...possibly done."
|
94
|
+
break
|
95
|
+
end
|
96
|
+
if doc.elements["deploy/message"].text == 'nojob'
|
97
|
+
puts "\n-----> FINISHED after %d seconds!" % (Time.now-start)
|
98
|
+
break
|
99
|
+
end
|
100
|
+
|
101
|
+
status = doc.elements["deploy/message"].text.gsub('["', '').gsub('"]', '')
|
102
|
+
if previous_status != status
|
103
|
+
case status
|
104
|
+
when "build"
|
105
|
+
puts "\n-----> Building/updating virtual machine..."
|
106
|
+
when "deploy"
|
107
|
+
print "-----> Copying virtual machine to app hosts"
|
108
|
+
when "publishing"
|
109
|
+
print "\n-----> Updating HTTP gateways"
|
110
|
+
when "cleanup"
|
111
|
+
print "\n-----> Removing old deployments"
|
112
|
+
end
|
113
|
+
previous_status = status
|
114
|
+
end
|
115
|
+
|
116
|
+
logs = doc.elements["deploy/logs"].text rescue nil
|
117
|
+
if logs
|
118
|
+
puts "" if status != "build" # Add newline after the dots
|
119
|
+
puts logs
|
120
|
+
timing << [Time.now-start, status, logs]
|
121
|
+
else
|
122
|
+
timing << [Time.now-start, status]
|
123
|
+
if status == 'error'
|
124
|
+
if logs.nil? or logs.empty?
|
125
|
+
raise "ERROR after %d seconds!" % (Time.now-start)
|
126
|
+
end
|
127
|
+
elsif status != "build"
|
128
|
+
print "."
|
129
|
+
STDOUT.flush
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
ensure
|
134
|
+
save_timing_data timing if opts[:timing]
|
135
|
+
end
|
136
|
+
|
137
|
+
def human_filesize(path)
|
138
|
+
size = File.size(path)
|
139
|
+
units = %w{B KB MB GB TB}
|
140
|
+
e = (Math.log(size)/Math.log(1024)).floor
|
141
|
+
s = "%.1f" % (size.to_f / 1024**e)
|
142
|
+
s.sub(/\.?0*$/, units[e])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'highline'
|
2
|
+
|
3
|
+
class DeployTool::Target::EfficientCloud < DeployTool::Target
|
4
|
+
SUPPORTED_API_VERSION = 1
|
5
|
+
def self.parse_target_spec(target_spec)
|
6
|
+
server, app_id = target_spec.split('@').reverse
|
7
|
+
if app_id.nil?
|
8
|
+
app_id = server.split('.', 2).first
|
9
|
+
end
|
10
|
+
[server, 'api.' + server, 'api.' + server.split('.', 2).last].each do |api_server|
|
11
|
+
begin
|
12
|
+
return [app_id.gsub('app', '').to_i, api_server] if check_version(api_server)
|
13
|
+
rescue => e
|
14
|
+
puts e
|
15
|
+
end
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.matches?(target_spec)
|
21
|
+
return true if parse_target_spec(target_spec)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
{:type => "EfficientCloud", :api_server => @api_client.server, :app_id => @api_client.app_id, :email => @api_client.email, :password => @api_client.password}
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"app%s@%s (EFC-based platform)" % [@api_client.app_id, @api_client.server]
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options)
|
33
|
+
@api_server = options['api_server']
|
34
|
+
@api_client = ApiClient.new(options['api_server'], options['app_id'], options['email'], options['password'])
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.check_version(api_server)
|
38
|
+
begin
|
39
|
+
info = get_json_resource("http://%s/info" % api_server)
|
40
|
+
rescue => e
|
41
|
+
$logger.debug "Exception: %s\n%s" % [e.message, e.backtrace.join("\n")]
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
return false unless info && info['name'] == "efc"
|
45
|
+
|
46
|
+
if info['api_version'] > SUPPORTED_API_VERSION
|
47
|
+
$logger.error "This version of deploytool is outdated.\nThis server requires at least API Version #{info['api_version']}."
|
48
|
+
end
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.create(target_spec)
|
53
|
+
$logger.info "Please specify your controlpanel login information"
|
54
|
+
email = HighLine.new.ask("E-mail: ")
|
55
|
+
password = HighLine.new.ask("Password: ") {|q| q.echo = "*" }
|
56
|
+
app_id, api_server = parse_target_spec(target_spec)
|
57
|
+
EfficientCloud.new('api_server' => api_server, 'app_id' => app_id, 'email' => email, 'password' => password)
|
58
|
+
end
|
59
|
+
|
60
|
+
def push(opts)
|
61
|
+
self.class.check_version(@api_server)
|
62
|
+
code_token = @api_client.upload
|
63
|
+
deploy_token = @api_client.deploy(code_token)
|
64
|
+
@api_client.deploy_status(deploy_token, opts) # Blocks till deploy is done
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
require 'deploytool/target/efficientcloud/api_client'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class DeployTool::Target::Heroku < DeployTool::Target
|
2
|
+
def self.matches?(target_spec)
|
3
|
+
target_spec[/(^|\.)heroku\.com$/]
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_h
|
7
|
+
{:type => "Heroku", :app_name => @app_name}
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"%s.heroku.com (Heroku)" % [@app_name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
@app_name = options['app_name']
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create(target_name)
|
19
|
+
app_name = target_name.gsub('.heroku.com', '')
|
20
|
+
# TODO: Require current directory to be a git repository
|
21
|
+
# TODO: Ask for app name if app name is nil or www
|
22
|
+
puts `heroku create #{app_name}`
|
23
|
+
Heroku.new('app_name' => app_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def push(opts)
|
27
|
+
puts `git push -f git@heroku.com:#{@app_name}.git master`
|
28
|
+
$?.exitstatus
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Module
|
4
|
+
def track_subclasses
|
5
|
+
instance_eval %{
|
6
|
+
def self.known_subclasses
|
7
|
+
@__deploytool_subclasses
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.add_known_subclass(s)
|
11
|
+
superclass.add_known_subclass(s) if superclass.respond_to?(:inherited_tracking_subclasses)
|
12
|
+
(@__deploytool_subclasses ||= []) << s
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.inherited_tracking_subclasses(s)
|
16
|
+
add_known_subclass(s)
|
17
|
+
inherited_not_tracking_subclasses(s)
|
18
|
+
end
|
19
|
+
alias :inherited_not_tracking_subclasses :inherited
|
20
|
+
alias :inherited :inherited_tracking_subclasses
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class DeployTool::Target
|
26
|
+
track_subclasses
|
27
|
+
|
28
|
+
def self.find(target_spec)
|
29
|
+
known_subclasses.each do |klass|
|
30
|
+
next unless klass.matches?(target_spec)
|
31
|
+
return klass.create(target_spec)
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_config(config)
|
37
|
+
known_subclasses.each do |klass|
|
38
|
+
next unless klass.to_s.split('::').last == config['type']
|
39
|
+
return klass.new(config)
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.get_json_resource(url)
|
45
|
+
res = nil
|
46
|
+
begin
|
47
|
+
timeout(5) do
|
48
|
+
res = Net::HTTP.get_response(Addressable::URI.parse(url))
|
49
|
+
end
|
50
|
+
rescue Timeout::Error
|
51
|
+
$logger.debug "Calling '%s' took longer than 5s, skipping" % [url, res.code, res.body]
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
if res.code != '200'
|
55
|
+
$logger.debug "Calling '%s' returned %s, skipping" % [url, res.code, res.body]
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
JSON.parse(res.body)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
(Dir.glob(File.dirname(__FILE__)+'/target/*.rb') - [__FILE__]).sort.each do |f|
|
63
|
+
require 'deploytool/target/' + File.basename(f)
|
64
|
+
end
|
data/lib/deploytool.rb
ADDED
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler/setup"
|
4
|
+
|
5
|
+
require 'rspec'
|
6
|
+
require 'deploytool'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.mock_with :rr
|
11
|
+
end
|
12
|
+
|
13
|
+
$logger = Logger.new File.expand_path('../spec.log', __FILE__)
|
14
|
+
$logger.formatter = proc { |severity, datetime, progname, msg|
|
15
|
+
"#{severity} #{datetime.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}\n"
|
16
|
+
}
|
data/spec/target_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DeployTool::Target do
|
4
|
+
context "Target selection" do
|
5
|
+
before do
|
6
|
+
stub.any_instance_of(HighLine).ask do |q, |
|
7
|
+
if q[/E-mail/]
|
8
|
+
"demo@efficientcloud.com"
|
9
|
+
elsif q[/Password/]
|
10
|
+
"demo"
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: Mock HTTP get method
|
17
|
+
end
|
18
|
+
|
19
|
+
["api.cloudfoundry.com", "cloudfoundry.com", "awesomeapp.cloudfoundry.com"].each do |target_spec| #, "api.cloud.1and1.com", "cloud.1and1.com", "awesomeapp.cloud.1and1.com"].each do
|
20
|
+
it "should detect #{target_spec} as a CloudFoundry target" do
|
21
|
+
DeployTool::Target.find(target_spec).class.should == DeployTool::Target::CloudFoundry
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
["app10000@api.srv.io", "app10000@srv.io", "app10000@app123.srv.io", "app10000.srv.io"].each do |target_spec|
|
26
|
+
it "should detect #{target_spec} as an Efficient Cloud target" do
|
27
|
+
DeployTool::Target.find(target_spec).class.should == DeployTool::Target::EfficientCloud
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
["heroku.com", "awesomeapp.heroku.com"].each do |target_spec|
|
32
|
+
it "should detect #{target_spec} as an Heroku target" do
|
33
|
+
DeployTool::Target.find(target_spec).class.should == DeployTool::Target::Heroku
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
["gandi.net", "1and1.com"].each do |target_spec|
|
38
|
+
it "should return an error with #{target_spec} as target" do
|
39
|
+
DeployTool::Target.find(target_spec).class.should == NilClass
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deployto
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Efficient Cloud Ltd
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-11 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: inifile
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 13
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 4
|
32
|
+
- 1
|
33
|
+
version: 0.4.1
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: addressable
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: multipart-post
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: highline
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
type: :runtime
|
77
|
+
version_requirements: *id004
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: zip
|
80
|
+
prerelease: false
|
81
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
type: :runtime
|
91
|
+
version_requirements: *id005
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: heroku
|
94
|
+
prerelease: false
|
95
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
type: :runtime
|
105
|
+
version_requirements: *id006
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
name: json
|
108
|
+
prerelease: false
|
109
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
hash: 3
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
version: "0"
|
118
|
+
type: :runtime
|
119
|
+
version_requirements: *id007
|
120
|
+
description: Deployment tool with support for multiple Platform-as-a-Service providers.
|
121
|
+
email: hello@platformdirectory.com
|
122
|
+
executables:
|
123
|
+
- deploy
|
124
|
+
extensions: []
|
125
|
+
|
126
|
+
extra_rdoc_files: []
|
127
|
+
|
128
|
+
files:
|
129
|
+
- README.md
|
130
|
+
- bin/deploy
|
131
|
+
- lib/deploytool.rb
|
132
|
+
- lib/deploytool/command.rb
|
133
|
+
- lib/deploytool/config.rb
|
134
|
+
- lib/deploytool/target.rb
|
135
|
+
- lib/deploytool/target/cloudfoundry.rb
|
136
|
+
- lib/deploytool/target/efficientcloud.rb
|
137
|
+
- lib/deploytool/target/efficientcloud/api_client.rb
|
138
|
+
- lib/deploytool/target/heroku.rb
|
139
|
+
- lib/deploytool/version.rb
|
140
|
+
- spec/spec.opts
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/target_spec.rb
|
143
|
+
homepage: http://platformdirectory.com/
|
144
|
+
licenses: []
|
145
|
+
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 3
|
157
|
+
segments:
|
158
|
+
- 0
|
159
|
+
version: "0"
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
hash: 3
|
166
|
+
segments:
|
167
|
+
- 0
|
168
|
+
version: "0"
|
169
|
+
requirements: []
|
170
|
+
|
171
|
+
rubyforge_project:
|
172
|
+
rubygems_version: 1.8.5
|
173
|
+
signing_key:
|
174
|
+
specification_version: 3
|
175
|
+
summary: Multi-platform deployment tool.
|
176
|
+
test_files: []
|
177
|
+
|