travis-rollout 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e7b70d440a7f1c5df1b7cb5f8f5b9f7ebed5396
4
+ data.tar.gz: 9f1ba41d70a47bd4d76083463b6cac812606fc1e
5
+ SHA512:
6
+ metadata.gz: 7e9ea82639e6dd032c22c86ec80ca9d333e256ad244b41c15d09edaecd14a7cf5eee6e591e593f77a105df4eb2bdfc1bdbdc2d5245bb1e639616650db8949cea
7
+ data.tar.gz: ad70ac68701d6c5440660309fea0c3403a510fdc569cc8ca5364ba4e4c8ff6ce6d4bca11337504332738d859c1a193b909f7955a9c01631eb39234cccfcb64ad
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'redis'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ travis-rollout (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ redis (3.3.0)
11
+ rspec (3.4.0)
12
+ rspec-core (~> 3.4.0)
13
+ rspec-expectations (~> 3.4.0)
14
+ rspec-mocks (~> 3.4.0)
15
+ rspec-core (3.4.4)
16
+ rspec-support (~> 3.4.0)
17
+ rspec-expectations (3.4.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.4.0)
20
+ rspec-mocks (3.4.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.4.0)
23
+ rspec-support (3.4.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ redis
30
+ rspec
31
+ travis-rollout!
32
+
33
+ BUNDLED WITH
34
+ 1.12.5
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT LICENSE
2
+
3
+ Copyright (c) 2016 Travis CI GmbH <contact@travis-ci.org>
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,35 @@
1
+ # travis-rollout
2
+
3
+ Enable by setting an env var `ROLLOUT` and setting `ENV` to `production` or
4
+ `staging`.
5
+
6
+ ## Usage
7
+
8
+ ```ruby
9
+ args = { uid: 1, user: 'svenfuchs', repo: 'travis-hub' }
10
+ Travis::Rollout.run(args) do
11
+ # reroute the message
12
+ end
13
+ ```
14
+
15
+ This will match:
16
+
17
+ * uid against the percentage `ROLLOUT_PERCENT`
18
+ * remaining arg values against the env vars `ROLLOUT_USERS` and `ROLLOUT_REPOS` (as comma separated values)
19
+
20
+ `uid` can be a string or integer. For a string it will calculate the crc32 to
21
+ turn it into an integer.
22
+
23
+ If a redis instance is passed as an option it will additionally check redis:
24
+
25
+ ```ruby
26
+ Travis::Rollout.run(args, redis: redis) do
27
+ # reroute the message
28
+ end
29
+ ```
30
+
31
+ It will use the value of the env var `ROLLOUT` as a namespace (e.g. `sync`), and check the keys:
32
+
33
+ * enabling: `sync.rollout.enabled`
34
+ * args: `sync.rollout.users`, `sync.rollout.repos`, `sync.rollout.owners`
35
+ * percentage: `sync.rollout.percent`
data/exercise.md ADDED
@@ -0,0 +1,25 @@
1
+ Refactor the class `Travis::Rollout` and remove the duplication of the methods
2
+ `by_[owner|repo|user]?`, `[owner|repo|user]s` etc.
3
+
4
+ Doing so also allow arbirary `args` hash to be passed:
5
+
6
+ ```
7
+ args = {
8
+ uid: 1
9
+ user: 'carlad',
10
+ repo: 'travis-hub'
11
+ foo: 'bar'
12
+ }
13
+
14
+ Rollout.reroute(args) do
15
+ # reroute the message
16
+ end
17
+
18
+ ```
19
+
20
+ This would:
21
+
22
+ * Match the `uid` against the percentage.
23
+ * Not match the `uid` against the env var `ROLLOUT_UID` (i.e. ignore this case)
24
+ * Match the strings `carlad`, `travis-hub` and `bar` against the respective env
25
+ vars `ROLLOUT_USERS`, `ROLLOUT_REPOS`, and `ROLLOUT_FOOS`
@@ -0,0 +1,7 @@
1
+ require 'travis/rollout'
2
+
3
+ module Travis
4
+ class Rollout
5
+ VERSION = "0.0.1"
6
+ end
7
+ end
@@ -0,0 +1,112 @@
1
+ require 'zlib'
2
+
3
+ module Travis
4
+ class Rollout
5
+ ENVS = %w(production staging)
6
+
7
+ def self.run(*all, &block)
8
+ rollout = new(*all, &block)
9
+ rollout.run if rollout.matches?
10
+ end
11
+
12
+ def self.matches?(*all)
13
+ new(*all).matches?
14
+ end
15
+
16
+ attr_reader :args, :options, :block
17
+
18
+ def initialize(args = {}, options = {}, &block)
19
+ @args, @options, @block = args, options, block
20
+ end
21
+
22
+ def run
23
+ block.call || true
24
+ end
25
+
26
+ def matches?
27
+ production? and enabled? and (by_owner? or by_repo? or by_user? or by_percent?)
28
+ end
29
+
30
+ private
31
+
32
+ def production?
33
+ ENVS.include?(ENV['ENV'])
34
+ end
35
+
36
+ def enabled?
37
+ !!ENV['ROLLOUT'] && (!redis || redis.get(:"#{name}.rollout.enabled") == '1')
38
+ end
39
+
40
+ def by_owner?
41
+ !!owner && owners.include?(owner)
42
+ end
43
+
44
+ def owner
45
+ args[:owner]
46
+ end
47
+
48
+ def owners
49
+ read_collection('rollout', 'owners')
50
+ end
51
+
52
+ def by_repo?
53
+ !!repo && repos.include?(repo)
54
+ end
55
+
56
+ def repo
57
+ args[:repo]
58
+ end
59
+
60
+ def repos
61
+ read_collection('rollout', 'repos')
62
+ end
63
+
64
+ def by_user?
65
+ !!user && users.include?(user)
66
+ end
67
+
68
+ def user
69
+ args[:user]
70
+ end
71
+
72
+ def users
73
+ read_collection('rollout', 'users')
74
+ end
75
+
76
+ def by_percent?
77
+ !!uid && uid % 100 < percent
78
+ end
79
+
80
+ def uid
81
+ uid = args[:uid]
82
+ uid.is_a?(String) ? Zlib.crc32(uid).to_i & 0x7fffffff : uid
83
+ end
84
+
85
+ def percent
86
+ percent = ENV['ROLLOUT_PERCENT'] || redis && redis.get(:"#{name}.rollout.percent") || -1
87
+ percent.to_i
88
+ end
89
+
90
+ def name
91
+ ENV['ROLLOUT'].to_s.split('.').first
92
+ end
93
+
94
+ def redis
95
+ options[:redis]
96
+ end
97
+
98
+ def camelize(string)
99
+ string.to_s.sub(/./) { |char| char.upcase }
100
+ end
101
+
102
+ def read_collection(*path)
103
+ redis_path = path.map(&:downcase).join('.')
104
+ env_key = path.map(&:upcase).join('_')
105
+ if redis
106
+ redis_collection = redis.smembers(:"#{name}.#{redis_path}")
107
+ return redis_collection if redis_collection.any?
108
+ end
109
+ ENV.fetch(env_key, '').to_s.split(',')
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,169 @@
1
+ require 'redis'
2
+ require 'travis/rollout'
3
+
4
+ describe Travis::Rollout do
5
+ let(:redis) { Redis.new }
6
+ let(:env) { %w(ENV ROLLOUT ROLLOUT_OWNERS ROLLOUT_REPOS ROLLOUT_USERS ROLLOUT_PERCENT) }
7
+
8
+ before { redis.set("#{name}.rollout.enabled", '1') }
9
+ after { redis.flushall }
10
+ after { env.each { |key| ENV.delete(key) } }
11
+ subject { rollout.matches? }
12
+
13
+ shared_examples_for 'matches by owner name' do
14
+ context 'matches if the given owner name matches the OWNERS env var' do
15
+ before { ENV['ROLLOUT_OWNERS'] = owner }
16
+ it { should eq true }
17
+ end
18
+
19
+ context 'matches if the given owner name matches the redis key [name].rollout.owners' do
20
+ before { redis.sadd("#{name}.rollout.owners", owner) }
21
+ it { should eq true }
22
+ end
23
+ end
24
+
25
+ shared_examples_for 'does not match by owner name' do
26
+ context 'does not match even if the given owner name matches the OWNERS env var' do
27
+ before { ENV['ROLLOUT_OWNERS'] = owner }
28
+ it { should eq false }
29
+ end
30
+
31
+ context 'does not match even if the given owner name matches the redis key [name].rollout.owners' do
32
+ before { redis.sadd("#{name}.rollout.owners", owner) }
33
+ it { should eq false }
34
+ end
35
+ end
36
+
37
+ shared_examples_for 'matches by repo slug' do
38
+ context 'matches if the given repo slug matches the REPOS env var' do
39
+ before { ENV['ROLLOUT_REPOS'] = repo }
40
+ it { should eq true }
41
+ end
42
+
43
+ context 'matches if the given repo slug matches the redis key [name].rollout.repos' do
44
+ before { redis.sadd("#{name}.rollout.repos", repo) }
45
+ it { should eq true }
46
+ end
47
+ end
48
+
49
+ shared_examples_for 'does not match by repo slug' do
50
+ context 'does not match even if the given repo slug matches the REPOS env var' do
51
+ before { ENV['ROLLOUT_REPOS'] = repo }
52
+ it { should eq false }
53
+ end
54
+
55
+ context 'does not match even if the given repo slug matches the redis key [name].rollout.repos' do
56
+ before { redis.sadd("#{name}.rollout.repos", repo) }
57
+ it { should eq false }
58
+ end
59
+ end
60
+
61
+ shared_examples_for 'matches by user name' do
62
+ context 'matches if the given user name matches the REPOS env var' do
63
+ before { ENV['ROLLOUT_USERS'] = user }
64
+ it { should eq true }
65
+ end
66
+
67
+ context 'matches if the given user name matches the redis key [name].rollout.users' do
68
+ before { redis.sadd("#{name}.rollout.users", user) }
69
+ it { should eq true }
70
+ end
71
+ end
72
+
73
+ shared_examples_for 'does not match by user name' do
74
+ context 'does not match even if the given user slug matches the REPOS env var' do
75
+ before { ENV['ROLLOUT_USERS'] = user }
76
+ it { should eq false }
77
+ end
78
+
79
+ context 'does not match even if the given user name matches the redis key [name].rollout.users' do
80
+ before { redis.sadd("#{name}.rollout.users", user) }
81
+ it { should eq false }
82
+ end
83
+ end
84
+
85
+ shared_examples_for 'matches by percentage' do
86
+ context 'matches if the given id matches the ROLLOUT_PERCENT env var' do
87
+ before { ENV['ROLLOUT_PERCENT'] = '100' }
88
+ it { should eq true }
89
+ end
90
+
91
+ context 'matches if the given id matches the redis key [name].rollout.percent' do
92
+ before { redis.set("#{name}.rollout.percent", 100) }
93
+ it { should eq true }
94
+ end
95
+ end
96
+
97
+ shared_examples_for 'does not match by percentage' do
98
+ context 'does not match even if the given id matches the ROLLOUT_PERCENT env var' do
99
+ before { ENV['ROLLOUT_PERCENT'] = '100' }
100
+ it { should eq false }
101
+ end
102
+
103
+ context 'does not match even if the given id matches the redis key [name].rollout.percent' do
104
+ before { redis.set("#{name}.rollout.percent", 100) }
105
+ it { should eq false }
106
+ end
107
+ end
108
+
109
+ shared_examples_for 'matches by' do |type|
110
+ context 'with ROLLOUT being set in production' do
111
+ before { ENV['ENV'] = 'production' }
112
+ before { ENV['ROLLOUT'] = name }
113
+ include_examples "matches by #{type}"
114
+ end
115
+
116
+ context 'with ROLLOUT being set in staging' do
117
+ before { ENV['ENV'] = 'production' }
118
+ before { ENV['ROLLOUT'] = name }
119
+ include_examples "matches by #{type}"
120
+ end
121
+
122
+ context 'with ROLLOUT being set in development' do
123
+ before { ENV['ENV'] = 'development' }
124
+ before { ENV['ROLLOUT'] = name }
125
+ include_examples "does not match by #{type}"
126
+ end
127
+
128
+ context 'with ROLLOUT not set in production' do
129
+ before { ENV['ENV'] = 'production' }
130
+ include_examples "does not match by #{type}"
131
+ end
132
+ end
133
+
134
+ # Gatekeeper knows an event uuid, an owner name, and a repo name
135
+ describe 'as used in gatekeeper' do
136
+ let(:name) { 'gator' }
137
+ let(:id) { '517be336-f16d-45cf-aa9b-a429547af6ad' }
138
+ let(:owner) { 'carlad' }
139
+ let(:repo) { 'travis-ci/travis-hub' }
140
+ let(:rollout) { described_class.new({ uid: id, owner: owner, repo: repo }, redis: redis) }
141
+
142
+ include_examples 'matches by', 'owner name'
143
+ include_examples 'matches by', 'repo slug'
144
+ include_examples 'matches by', 'percentage'
145
+ end
146
+
147
+ # Sync knows a user id, or repo id
148
+ describe 'as used in sync' do
149
+ let(:name) { 'sync' }
150
+
151
+ describe 'in the user sync worker' do
152
+ let(:id) { 1 }
153
+ let(:user) { 'carlad' }
154
+ let(:rollout) { described_class.new({ uid: id, user: user }, redis: redis) }
155
+
156
+ include_examples 'matches by', 'user name'
157
+ include_examples 'matches by', 'percentage'
158
+ end
159
+
160
+ describe 'in the repo branches sync worker' do
161
+ let(:id) { 1 } # user id
162
+ let(:owner) { 'carlad' }
163
+ let(:rollout) { described_class.new({ uid: id, owner: owner }, redis: redis) }
164
+
165
+ include_examples 'matches by', 'owner name'
166
+ include_examples 'matches by', 'percentage'
167
+ end
168
+ end
169
+ end
File without changes
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'travis/rollout/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'travis-rollout'
8
+ s.version = Travis::Rollout::VERSION
9
+ s.authors = ['Travis CI']
10
+ s.email = 'contact@travis-ci.org'
11
+ s.homepage = 'https://github.com/travis-ci/travis-rollout'
12
+ s.summary = 'Small helper class for rolling out apps'
13
+ s.description = "#{s.summary}."
14
+
15
+ s.files = Dir['{lib/**/*,spec/**/*,[A-Z]*}']
16
+ s.platform = Gem::Platform::RUBY
17
+ s.require_paths = ['lib']
18
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: travis-rollout
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Travis CI
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Small helper class for rolling out apps.
14
+ email: contact@travis-ci.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - Gemfile
20
+ - Gemfile.lock
21
+ - LICENSE
22
+ - README.md
23
+ - exercise.md
24
+ - lib/travis/rollout.rb
25
+ - lib/travis/rollout/version.rb
26
+ - spec/rollout_spec.rb
27
+ - spec/spec_helper.rb
28
+ - travis-rollout.gemspec
29
+ homepage: https://github.com/travis-ci/travis-rollout
30
+ licenses: []
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.4.5
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Small helper class for rolling out apps
52
+ test_files: []
53
+ has_rdoc: