bcnd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bcnd'
4
+ Bcnd::Runner.new.deploy
@@ -0,0 +1,6 @@
1
+ require 'bcnd/quay_io'
2
+ require 'bcnd/ci'
3
+ require 'bcnd/runner'
4
+
5
+ module Bcnd
6
+ end
@@ -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
@@ -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
@@ -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: []