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 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
@@ -0,0 +1,3 @@
1
+ module DeployTool
2
+ VERSION = "0.9.0"
3
+ end
data/lib/deploytool.rb ADDED
@@ -0,0 +1,4 @@
1
+ module DeployTool; end
2
+
3
+ require 'deploytool/config'
4
+ require 'deploytool/target'
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -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
+ }
@@ -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
+