travis-deploy 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/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/Rakefile +5 -0
- data/bin/travis-deploy +11 -0
- data/lib/travis/deploy.rb +52 -0
- data/lib/travis/deploy/config.rb +70 -0
- data/lib/travis/deploy/deploy.rb +89 -0
- data/lib/travis/deploy/helper.rb +23 -0
- data/lib/travis/deploy/secure_key.rb +54 -0
- data/lib/travis/keychain.rb +39 -0
- data/lib/travis_cli.rb +1 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/travis/deploy/config_spec.rb +33 -0
- data/spec/travis/deploy/deploy_spec.rb +117 -0
- data/spec/travis/deploy/secure_key_spec.rb +70 -0
- data/spec/travis/keychain_spec.rb +42 -0
- data/travis-deploy.gemspec +22 -0
- metadata +136 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) Sven Fuchs <svenfuchs@artweb-design.de>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Travis Deploy Tool
|
2
|
+
|
3
|
+
Deployment tool for Travis CI
|
4
|
+
|
5
|
+
Currently supports the following commands:
|
6
|
+
|
7
|
+
$ travis deploy [remote] # deploy to remote
|
8
|
+
$ travis config [remote] # sync the local config file from travis-keychain and push the config to remote
|
9
|
+
|
10
|
+
The tool relies on git remotes being defined in `.git/config`. So, in order to deploy to production there needs to be a `production` remote repository defined in the git config.
|
11
|
+
|
12
|
+
## Deployment
|
13
|
+
|
14
|
+
Deploying to `staging` will just push to staging and run the migrations if `-m` was given. One can also pass `-c` in order to configure the app.
|
15
|
+
|
16
|
+
Deploying to production.
|
17
|
+
$ git push staging HEAD:master
|
18
|
+
Running migrations.
|
19
|
+
$ heroku run rake db:migrate -r staging
|
20
|
+
|
21
|
+
Deploying to production will also update the production branch and tag the current commit.
|
22
|
+
|
23
|
+
Updating production branch.
|
24
|
+
$ git checkout production
|
25
|
+
$ git reset --hard master
|
26
|
+
$ git push origin production -f
|
27
|
+
Tagging deploy 2011-12-11 16:04.
|
28
|
+
$ git tag -a 'deploy.2011-12-11.16-04' -m 'deploy 2011-12-11 16:04'
|
29
|
+
$ git push --tags
|
30
|
+
$ git checkout master
|
31
|
+
Deploying to production.
|
32
|
+
$ git push production HEAD:master -f
|
33
|
+
Running migrations.
|
34
|
+
$ heroku run rake db:migrate -r production
|
35
|
+
|
36
|
+
WARNING:
|
37
|
+
|
38
|
+
The production branch is updated using `git reset --hard [branch]` and pushed using `git push origin production -f`.
|
39
|
+
|
40
|
+
That means that all commits in the `production` branch that are not present in the target branch (e.g. `master`) will be removed from the history of `production`.
|
41
|
+
|
42
|
+
**So never commit to the production branch directly!**
|
43
|
+
|
data/Rakefile
ADDED
data/bin/travis-deploy
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'travis/deploy'
|
7
|
+
require 'thor/runner'
|
8
|
+
|
9
|
+
klass, task = Thor::Util.find_class_and_task_by_namespace("travis:#{ARGV.shift}")
|
10
|
+
ARGV.unshift(task) if task
|
11
|
+
klass.start(ARGV, :shell => Thor::Base.shell.new)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
$stdout.sync = true
|
4
|
+
|
5
|
+
module Travis
|
6
|
+
autoload :Keychain, 'travis/keychain'
|
7
|
+
|
8
|
+
class Deploy < Thor
|
9
|
+
autoload :Config, 'travis/deploy/config'
|
10
|
+
autoload :Deploy, 'travis/deploy/deploy'
|
11
|
+
autoload :Helper, 'travis/deploy/helper'
|
12
|
+
autoload :SecureKey, 'travis/deploy/secure_key'
|
13
|
+
|
14
|
+
namespace 'travis'
|
15
|
+
|
16
|
+
desc 'config', 'Sync config between keychain, app and local working directory'
|
17
|
+
method_option :env, :aliases => '-e', :type => :string
|
18
|
+
method_option :source, :aliases => '-s', :type => :string
|
19
|
+
method_option :backup, :aliases => '-b', :type => :boolean, :default => false
|
20
|
+
|
21
|
+
def config(remote)
|
22
|
+
Config.new(shell, remote, options).invoke
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'deploy', 'Deploy to the given remote'
|
26
|
+
method_option :migrate, :aliases => '-m', :type => :boolean, :default => false
|
27
|
+
method_option :configure, :aliases => '-c', :type => :boolean, :default => false
|
28
|
+
|
29
|
+
def deploy(remote)
|
30
|
+
Deploy.new(shell, remote, options).invoke
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'encrypt <slug> <secret>', 'Encrypt string for a repository'
|
34
|
+
method_option :host, :aliases => '-h', :type => :string
|
35
|
+
|
36
|
+
def encrypt(slug, secret)
|
37
|
+
puts "\nAbout to encrypt '#{secret}' for '#{slug}'\n\n"
|
38
|
+
|
39
|
+
encrypted = nil
|
40
|
+
begin
|
41
|
+
encrypted = SecureKey.new(slug, options[:host]).encrypt(secret)
|
42
|
+
rescue SecureKey::FetchKeyError
|
43
|
+
abort 'There was an error while fetching public key, please check if you entered correct slug'
|
44
|
+
end
|
45
|
+
|
46
|
+
puts "Please add the following to your .travis.yml file:"
|
47
|
+
puts ""
|
48
|
+
puts " secure: \"#{Base64.encode64(encrypted).strip.gsub("\n", "\\n")}\""
|
49
|
+
puts ""
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Travis
|
5
|
+
class Deploy
|
6
|
+
class Config
|
7
|
+
include Helper
|
8
|
+
|
9
|
+
attr_reader :shell, :remote, :env, :options
|
10
|
+
|
11
|
+
def initialize(shell, remote, options)
|
12
|
+
@remote = remote
|
13
|
+
@options = options
|
14
|
+
@shell = shell
|
15
|
+
@env = options['env'] || remote
|
16
|
+
end
|
17
|
+
|
18
|
+
def invoke
|
19
|
+
store unless options['source']
|
20
|
+
push
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def app
|
26
|
+
@app ||= begin
|
27
|
+
app = File.basename(Dir.pwd).gsub('travis-', '')
|
28
|
+
app = 'web' if app == 'ci'
|
29
|
+
app
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def config
|
34
|
+
@config ||= source || keychain.fetch
|
35
|
+
end
|
36
|
+
|
37
|
+
def source
|
38
|
+
File.read(options['source']) if options['source']
|
39
|
+
end
|
40
|
+
|
41
|
+
def keychain
|
42
|
+
@keychain ||= Keychain.new(app, shell)
|
43
|
+
end
|
44
|
+
|
45
|
+
def store
|
46
|
+
backup if backup?
|
47
|
+
File.open(filename, 'w+') { |f| f.write(config) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def push
|
51
|
+
say 'Configuring the app ...'
|
52
|
+
config = Shellwords.escape(YAML.dump(YAML.load(self.config)[env]))
|
53
|
+
run "heroku config:add travis_config=#{config} -r #{remote}", :echo => "heroku config:add travis_config=... -r #{remote}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def backup
|
57
|
+
say 'Backing up the old config file ...'
|
58
|
+
run "cp #{filename} #{filename}.backup"
|
59
|
+
end
|
60
|
+
|
61
|
+
def backup?
|
62
|
+
!!options['backup']
|
63
|
+
end
|
64
|
+
|
65
|
+
def filename
|
66
|
+
"config/travis.yml"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Travis
|
2
|
+
class Deploy
|
3
|
+
class Deploy
|
4
|
+
include Helper
|
5
|
+
|
6
|
+
attr_reader :shell, :remote, :options
|
7
|
+
|
8
|
+
def initialize(shell, remote, options)
|
9
|
+
@remote = remote
|
10
|
+
@options = options
|
11
|
+
@shell = shell
|
12
|
+
end
|
13
|
+
|
14
|
+
def invoke
|
15
|
+
if clean?
|
16
|
+
tag if remote == 'production'
|
17
|
+
configure if configure?
|
18
|
+
push
|
19
|
+
migrate if migrate?
|
20
|
+
else
|
21
|
+
error 'There are unstaged changes.'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
attr_reader :remote
|
28
|
+
|
29
|
+
def clean?
|
30
|
+
`git status`.include?('working directory clean')
|
31
|
+
end
|
32
|
+
|
33
|
+
def push
|
34
|
+
say "Deploying to #{remote}."
|
35
|
+
run "git push #{remote} HEAD:master -f".strip
|
36
|
+
end
|
37
|
+
|
38
|
+
def tag
|
39
|
+
say "Updating production branch."
|
40
|
+
with_branch('production') do |branch|
|
41
|
+
run "git reset --hard #{branch}"
|
42
|
+
run 'git push origin production -f'
|
43
|
+
|
44
|
+
say "Tagging #{version}."
|
45
|
+
run "git tag -a '#{version.gsub(':', '-').gsub(' ', '.')}' -m '#{version}'"
|
46
|
+
run 'git push --tags'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_branch(target)
|
51
|
+
current = branch
|
52
|
+
run "git checkout #{target}"
|
53
|
+
yield current
|
54
|
+
run "git checkout #{current}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def branch
|
58
|
+
`git branch --no-color 2> /dev/null` =~ /\* (.*)$/ && $1
|
59
|
+
end
|
60
|
+
|
61
|
+
def version
|
62
|
+
@version ||= "deploy #{Time.now.utc.strftime('%Y-%m-%d %H:%M')}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def configure?
|
66
|
+
!!options['configure']
|
67
|
+
end
|
68
|
+
|
69
|
+
def configure
|
70
|
+
Config.new(shell, remote, {}).invoke
|
71
|
+
end
|
72
|
+
|
73
|
+
def migrate?
|
74
|
+
!!options['migrate']
|
75
|
+
end
|
76
|
+
|
77
|
+
def migrate
|
78
|
+
say 'Running migrations'
|
79
|
+
run "heroku run rake db:migrate -r #{remote}"
|
80
|
+
restart
|
81
|
+
end
|
82
|
+
|
83
|
+
def restart
|
84
|
+
say 'Restarting the app ...'
|
85
|
+
run "heroku restart -r #{remote}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Travis
|
2
|
+
class Deploy
|
3
|
+
module Helper
|
4
|
+
protected
|
5
|
+
|
6
|
+
def run(cmd, options = {})
|
7
|
+
cmd = cmd.strip
|
8
|
+
puts "$ #{options[:echo] || cmd}" unless options[:echo].is_a?(FalseClass)
|
9
|
+
exit unless system(cmd)
|
10
|
+
end
|
11
|
+
|
12
|
+
def say(message)
|
13
|
+
shell.say(message, :green)
|
14
|
+
end
|
15
|
+
|
16
|
+
def error(message)
|
17
|
+
message = shell.set_color(message, :red)
|
18
|
+
shell.error(message)
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'multi_json'
|
5
|
+
require 'openssl'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Travis
|
9
|
+
class Deploy
|
10
|
+
class SecureKey
|
11
|
+
class FetchKeyError < StandardError; end
|
12
|
+
attr_reader :slug, :host
|
13
|
+
|
14
|
+
def initialize(slug, host = nil)
|
15
|
+
@slug = slug
|
16
|
+
@host = host || "api.travis-ci.org"
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt(secret)
|
20
|
+
encrypted = key.public_encrypt(secret)
|
21
|
+
end
|
22
|
+
|
23
|
+
def key
|
24
|
+
@key ||= fetch_key
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_key
|
28
|
+
uri = URI.parse("https://#{host}/repos/#{slug}/key")
|
29
|
+
|
30
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
31
|
+
http.use_ssl = true
|
32
|
+
|
33
|
+
request = Net::HTTP::Get.new(uri.request_uri, 'Accept' => 'application/vnd.travis-ci.2+json')
|
34
|
+
|
35
|
+
response = http.request(request)
|
36
|
+
|
37
|
+
if response.code.to_i == 200
|
38
|
+
body = MultiJson.decode(response.body)
|
39
|
+
public_key = body['key']
|
40
|
+
begin
|
41
|
+
OpenSSL::PKey::RSA.new(public_key)
|
42
|
+
rescue OpenSSL::PKey::RSAError
|
43
|
+
# unsure why, but it seems that some keys are generated in a
|
44
|
+
# wrong way
|
45
|
+
public_key.gsub!('RSA PUBLIC KEY', 'PUBLIC KEY')
|
46
|
+
OpenSSL::PKey::RSA.new(public_key)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise FetchKeyError
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Travis
|
2
|
+
class Keychain
|
3
|
+
include Deploy::Helper
|
4
|
+
|
5
|
+
attr_reader :app, :shell, :dir
|
6
|
+
|
7
|
+
def initialize(app, shell, dir = '../travis-keychain')
|
8
|
+
@app = app
|
9
|
+
@shell = shell
|
10
|
+
@dir = File.expand_path(dir)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch
|
14
|
+
chdir { pull }
|
15
|
+
read
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def pull
|
21
|
+
error 'There are unstaged changes in your travis-keychain working directory.' unless clean?
|
22
|
+
say 'Fetching the keychain ...'
|
23
|
+
run 'git pull'
|
24
|
+
end
|
25
|
+
|
26
|
+
def read
|
27
|
+
File.read(File.join(dir, "config/travis.#{app}.yml")) || ''
|
28
|
+
end
|
29
|
+
|
30
|
+
def chdir(&block)
|
31
|
+
FileUtils.mkdir_p(dir)
|
32
|
+
Dir.chdir(dir, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def clean?
|
36
|
+
`git status`.include?('working directory clean')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/travis_cli.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'travis/deploy'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
ENV['RAILS_ENV'] = ENV['ENV'] = 'test'
|
2
|
+
|
3
|
+
require 'travis/deploy'
|
4
|
+
require 'webmock/rspec'
|
5
|
+
|
6
|
+
RSpec.configure do |c|
|
7
|
+
c.before(:each) { Time.stub(:now => Time.now.utc) }
|
8
|
+
end
|
9
|
+
|
10
|
+
module Mock
|
11
|
+
class Shell
|
12
|
+
def messages
|
13
|
+
@messages ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def say(*args)
|
17
|
+
messages << args
|
18
|
+
end
|
19
|
+
alias :error :say
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Kernel
|
24
|
+
def capture_stdout
|
25
|
+
out = StringIO.new
|
26
|
+
$stdout = out
|
27
|
+
yield
|
28
|
+
return out.string
|
29
|
+
ensure
|
30
|
+
$stdout = STDOUT
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Travis::Deploy::Config do
|
4
|
+
let(:shell) { Mock::Shell.new }
|
5
|
+
let(:config) { "staging:\n foo: bar" }
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
Travis::Deploy::Config.any_instance.stub(:clean? => true)
|
9
|
+
Travis::Deploy::Config.any_instance.stub(:run)
|
10
|
+
Travis::Keychain.any_instance.stub(:fetch => config)
|
11
|
+
File.stub(:open)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'sync' do
|
15
|
+
it 'fetches the config from the keychain' do
|
16
|
+
command = Travis::Deploy::Config.new(shell, 'staging', {})
|
17
|
+
command.send(:keychain).should_receive(:fetch).and_return(config)
|
18
|
+
command.invoke
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'writes the config to the local config file' do
|
22
|
+
command = Travis::Deploy::Config.new(shell, 'staging', {})
|
23
|
+
File.should_receive(:open).with { |path, mode| path =~ %r(config/travis.yml) }
|
24
|
+
command.invoke
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'pushes the config to the given heroku remote' do
|
28
|
+
command = Travis::Deploy::Config.new(shell, 'staging', {})
|
29
|
+
command.should_receive(:run).with { |cmd, options| cmd =~ /heroku config:add travis_config=.* -r staging/m }
|
30
|
+
command.invoke
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Travis::Deploy::Deploy do
|
4
|
+
let(:shell) { Mock::Shell.new }
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
$stdout = StringIO.new
|
8
|
+
Travis::Deploy::Deploy.any_instance.stub(:clean? => true)
|
9
|
+
Travis::Deploy::Deploy.any_instance.stub(:branch => 'master')
|
10
|
+
File.stub(:open)
|
11
|
+
end
|
12
|
+
|
13
|
+
after :each do
|
14
|
+
$stdout = STDOUT
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'with a clean working directory' do
|
18
|
+
describe 'given remote "production"' do
|
19
|
+
let(:command) { Travis::Deploy::Deploy.new(shell, 'production', {}) }
|
20
|
+
|
21
|
+
before :each do
|
22
|
+
command.stub(:system => true)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'switches to the production branch' do
|
26
|
+
command.should_receive(:system).with('git checkout production').and_return(true)
|
27
|
+
command.invoke
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'resets the production branch to the current branch' do
|
31
|
+
command.should_receive(:system).with('git reset --hard master').and_return(true)
|
32
|
+
command.invoke
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'pushes the production branch to origin' do
|
36
|
+
command.should_receive(:system).with('git push origin production -f').and_return(true)
|
37
|
+
command.invoke
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'switches back to the previous branch' do
|
41
|
+
command.should_receive(:system).with('git checkout master').and_return(true)
|
42
|
+
command.invoke
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'tags the current commit ' do
|
46
|
+
command.should_receive(:system).with { |cmd| cmd =~ /git tag -a 'deploy.*' -m 'deploy.*'/ }.and_return(true)
|
47
|
+
command.invoke
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'pushes the tag to origin' do
|
51
|
+
command.should_receive(:system).with('git push --tags').and_return(true)
|
52
|
+
command.invoke
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'pushes to the given remote' do
|
56
|
+
command.should_receive(:system).with('git push production HEAD:master -f').and_return(true)
|
57
|
+
command.invoke
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'given the remote "staging"' do
|
62
|
+
let(:command) { Travis::Deploy::Deploy.new(shell, 'staging', {}) }
|
63
|
+
|
64
|
+
before :each do
|
65
|
+
command.stub(:system => true)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does not switch to the production branch' do
|
69
|
+
command.should_not_receive(:system).with('git checkout production')
|
70
|
+
command.invoke
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'does not tag the current commit if the given remote is "staging"' do
|
74
|
+
command.should_not_receive(:system).with { |cmd| cmd =~ /git tag -a 'deploy .*' -m 'deploy .*'/ }
|
75
|
+
command.invoke
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'pushes to the given remote' do
|
79
|
+
command.should_receive(:system).with('git push staging HEAD:master -f').and_return(true)
|
80
|
+
command.invoke
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'migrates the database if --migrate is given' do
|
85
|
+
command = Travis::Deploy::Deploy.new(shell, 'production', 'migrate' => true)
|
86
|
+
command.stub(:system => true)
|
87
|
+
command.should_receive(:system).with('heroku run rake db:migrate -r production').and_return(true)
|
88
|
+
command.invoke
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'restarts the app when the database is migrated' do
|
92
|
+
command = Travis::Deploy::Deploy.new(shell, 'production', 'migrate' => true)
|
93
|
+
command.stub(:system => true)
|
94
|
+
command.should_receive(:system).with('heroku restart -r production').and_return(true)
|
95
|
+
command.invoke
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'configures the application if --configure is given' do
|
99
|
+
command = Travis::Deploy::Deploy.new(shell, 'production', 'configure' => true)
|
100
|
+
command.stub(:system => true)
|
101
|
+
command.should_receive(:configure)
|
102
|
+
command.invoke
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'with a dirty working directory' do
|
107
|
+
before :each do
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'outputs an error message' do
|
111
|
+
command = Travis::Deploy::Deploy.new(shell, 'production', {})
|
112
|
+
command.stub(:clean? => false)
|
113
|
+
command.should_receive(:error).with('There are unstaged changes.')
|
114
|
+
command.invoke
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Travis::Deploy::SecureKey do
|
4
|
+
describe 'key' do
|
5
|
+
before do
|
6
|
+
# Travis API requests redirect to HTTPS
|
7
|
+
stub_request(:any, %r(^http://secure.travis-ci\.org/)).to_return do |request|
|
8
|
+
{
|
9
|
+
:body => '',
|
10
|
+
:status => [301, 'Moved Permanently'],
|
11
|
+
:headers => {
|
12
|
+
'Location' => request.uri.to_s.sub(/^http/, 'https')
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'allows to pass host' do
|
19
|
+
slug = 'travis-ci/travis-cli'
|
20
|
+
public_key = <<-KEY
|
21
|
+
-----BEGIN RSA PUBLIC KEY-----
|
22
|
+
MIGJAoGBAON8DcLBjBwkspYPtHvdjoOYwjsUJOh5Otr+TEOlfvUgDjc/kGRjP6ku
|
23
|
+
THug6JpLU0GlRqOr4u9sFuJCKlDjCJV+2vWzP4e+3zrP9hZGdQUcbG2fhSOBn2Wv
|
24
|
+
9wFMn+WFgJ3fEsvIb5yzNsRnH3mMe1MZkTPBZIrt+6M1lM1yOZQpAgMBAAE=
|
25
|
+
-----END RSA PUBLIC KEY-----
|
26
|
+
KEY
|
27
|
+
|
28
|
+
stub_request(:get, "https://foo.travis-ci.org/repos/#{slug}/key").to_return(
|
29
|
+
{
|
30
|
+
:body => %({"key":#{MultiJson.dump(public_key)}}),
|
31
|
+
:status => [200, 'OK']
|
32
|
+
}
|
33
|
+
)
|
34
|
+
|
35
|
+
secure_key = Travis::Deploy::SecureKey.new(slug, 'foo.travis-ci.org')
|
36
|
+
|
37
|
+
expect{
|
38
|
+
secure_key.key
|
39
|
+
}.to_not raise_error(Travis::Deploy::SecureKey::FetchKeyError)
|
40
|
+
|
41
|
+
secure_key.key.to_pem.should == OpenSSL::PKey::RSA.new(public_key).to_pem
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'fetches the public key for a valid slug' do
|
45
|
+
slug = 'travis-ci/travis-cli'
|
46
|
+
public_key = <<-KEY
|
47
|
+
-----BEGIN RSA PUBLIC KEY-----
|
48
|
+
MIGJAoGBAON8DcLBjBwkspYPtHvdjoOYwjsUJOh5Otr+TEOlfvUgDjc/kGRjP6ku
|
49
|
+
THug6JpLU0GlRqOr4u9sFuJCKlDjCJV+2vWzP4e+3zrP9hZGdQUcbG2fhSOBn2Wv
|
50
|
+
9wFMn+WFgJ3fEsvIb5yzNsRnH3mMe1MZkTPBZIrt+6M1lM1yOZQpAgMBAAE=
|
51
|
+
-----END RSA PUBLIC KEY-----
|
52
|
+
KEY
|
53
|
+
|
54
|
+
stub_request(:get, "https://api.travis-ci.org/repos/#{slug}/key").to_return(
|
55
|
+
{
|
56
|
+
:body => %({"key":#{MultiJson.dump(public_key)}}),
|
57
|
+
:status => [200, 'OK']
|
58
|
+
}
|
59
|
+
).with(:headers => {'Accept' => 'application/vnd.travis-ci.2+json' })
|
60
|
+
|
61
|
+
secure_key = Travis::Deploy::SecureKey.new(slug)
|
62
|
+
|
63
|
+
expect{
|
64
|
+
secure_key.key
|
65
|
+
}.to_not raise_error(Travis::Deploy::SecureKey::FetchKeyError)
|
66
|
+
|
67
|
+
secure_key.key.to_pem.should == OpenSSL::PKey::RSA.new(public_key).to_pem
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Travis::Keychain do
|
4
|
+
let(:shell) { stub('shell', :say => nil, :error => nil) }
|
5
|
+
let(:keychain) { Travis::Keychain.new('hub', shell) }
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
keychain.stub(:system => true)
|
9
|
+
keychain.stub(:`)
|
10
|
+
keychain.stub(:clean? => true)
|
11
|
+
File.stub(:read)
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch
|
15
|
+
capture_stdout do
|
16
|
+
keychain.fetch
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'fetch' do
|
21
|
+
it 'changes to the keychain directory' do
|
22
|
+
Dir.should_receive(:chdir).with { |path| path =~ %r(/travis-keychain$) }
|
23
|
+
fetch
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'errors if the working directory is dirty' do
|
27
|
+
keychain.stub(:clean? => false)
|
28
|
+
keychain.should_receive(:error).with('There are unstaged changes in your travis-keychain working directory.')
|
29
|
+
fetch
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'pulls changes from origin' do
|
33
|
+
keychain.should_receive(:run).with('git pull')
|
34
|
+
fetch
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'reads the configuration' do
|
38
|
+
File.should_receive(:read).with { |path| path =~ %r(config/travis.hub.yml$) }
|
39
|
+
fetch
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'travis-deploy'
|
5
|
+
gem.version = '0.1.0'
|
6
|
+
|
7
|
+
gem.authors = ['Travis Deploy Tool']
|
8
|
+
gem.email = ['contact@travis-ci.org']
|
9
|
+
gem.description = 'A command-line interface to Travis CI'
|
10
|
+
gem.summary = gem.description
|
11
|
+
gem.homepage = 'https://github.com/travis-ci/travis-cli'
|
12
|
+
|
13
|
+
gem.add_dependency 'thor', '~> 0.16.0'
|
14
|
+
gem.add_dependency 'multi_json', '~> 1.3'
|
15
|
+
gem.add_development_dependency 'rspec', '~> 2.6'
|
16
|
+
gem.add_development_dependency 'webmock', '~> 1.8'
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($/)
|
19
|
+
gem.executables = gem.files.grep(/^bin/).map{|f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(/^spec/)
|
21
|
+
gem.require_paths = ['lib']
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: travis-deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Travis Deploy Tool
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.16.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.16.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.6'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.6'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: webmock
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.8'
|
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: '1.8'
|
78
|
+
description: A command-line interface to Travis CI
|
79
|
+
email:
|
80
|
+
- contact@travis-ci.org
|
81
|
+
executables:
|
82
|
+
- travis-deploy
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- .travis.yml
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- bin/travis-deploy
|
93
|
+
- lib/travis/deploy.rb
|
94
|
+
- lib/travis/deploy/config.rb
|
95
|
+
- lib/travis/deploy/deploy.rb
|
96
|
+
- lib/travis/deploy/helper.rb
|
97
|
+
- lib/travis/deploy/secure_key.rb
|
98
|
+
- lib/travis/keychain.rb
|
99
|
+
- lib/travis_cli.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
- spec/travis/deploy/config_spec.rb
|
102
|
+
- spec/travis/deploy/deploy_spec.rb
|
103
|
+
- spec/travis/deploy/secure_key_spec.rb
|
104
|
+
- spec/travis/keychain_spec.rb
|
105
|
+
- travis-deploy.gemspec
|
106
|
+
homepage: https://github.com/travis-ci/travis-cli
|
107
|
+
licenses: []
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.23
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: A command-line interface to Travis CI
|
130
|
+
test_files:
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
- spec/travis/deploy/config_spec.rb
|
133
|
+
- spec/travis/deploy/deploy_spec.rb
|
134
|
+
- spec/travis/deploy/secure_key_spec.rb
|
135
|
+
- spec/travis/keychain_spec.rb
|
136
|
+
has_rdoc:
|