bcnd 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +131 -0
- data/Rakefile +7 -0
- data/bin/bcnd +4 -0
- data/lib/bcnd.rb +6 -0
- data/lib/bcnd/ci.rb +97 -0
- data/lib/bcnd/quay_io.rb +111 -0
- data/lib/bcnd/runner.rb +58 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 703e6c4a7228ffff25568739b2f1e18d57ffb086
|
4
|
+
data.tar.gz: 8977b5932bd0bc0ed00cb7063af63f46e30f44c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4fe701eeb0cd98f0985bd6dd9adc8fc725f9a92c8263524ce99c16815afe37e65b430080d4eff619b237b247a644ed3cfd70b6a3156baf71f1082d8a41feba7a
|
7
|
+
data.tar.gz: 4f74ad4442c5e3d867f34bca44bb6d77c898797c9dcb921fc789d1689b8fea463c785fa0c3d61a8879aa8171f858e0b71a22cf098fd59bd5c41bbc668d6e8419
|
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Barcelona Deployer
|
2
|
+
|
3
|
+
Degica's opinionated deploy pipeline tool
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```
|
8
|
+
gem 'bcnd'
|
9
|
+
```
|
10
|
+
|
11
|
+
## Prerequisites
|
12
|
+
|
13
|
+
bcnd is very opinionated about your development configurations.
|
14
|
+
So there are lots of requirements you need to prepare beforehand.
|
15
|
+
|
16
|
+
### Quay.io
|
17
|
+
|
18
|
+
You need quay.io as your docker image registry
|
19
|
+
|
20
|
+
### Barcelona
|
21
|
+
|
22
|
+
where your applications will be deployed
|
23
|
+
|
24
|
+
### GitHub
|
25
|
+
|
26
|
+
bcnd only supports GitHub as a source code repository.
|
27
|
+
Your GitHub repository must have 2 branches: mainline and stable
|
28
|
+
bcnd doesn't offer a customizability for the branch names.
|
29
|
+
|
30
|
+
#### mainline branch
|
31
|
+
|
32
|
+
a mainline branch includes all reviewed commits. By default mainline branch is `master`.
|
33
|
+
bcnd automatically deploys mainline branch to your mainline environment.
|
34
|
+
|
35
|
+
#### stable branch
|
36
|
+
|
37
|
+
a stable branch includes stable commits. By default stable branch is `production`.
|
38
|
+
bcnd automatically deploys stable branch to your stable environment.
|
39
|
+
|
40
|
+
### Webhooks
|
41
|
+
|
42
|
+
- webhook for quay.io automated builds
|
43
|
+
- webhook for your CI service
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
Setup your CI configuration as follows:
|
48
|
+
|
49
|
+
- Install barcelona client
|
50
|
+
- Install bcnd
|
51
|
+
- Execute `bcnd` in your CI's "after success" script
|
52
|
+
|
53
|
+
Here's the example for travis CI
|
54
|
+
|
55
|
+
```yml
|
56
|
+
before_script:
|
57
|
+
- npm install -g barcelona
|
58
|
+
- gem install bcnd
|
59
|
+
script:
|
60
|
+
- bundle excec rspec
|
61
|
+
- bcnd
|
62
|
+
```
|
63
|
+
|
64
|
+
### Environment variables
|
65
|
+
|
66
|
+
Set the following environment variables to your CI build environment:
|
67
|
+
|
68
|
+
- `QUAY_TOKEN`
|
69
|
+
- **Required**
|
70
|
+
- a quay.io oauth token. you can create a new oauth token at quay.io organization page -> OAuth Applications -> Create New Application -> Generate Token
|
71
|
+
- `GITHUB_TOKEN`
|
72
|
+
- **Conditional** If you have stable branch this is required.
|
73
|
+
- a github oauth token which has a permission to read your application's repository
|
74
|
+
- `BARCELONA_ENDPOINT`
|
75
|
+
- **Required**
|
76
|
+
- A URL of Barcelona service e.g. `https://barcelona.your-domain.com`
|
77
|
+
- `MAINLINE_HERITAGE_TOKEN`
|
78
|
+
- **Required**
|
79
|
+
- Barcelona's heritage token for the mainline application
|
80
|
+
- `STABLE_HERITAGE_TOKEN`
|
81
|
+
- **Conditional** If you have stable branch this is required.
|
82
|
+
- Barcelona's heritage token for the stable application
|
83
|
+
- `QUAY_REPOSITORY`
|
84
|
+
- **Optional**
|
85
|
+
- A name of your quay repository. It should be `[organization]/[repo name]`. If you don't set this variable bcnd uses github repository name as a quay repository name.
|
86
|
+
|
87
|
+
### Configurations
|
88
|
+
|
89
|
+
You can customize mainline branch name, mainline environment(where mainline code is deployed), stable branch name, and stable environment with `barcelona.yml`
|
90
|
+
|
91
|
+
```yaml
|
92
|
+
# /barcelona.yml example
|
93
|
+
---
|
94
|
+
environments:
|
95
|
+
# your barcelona configurations. bcnd doesn't touch this.
|
96
|
+
bcnd:
|
97
|
+
mainline_branch: dev # default: master
|
98
|
+
mainline_environment: dev # default: staging
|
99
|
+
stable_branch: master # default: production
|
100
|
+
stable_environment: master # default: production
|
101
|
+
```
|
102
|
+
|
103
|
+
## How It Works
|
104
|
+
|
105
|
+
### Deploying to a staging environment
|
106
|
+
|
107
|
+
When a commit is pushed into a mainline branch, bcnd deploys your application to barcelona's mainline environment.
|
108
|
+
Here's what bcnd actually does:
|
109
|
+
|
110
|
+
- Wait for quay.io automated build to be finished
|
111
|
+
- Attach a docker image tag to the `latest` docker image.
|
112
|
+
- The tag is the latest mainline branch's commit hash
|
113
|
+
- Call Barcelona deploy API with the tag name
|
114
|
+
- `bcn deploy -e [mainline env] --tag [git_commit_hash]`
|
115
|
+
|
116
|
+
### Deploying to a production environment
|
117
|
+
|
118
|
+
When a commit is pushed into a stable branch, bcnd deploys your application to barcelona's stable environment
|
119
|
+
Here's what bcnd actually does:
|
120
|
+
|
121
|
+
- Compare `master` and `production` branch
|
122
|
+
- If there is a difference between the branches, bcnd raises an error
|
123
|
+
- Get master branch's latest commit hash
|
124
|
+
- Find a docker image from quay.io with the tag of the master commit hash
|
125
|
+
- Call Barcelona deploy API with the tag name
|
126
|
+
- `bcn deploy -e [stable env] --tag [git_commit_hash]`
|
127
|
+
|
128
|
+
|
129
|
+
## LICENSE
|
130
|
+
|
131
|
+
MIT
|
data/Rakefile
ADDED
data/bin/bcnd
ADDED
data/lib/bcnd.rb
ADDED
data/lib/bcnd/ci.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module Bcnd
|
3
|
+
class CI
|
4
|
+
DEFAULT_CONFIG = {
|
5
|
+
"mainline_branch" => "master",
|
6
|
+
"mainline_environment" => "staging",
|
7
|
+
"stable_branch" => "production",
|
8
|
+
"stable_environment" => "production"
|
9
|
+
}
|
10
|
+
|
11
|
+
attr_accessor :repository,
|
12
|
+
:commit,
|
13
|
+
:branch,
|
14
|
+
:quay_repository,
|
15
|
+
:quay_token,
|
16
|
+
:github_token,
|
17
|
+
:mainline_heritage_token,
|
18
|
+
:stable_heritage_token,
|
19
|
+
:stage_config
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
load_ci_environment
|
23
|
+
load_stage_config
|
24
|
+
self.quay_token = ENV['QUAY_TOKEN']
|
25
|
+
self.github_token = ENV['GITHUB_TOKEN']
|
26
|
+
self.mainline_heritage_token = ENV['MAINLINE_HERITAGE_TOKEN']
|
27
|
+
self.stable_heritage_token = ENV['STABLE_HERITAGE_TOKEN']
|
28
|
+
self.quay_repository = ENV['QUAY_REPOSITORY'] || self.repository
|
29
|
+
end
|
30
|
+
|
31
|
+
def pull_request?
|
32
|
+
case ci_service
|
33
|
+
when :travis
|
34
|
+
ENV['TRAVIS_PULL_REQUEST'] != 'false'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ci_service
|
39
|
+
if ENV['TRAVIS']
|
40
|
+
:travis
|
41
|
+
else
|
42
|
+
:unknown
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def mainline_branch
|
47
|
+
stage_config[:mainline][:branch]
|
48
|
+
end
|
49
|
+
|
50
|
+
def stable_branch
|
51
|
+
stage_config[:stable][:branch]
|
52
|
+
end
|
53
|
+
|
54
|
+
def deploy_stage
|
55
|
+
{
|
56
|
+
mainline_branch => :mainline,
|
57
|
+
stable_branch => :stable
|
58
|
+
}[self.branch]
|
59
|
+
end
|
60
|
+
|
61
|
+
def deploy_environment
|
62
|
+
stage_config[deploy_stage][:environment]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def load_ci_environment
|
68
|
+
case ci_service
|
69
|
+
when :travis
|
70
|
+
self.repository = ENV['TRAVIS_REPO_SLUG']
|
71
|
+
self.commit = ENV['TRAVIS_COMMIT']
|
72
|
+
self.branch = ENV['TRAVIS_BRANCH']
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_stage_config
|
77
|
+
config = DEFAULT_CONFIG.merge(load_config_file)
|
78
|
+
self.stage_config = {
|
79
|
+
mainline: {
|
80
|
+
branch: config["mainline_branch"],
|
81
|
+
environment: config["mainline_environment"]
|
82
|
+
},
|
83
|
+
stable: {
|
84
|
+
branch: config["stable_branch"],
|
85
|
+
environment: config["stable_environment"]
|
86
|
+
}
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def load_config_file
|
91
|
+
file = File.read('barcelona.yml')
|
92
|
+
YAML.load(file)["bcnd"] || {}
|
93
|
+
rescue Errno::ENOENT => e
|
94
|
+
{}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/bcnd/quay_io.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Bcnd
|
5
|
+
class QuayIo
|
6
|
+
class Connection
|
7
|
+
BASE_URL = 'https://quay.io/api/v1'
|
8
|
+
attr_accessor :token
|
9
|
+
|
10
|
+
def initialize(token)
|
11
|
+
@token = token
|
12
|
+
end
|
13
|
+
|
14
|
+
def request(method: :get, path:, body: {}, query_params: {})
|
15
|
+
response = RestClient::Request.execute(
|
16
|
+
method: method,
|
17
|
+
url: "#{BASE_URL}#{path}",
|
18
|
+
payload: body.empty? ? nil : body.to_json,
|
19
|
+
headers: {
|
20
|
+
"Authorization" => "Bearer #{token}",
|
21
|
+
"Content-Type" => "application/json",
|
22
|
+
params: query_params
|
23
|
+
}
|
24
|
+
)
|
25
|
+
JSON.load(response.to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(path:, body: {}, query_params: {})
|
29
|
+
request(method: :get, path: path, body: body, query_params: query_params)
|
30
|
+
end
|
31
|
+
|
32
|
+
def put(path:, body: {}, query_params: {})
|
33
|
+
request(method: :put, path: path, body: body, query_params: query_params)
|
34
|
+
end
|
35
|
+
|
36
|
+
def post(path:, body: {}, query_params: {})
|
37
|
+
request(method: :post, path: path, body: body, query_params: query_params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(path:, body: {}, query_params: {})
|
41
|
+
request(method: :delete, path: path, body: body, query_params: query_params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :conn
|
46
|
+
|
47
|
+
def initialize(token)
|
48
|
+
@conn = Connection.new(token)
|
49
|
+
end
|
50
|
+
|
51
|
+
def automated_builds_for(repo:, git_sha:)
|
52
|
+
builds = conn.get(path: "/repository/#{repo}/build/")["builds"]
|
53
|
+
builds.select do |b|
|
54
|
+
b["trigger_metadata"]["commit"] == git_sha.downcase
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def automated_build_status(repo:, git_sha:)
|
59
|
+
builds = automated_builds_for(repo: repo, git_sha: git_sha)
|
60
|
+
phases = builds.map { |b| b["phase"] }
|
61
|
+
|
62
|
+
if !phases.include?("complete") && phases.include?("error")
|
63
|
+
return :failed
|
64
|
+
end
|
65
|
+
|
66
|
+
if phases.include?("complete")
|
67
|
+
return :finished
|
68
|
+
else
|
69
|
+
return :building
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def wait_for_automated_build(repo:, git_sha:, timeout: 3600)
|
74
|
+
loop do
|
75
|
+
status = automated_build_status(repo: repo, git_sha: git_sha)
|
76
|
+
case status
|
77
|
+
when :failed
|
78
|
+
raise "The docker build failed"
|
79
|
+
when :finished
|
80
|
+
puts ""
|
81
|
+
return
|
82
|
+
when :building
|
83
|
+
print '.'
|
84
|
+
sleep 5
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def docker_image_id_for_tag(repo:, tag:)
|
90
|
+
resp = conn.get(
|
91
|
+
path: "/repository/#{repo}/tag/",
|
92
|
+
query_params: {
|
93
|
+
"specificTag" => tag
|
94
|
+
}
|
95
|
+
)
|
96
|
+
tags = resp["tags"]
|
97
|
+
tags.find { |tag|
|
98
|
+
tag["end_ts"].nil?
|
99
|
+
}["docker_image_id"]
|
100
|
+
end
|
101
|
+
|
102
|
+
def put_tag(repo:, image_id:, tag:)
|
103
|
+
conn.put(
|
104
|
+
path: "/repository/#{repo}/tag/#{tag}",
|
105
|
+
body: {
|
106
|
+
image: image_id
|
107
|
+
}
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/bcnd/runner.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
|
3
|
+
module Bcnd
|
4
|
+
class Runner
|
5
|
+
attr_accessor :env
|
6
|
+
def initialize
|
7
|
+
self.env = Bcnd::CI.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def deploy
|
11
|
+
return if env.pull_request?
|
12
|
+
|
13
|
+
case env.deploy_stage
|
14
|
+
when :mainline
|
15
|
+
deploy_mainline
|
16
|
+
when :stable
|
17
|
+
deploy_stable
|
18
|
+
else
|
19
|
+
puts "Can't recognize the current stage"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def deploy_mainline
|
26
|
+
quay.wait_for_automated_build(repo: env.quay_repository, git_sha: env.commit)
|
27
|
+
image_id = quay.docker_image_id_for_tag(repo: env.quay_repository, tag: 'latest') # FIXME
|
28
|
+
quay.put_tag(repo: env.quay_repository, image_id: image_id, tag: env.commit)
|
29
|
+
puts "attached tag #{env.commit} to image #{image_id}"
|
30
|
+
bcn_deploy(env.commit, env.mainline_heritage_token)
|
31
|
+
end
|
32
|
+
|
33
|
+
def deploy_stable
|
34
|
+
comp = github.compare(env.repository, env.mainline_branch, env.stable_branch)
|
35
|
+
tag = comp.merge_base_commit.sha
|
36
|
+
image_id = quay.docker_image_id_for_tag(repo: env.quay_repository, tag: tag)
|
37
|
+
raise "There is no docker image to be deployed" unless image_id
|
38
|
+
|
39
|
+
bcn_deploy(tag, env.stable_heritage_token)
|
40
|
+
end
|
41
|
+
|
42
|
+
def quay
|
43
|
+
@quay ||= Bcnd::QuayIo.new(env.quay_token)
|
44
|
+
end
|
45
|
+
|
46
|
+
def github
|
47
|
+
@github ||= Octokit::Client.new(access_token: env.github_token)
|
48
|
+
end
|
49
|
+
|
50
|
+
def bcn_deploy(tag, token)
|
51
|
+
system "bcn deploy -e #{env.deploy_environment} --tag #{tag} --heritage-token #{token} 1> /dev/null"
|
52
|
+
puts "deploy triggered with tag #{tag} to #{env.deploy_environment} environment"
|
53
|
+
if $?.exitstatus != 0
|
54
|
+
raise "bcn returned non-zero exitcode #{$?.exitstatus}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bcnd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kazunori Kajihiro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: octokit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webmock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.22'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.22'
|
69
|
+
description: Degica's opinionated deployment tool.
|
70
|
+
email:
|
71
|
+
- kkajihiro@degica.com
|
72
|
+
executables:
|
73
|
+
- bcnd
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- bin/bcnd
|
80
|
+
- lib/bcnd.rb
|
81
|
+
- lib/bcnd/ci.rb
|
82
|
+
- lib/bcnd/quay_io.rb
|
83
|
+
- lib/bcnd/runner.rb
|
84
|
+
homepage: https://github.com/degica/bcnd
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.4.5.1
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Degica's opinionated deployment tool
|
108
|
+
test_files: []
|