bcnd 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.
@@ -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: []