keepit 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: cc0d1dfa47aa651e39fd114b33e6065f02809111
4
+ data.tar.gz: 7a7a3ce7e46f67e824435d28a3d57c794cb78a6f
5
+ SHA512:
6
+ metadata.gz: 2e5844e3312176679e74388a65a30dd4ea4324fc03cd3d1c873e52d4cba9b7ee6a482cbafc2e6f24db840ae564beda3dd2fa5f59ba7745a0c9885578b51d2802
7
+ data.tar.gz: cb4c0ab8c1da0a147a358d62d4bfb6ce0fb5a97886115197ba5eee4faefd442fcb890fc06c458eaf3bd910387365429bcad64c94819d8d141111eba4b528e264
@@ -0,0 +1,16 @@
1
+ build:
2
+ test:
3
+ image: abakpress/dind-testing
4
+ pull: true
5
+ privileged: true
6
+ volumes:
7
+ - /home/data/drone/images:/images
8
+ - /home/data/drone/gems:/bundle
9
+ environment:
10
+ - COMPOSE_FILE_EXT=drone
11
+ - RUBY_IMAGE_TAG=2.2-latest
12
+ commands:
13
+ - wrapdocker docker -v
14
+ - fetch-images --image abakpress/ruby-app:$RUBY_IMAGE_TAG
15
+ - dip provision
16
+ - dip rspec
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --tty
3
+ --format progress
4
+ --order random
File without changes
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in keepit.gemspec
4
+ gemspec
@@ -0,0 +1,29 @@
1
+ # Keepit
2
+
3
+ Tools for safe interacting with external api services.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'keepit'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install keepit
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `dip provision` to install dependencies. Then, run `dip rspec` to run the tests.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/abak-press/keepit.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/dip.yml ADDED
@@ -0,0 +1,38 @@
1
+ version: '1'
2
+
3
+ environment:
4
+ DOCKER_RUBY_VERSION: 2.2
5
+ RUBY_IMAGE_TAG: 2.2-latest
6
+ COMPOSE_FILE_EXT: development
7
+ RAILS_ENV: test
8
+ APRESS_GEMS_CREDENTIALS: ""
9
+
10
+ compose:
11
+ files:
12
+ - docker-compose.yml
13
+ - docker-compose.${COMPOSE_FILE_EXT}.yml
14
+
15
+ interaction:
16
+ sh:
17
+ service: app
18
+
19
+ irb:
20
+ service: app
21
+ command: irb
22
+
23
+ bundle:
24
+ service: app
25
+ command: bundle
26
+
27
+ rspec:
28
+ service: app
29
+ command: bundle exec rspec
30
+
31
+ clean:
32
+ service: app
33
+ command: rm -f Gemfile.lock
34
+
35
+ provision:
36
+ - docker volume create --name bundler_data
37
+ - dip clean
38
+ - dip bundle install
@@ -0,0 +1,13 @@
1
+ version: '2'
2
+
3
+ services:
4
+ app:
5
+ volumes:
6
+ - .:/app
7
+ - ../:/localgems
8
+ - bundler-data:/bundle
9
+
10
+ volumes:
11
+ bundler-data:
12
+ external:
13
+ name: bundler_data
@@ -0,0 +1,7 @@
1
+ version: '2'
2
+
3
+ services:
4
+ app:
5
+ volumes:
6
+ - .:/app
7
+ - /bundle:/bundle
@@ -0,0 +1,9 @@
1
+ version: '2'
2
+
3
+ services:
4
+ app:
5
+ image: abakpress/ruby-app:$RUBY_IMAGE_TAG
6
+ environment:
7
+ - BUNDLE_PATH=/bundle/$DOCKER_RUBY_VERSION
8
+ - BUNDLE_CONFIG=/app/.bundle/config
9
+ command: bash
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "keepit/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "keepit"
7
+ spec.version = Keepit::VERSION
8
+ spec.authors = ["Michail Merkushin"]
9
+ spec.email = ["merkushin.m.s@gmail.com"]
10
+ spec.summary = "Classes for protection"
11
+ spec.homepage = "https://github.com/abak-press/keepit"
12
+
13
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
14
+ f.match(%r{^(test|spec|features)/})
15
+ end
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_runtime_dependency "redis"
21
+ spec.add_runtime_dependency "dry-configurable"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "mock_redis"
27
+ spec.add_development_dependency "timecop"
28
+ spec.add_development_dependency "pry-byebug"
29
+ spec.add_development_dependency "test-unit"
30
+ end
@@ -0,0 +1,17 @@
1
+ require "redis"
2
+ require "dry/configurable"
3
+ require "keepit/decaying"
4
+ require "keepit/transient_store"
5
+ require "keepit/locker"
6
+ require "keepit/guard"
7
+ require "keepit/version"
8
+
9
+ module Keepit
10
+ extend Dry::Configurable
11
+
12
+ setting :redis
13
+
14
+ def self.redis
15
+ config.redis || ::Redis.current
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ module Keepit
2
+ # A float value which decays exponentially toward 0 over time.
3
+ class Decaying
4
+ attr_accessor :e
5
+ attr_accessor :p
6
+
7
+ # opts - Hash
8
+ # :p - Float (0.0) The initial value
9
+ # :e - Float (Math::E) Exponent base
10
+ # :r - Float (Math.log(0.5) / 10) Timescale factor - defaulting to decay 50% every 10 seconds
11
+ def initialize(opts = {})
12
+ @p = opts[:p] || 0.0
13
+ @e = opts[:e] || Math::E
14
+ @r = opts[:r] || Math.log(0.5) / 10
15
+ @t0 = Time.now.to_i
16
+ end
17
+
18
+ # Add to current value
19
+ #
20
+ # d - Float value to add
21
+ def <<(d)
22
+ @p = value + d
23
+ end
24
+
25
+ # Returns Float the current value (adjusted for the time decay)
26
+ def value
27
+ return 0.0 unless @p > 0
28
+ now = Time.now.to_i
29
+ dt = now - @t0
30
+ @t0 = now
31
+ @p *= @e**(@r * dt)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ # Базовый класс защитника от ошибок при обращении к сторонним сервисам.
2
+ #
3
+ # Основной принцип работы:
4
+ #
5
+ # Каждый сервис-клиент реализует свой класс защитника со списком возможных исключений.
6
+ # Если сторонний сервис возвращает определнное кол-во ошибок, которое больше некого порога,
7
+ # то защитник перестает давать общаться с ним на некоторое время.
8
+ # До преодоления порога мы сообщаем об ошибках в NewRelic и письмом проектому менеджеру.
9
+ # Также защитник не дает общаться с сервисом, если он находится на тех. обслуживании.
10
+ #
11
+ # Examples:
12
+ #
13
+ # Keepit::Guard.config.error_notificator = ->(resource, error) { ErrorMailer.report(resource, error) }
14
+ #
15
+ # module BarmenClient
16
+ # class Guard < ::Keepit::Guard
17
+ # config.resource = "barmen".freeze
18
+ # config.rescue_errors = [ActiveResource::ConnectionError]
19
+ # end
20
+ # end
21
+ #
22
+ # BarmenClient::Guard.wrap { BarmenClient::Banner.find(...) }
23
+ #
24
+ module Keepit
25
+ class Guard
26
+ extend Dry::Configurable
27
+
28
+ setting :resource, reader: true
29
+ setting :rescue_errors, [StandardError], reader: true
30
+ setting :max_error_rate, 10, reader: true
31
+ setting :error_notificator, reader: true
32
+
33
+ class << self
34
+ def wrap
35
+ return nil unless fine?
36
+ yield
37
+ rescue *rescue_errors => error
38
+ error_rates[resource] << 1
39
+ error_notificator.call(resource, error)
40
+ nil
41
+ end
42
+
43
+ def reset
44
+ error_rates.clear
45
+ end
46
+
47
+ def fine?
48
+ !error_rate_exceeded? && !resource_locked?
49
+ end
50
+
51
+ private
52
+
53
+ def error_rates
54
+ @error_rates ||= Hash.new { |hash, key| hash[key] = ::Keepit::Decaying.new }
55
+ end
56
+
57
+ def error_rate_exceeded?
58
+ error_rates[resource].value >= (max_error_rate || DEFAULT_MAX_ERROR_RATE)
59
+ end
60
+
61
+ def resource_locked?
62
+ ::Keepit::Locker.locked?(resource, check_global: false)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ module Keepit
2
+ module Locker
3
+ GLOBAL = "global".freeze
4
+ KEY = "keepit:locks".freeze
5
+ EXPIRES_IN = 60
6
+
7
+ def self.lock(resources, blocking = false)
8
+ result = ::Keepit.redis.sadd(KEY, resources)
9
+ sleep(EXPIRES_IN) if blocking
10
+
11
+ result
12
+ end
13
+
14
+ def self.unlock(resources, blocking = false)
15
+ result = ::Keepit.redis.srem(KEY, resources)
16
+ sleep(EXPIRES_IN) if blocking
17
+
18
+ result
19
+ end
20
+
21
+ def self.locked?(resource, check_global: true)
22
+ if check_global
23
+ locked_resources.any? { |r| r == resource || r == GLOBAL }
24
+ else
25
+ locked_resources.any? { |r| r == resource }
26
+ end
27
+ end
28
+
29
+ def self.locked_resources
30
+ ::Keepit::TransientStore.fetch(:locks, expires_in: EXPIRES_IN) do
31
+ Array(::Keepit.redis.smembers(KEY))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ module Keepit
2
+ module TransientStore
3
+ Value = Struct.new(:value, :expired_at) do
4
+ def expired?
5
+ expired_at < Time.now.to_i
6
+ end
7
+ end
8
+
9
+ def self.clear
10
+ store.clear
11
+ end
12
+
13
+ def self.delete(key)
14
+ item = store.delete(key)
15
+ item.value if item
16
+ end
17
+
18
+ def self.empty?
19
+ store.empty?
20
+ end
21
+
22
+ def self.exist?(key)
23
+ return true if store.key?(key) && !store[key].expired?
24
+
25
+ delete(key)
26
+
27
+ false
28
+ end
29
+
30
+ def self.fetch(key, expires_in:, &block)
31
+ if exist?(key)
32
+ store[key].value
33
+ else
34
+ value = yield
35
+ write(key, value, expires_in: expires_in)
36
+ end
37
+ end
38
+
39
+ def self.read(key)
40
+ store[key].value if exist?(key)
41
+ end
42
+
43
+ def self.store
44
+ Thread.current[:keepit_transient_store] ||= {}
45
+ end
46
+
47
+ def self.write(key, value, expires_in:)
48
+ store[key] = Value.new(value, Time.now.to_i + expires_in.to_i)
49
+
50
+ value
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Keepit
2
+ VERSION = "0.1.0".freeze
3
+ end
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: keepit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michail Merkushin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-07-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-configurable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mock_redis
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: timecop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: test-unit
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email:
141
+ - merkushin.m.s@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".drone.yml"
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - CHANGELOG.md
150
+ - Gemfile
151
+ - README.md
152
+ - Rakefile
153
+ - dip.yml
154
+ - docker-compose.development.yml
155
+ - docker-compose.drone.yml
156
+ - docker-compose.yml
157
+ - keepit.gemspec
158
+ - lib/keepit.rb
159
+ - lib/keepit/decaying.rb
160
+ - lib/keepit/guard.rb
161
+ - lib/keepit/locker.rb
162
+ - lib/keepit/transient_store.rb
163
+ - lib/keepit/version.rb
164
+ homepage: https://github.com/abak-press/keepit
165
+ licenses: []
166
+ metadata: {}
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubyforge_project:
183
+ rubygems_version: 2.6.8
184
+ signing_key:
185
+ specification_version: 4
186
+ summary: Classes for protection
187
+ test_files: []