checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
---
2
2
SHA256:
3
- metadata.gz: 4603de2e6326eab5818c348b3455b0677ab4e50ea9626a671ff011537a7c7823
4
- data.tar.gz: 379cc0a81b59117a9a61058492252f8508ac08f6a4fe748be4ebda1a53e4d1fa
3
+ metadata.gz: caed325a3eff3edb0ddfc9cc8ac8239c1fc258fc64c22b7815e39d3a27bc050c
4
+ data.tar.gz: c5c25fbae8d87cf6b72c56cd6dd1087ef627ae3a55f60c87b5a7fd4c8b93f10f
5
5
SHA512:
6
- metadata.gz: c6832199852204931887073f7c783aadac05d3b7b7d9d81f0059d67685ef63fcb2432bc046d67cfd6f4140cf60643a87857d74b62d1c944fe9e3e466cd19c137
7
- data.tar.gz: 6b150aff4364bc079e52d553404ace6e5b0a21dcd9156b5718cbfc67c0bcad183e2a77fb1cbfcd2bee3d6c0b96bf3d679fc3744cd390031401a5eb8c6ccacac8
6
+ metadata.gz: b14c7ac8b74a330928d069065cb396a6ee25b556611739e84b2b0cc857cc42a73b58b2bbce7cdc380390cee2a09f6a47638bb0820197990cf7632217fab5ff38
7
+ data.tar.gz: 139b5610355363eb11e0b2f7ffe87e29d66e02d99452262308a8bae9fe93da41824200d3bff11c0d301b560bbc8aed9fa74cfacdb340221a48e8b48c9fd8645d
data/.drone.yml ADDED
@@ -0,0 +1,41 @@
1
+ kind: pipeline
2
+ name: Bearer Ruby - Publish Gem
3
+
4
+ steps:
5
+ - name: Install
6
+ image: ruby:2.6.5
7
+ commands:
8
+ - gem build bearer.gemspec
9
+ - name: Push
10
+ image: ruby:2.6.5
11
+ commands:
12
+ - /bin/bash bin/setup-credentials
13
+ - gem push *.gem
14
+ environment:
15
+ RUBYGEMS_AUTH_TOKEN:
16
+ from_secret: RUBYGEMS_AUTH_TOKEN
17
+ trigger:
18
+ event:
19
+ - tag
20
+ ref:
21
+ - refs/tags/release-v*
22
+
23
+ ---
24
+ kind: pipeline
25
+ name: Bearer Ruby - Build and Test
26
+
27
+ clone:
28
+ depth: 50
29
+
30
+ steps:
31
+ - name: install dependencies & test & rubocop
32
+ image: ruby:2.6.5
33
+ commands:
34
+ - gem install bundler
35
+ - bundle install --deployment --without development
36
+ - bundle exec rspec spec
37
+ - bundle exec rubocop
38
+ when:
39
+ event:
40
+ - pull_request
41
+ - push
data/.github/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @RadekMolenda @cfabianski
data/.github/main.workflow DELETED
@@ -1,23 +0,0 @@
1
- workflow "Build, Test, and Publish" {
2
- on = "push"
3
- resolves = ["Publish"]
4
- }
5
-
6
- action "Build" {
7
- uses = "scarhand/actions-ruby@master"
8
- args = "build *.gemspec"
9
- }
10
-
11
- # Filter for a new tag
12
- action "Tag" {
13
- needs = "Build"
14
- uses = "actions/bin/filter@master"
15
- args = "tag v*"
16
- }
17
-
18
- action "Publish" {
19
- needs = "Tag"
20
- uses = "scarhand/actions-ruby@master"
21
- args = "push *.gem"
22
- secrets = ["RUBYGEMS_AUTH_TOKEN"]
23
- }
data/.rubocop.yml CHANGED
@@ -18,15 +18,22 @@ Metrics/LineLength:
18
18
Exclude:
19
19
- 'config/**/*'
20
20
- 'spec/**/*'
21
- Max: 120
21
+ Max: 160
22
+
23
+ Metrics/ClassLength:
24
+ Max: 200
22
25
23
26
Metrics/BlockLength:
24
27
Exclude:
25
28
- 'app/admin/**/*'
26
29
30
+
27
31
Metrics/MethodLength:
28
32
Max: 20
29
33
34
+ Metrics/AbcSize:
35
+ Max: 20
36
+
30
37
Style/Documentation:
31
38
Enabled: false
32
39
@@ -38,3 +45,6 @@ Layout/AlignParameters:
38
45
39
46
Bundler/OrderedGems:
40
47
Enabled: false
48
+
49
+ Metrics/ParameterLists:
50
+ Enabled: false
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.1
1
+ 2.6.5
data/Gemfile CHANGED
@@ -4,6 +4,20 @@ source "https://rubygems.org"
4
4
5
5
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
7
- gem "webmock"
8
7
# Specify your gem's dependencies in bearer.gemspec
9
8
gemspec
9
+
10
+ group :development do
11
+ gem "rake", "~> 10.0"
12
+ gem "pry", "~> 0.12.2"
13
+ gem "pry-byebug", "~> 3.7.0"
14
+ gem "overcommit", "~> 0.50.0"
15
+ gem "solargraph", "~> 0.37.2"
16
+ end
17
+
18
+ group :ci do
19
+ gem "bundler", "~> 2.0"
20
+ gem "rspec", "~> 3.0"
21
+ gem "rubocop", "~> 0.65.0"
22
+ gem "webmock", "~> 3.7.6"
23
+ end
data/Gemfile.lock CHANGED
@@ -1,52 +1,61 @@
1
1
PATH
2
2
remote: .
3
3
specs:
4
- bearer (0.2.0)
4
+ bearer (3.0.0)
5
5
6
6
GEM
7
7
remote: https://rubygems.org/
8
8
specs:
9
- addressable (2.6.0)
10
- public_suffix (>= 2.0.2, < 4.0)
9
+ addressable (2.7.0)
10
+ public_suffix (>= 2.0.2, < 5.0)
11
11
ast (2.4.0)
12
- childprocess (0.9.0)
13
- ffi (~> 1.0, >= 1.0.11)
12
+ backport (1.1.2)
13
+ byebug (11.0.1)
14
+ childprocess (3.0.0)
14
15
coderay (1.1.2)
15
16
crack (0.4.3)
16
17
safe_yaml (~> 1.0.0)
17
18
diff-lcs (1.3)
18
- ffi (1.10.0)
19
- hashdiff (0.3.8)
19
+ hashdiff (1.0.0)
20
+ htmlentities (4.3.4)
20
21
iniparse (1.4.4)
21
- jaro_winkler (1.5.2)
22
+ jaro_winkler (1.5.3)
22
23
method_source (0.9.2)
23
- overcommit (0.46.0)
24
- childprocess (~> 0.6, >= 0.6.3)
24
+ mini_portile2 (2.4.0)
25
+ nokogiri (1.10.4)
26
+ mini_portile2 (~> 2.4.0)
27
+ overcommit (0.50.0)
28
+ childprocess (>= 0.6.3, < 4)
25
29
iniparse (~> 1.4)
26
- parallel (1.14.0)
27
- parser (2.6.0.0)
30
+ parallel (1.17.0)
31
+ parser (2.6.4.1)
28
32
ast (~> 2.4.0)
29
33
powerpack (0.1.2)
30
34
pry (0.12.2)
31
35
coderay (~> 1.1.0)
32
36
method_source (~> 0.9.0)
37
+ pry-byebug (3.7.0)
38
+ byebug (~> 11.0)
39
+ pry (~> 0.10)
33
40
psych (3.1.0)
34
- public_suffix (3.0.3)
41
+ public_suffix (4.0.1)
35
42
rainbow (3.0.0)
36
43
rake (10.5.0)
44
+ reverse_markdown (1.3.0)
45
+ nokogiri
37
46
rspec (3.8.0)
38
47
rspec-core (~> 3.8.0)
39
48
rspec-expectations (~> 3.8.0)
40
49
rspec-mocks (~> 3.8.0)
41
- rspec-core (3.8.0)
50
+ rspec-core (3.8.2)
42
51
rspec-support (~> 3.8.0)
43
- rspec-expectations (3.8.2)
52
+ rspec-expectations (3.8.4)
44
53
diff-lcs (>= 1.2.0, < 2.0)
45
54
rspec-support (~> 3.8.0)
46
- rspec-mocks (3.8.0)
55
+ rspec-mocks (3.8.1)
47
56
diff-lcs (>= 1.2.0, < 2.0)
48
57
rspec-support (~> 3.8.0)
49
- rspec-support (3.8.0)
58
+ rspec-support (3.8.2)
50
59
rubocop (0.65.0)
51
60
jaro_winkler (~> 1.5.1)
52
61
parallel (~> 1.10)
@@ -56,26 +65,43 @@ GEM
56
65
rainbow (>= 2.2.2, < 4.0)
57
66
ruby-progressbar (~> 1.7)
58
67
unicode-display_width (~> 1.4.0)
59
- ruby-progressbar (1.10.0)
68
+ ruby-progressbar (1.10.1)
60
69
safe_yaml (1.0.5)
70
+ solargraph (0.37.2)
71
+ backport (~> 1.1)
72
+ bundler (>= 1.17.2)
73
+ htmlentities (~> 4.3, >= 4.3.4)
74
+ jaro_winkler (~> 1.5)
75
+ nokogiri (~> 1.9, >= 1.9.1)
76
+ parser (~> 2.3)
77
+ reverse_markdown (~> 1.0, >= 1.0.5)
78
+ rubocop (~> 0.52)
79
+ thor (~> 0.19, >= 0.19.4)
80
+ tilt (~> 2.0)
81
+ yard (~> 0.9)
82
+ thor (0.20.3)
83
+ tilt (2.0.10)
61
84
unicode-display_width (1.4.1)
62
- webmock (3.5.1)
85
+ webmock (3.7.6)
63
86
addressable (>= 2.3.6)
64
87
crack (>= 0.3.2)
65
- hashdiff
88
+ hashdiff (>= 0.4.0, < 2.0.0)
89
+ yard (0.9.20)
66
90
67
91
PLATFORMS
68
92
ruby
69
93
70
94
DEPENDENCIES
71
95
bearer!
72
- bundler (~> 1.16)
73
- overcommit
74
- pry
96
+ bundler (~> 2.0)
97
+ overcommit (~> 0.50.0)
98
+ pry (~> 0.12.2)
99
+ pry-byebug (~> 3.7.0)
75
100
rake (~> 10.0)
76
101
rspec (~> 3.0)
77
102
rubocop (~> 0.65.0)
78
- webmock
103
+ solargraph (~> 0.37.2)
104
+ webmock (~> 3.7.6)
79
105
80
106
BUNDLED WITH
81
- 1.17.2
107
+ 2.0.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Bearer
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
# Bearer
2
2
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/bearer`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ This gem is a Ruby client to universally call any API using [Bearer.sh](https://www.bearer.sh).
4
4
5
- TODO: Delete this and the text above, and describe your gem
5
+ _NB: If you are using Rails, also have a look at the [Rails](https://github.com/bearer/bearer-rails) gem_
6
6
7
7
## Installation
8
8
@@ -14,26 +14,93 @@ gem 'bearer'
14
14
15
15
And then execute:
16
16
17
- $ bundle
18
-
17
+ ```shell
18
+ $ bundle
19
+ ```
19
20
Or install it yourself as:
20
21
21
- $ gem install bearer
22
+ ```shell
23
+ $ gem install bearer
24
+ ```
22
25
23
26
## Usage
24
27
25
- TODO: Write usage instructions here
28
+ Grab your Bearer [Secret Key](https://app.bearer.sh/keys) and integration id from
29
+ the [Dashboard](https://app.bearer.sh) and then you can use the client as follows:
30
+
31
+ ### Calling any APIs
32
+
33
+ ```ruby
34
+ require "bearer"
35
+
36
+ bearer = Bearer.new("BEARER_SECRET_KEY") # find it on https://app.bearer.sh/keys
37
+ github = (
38
+ bearer
39
+ .integration("your integration id") # you'll find it on the Bearer dashboard https://app.bearer.sh
40
+ .auth("your auth id") # Create an auth id for your integration via the dashboard
41
+ )
42
+
43
+ puts github.get("/repositories").data
44
+ ```
45
+
46
+ We use `Net::HTTP` internally and we
47
+ return it's response from the request methods (`request`,
48
+ `get`, `head`, `post`, `put`, `patch`, `delete`).
49
+
50
+ More advanced examples:
51
+
52
+ ```ruby
53
+ # With query parameters
54
+ puts github.get("/repositories", query: { since: 364 }).data
55
+
56
+ # With body data
57
+ puts github.post("/user/repos", body: { name: "Just setting up my Bearer.sh" }).data
58
+ ```
59
+
60
+ ### Global configuration
61
+
62
+ You can configure the client globally with your [Secret Key](https://app.bearer.sh/keys):
63
+
64
+ ```ruby
65
+ Bearer::Configuration.setup do |config|
66
+ config.secret_key = "BEARER_SECRET_KEY" # copy and paste your Bearer `Secret Key`
67
+ end
68
+ ```
69
+
70
+ You can now use the client without having to pass the Secret Key each time:
71
+
72
+ ```ruby
73
+ github = Bearer.integration("your integration id").auth("your auth id")
74
+
75
+ puts github.get("/repositories").data
76
+ ```
77
+ ### Setting the request timeout
78
+
79
+ By default in bearer client read and open timeouts are set to 5 seconds. Bearer allows to increase the read timeout to up to 30 seconds
80
+
81
+ ```ruby
82
+ Bearer::Configuration.setup do |config|
83
+ # increase the request timeout to 10 seconds, and reduce the open connection timeout to 1 second
84
+ config.http_client_settings = { read_timeout: 10, open_timeout: 1 }
85
+ end
86
+
87
+ # it is also possible to set the read_timeout and other Net::HTTP client settings per integration
88
+
89
+ github = Bearer.integration("your integration id", { read_timeout: 10 })
90
+
91
+ puts github.get("/repositories").data # This request will timeout after 10 seconds
92
+ ```
26
93
27
94
## Development
28
95
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
96
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Also make sure you have `overcommit` installed and initialized in the repo [check overcommit repo](https://github.com/sds/overcommit) for further reference.
30
97
31
98
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).
32
99
33
100
## Contributing
34
101
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bearer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
102
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bearer/bearer-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
103
37
104
## Code of Conduct
38
105
39
- Everyone interacting in the Bearer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/bearer/blob/master/CODE_OF_CONDUCT.md).
106
+ Everyone interacting in the Bearer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bearer/bearer-ruby/blob/master/CODE_OF_CONDUCT.md).
data/bearer.gemspec CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |spec|
6
6
spec.name = "bearer"
7
7
spec.version = Bearer::VERSION
8
8
spec.authors = ["Bearer Team<engineering@bearer.sh>"]
9
- spec.email = ["antoine@bearer.sh"]
9
+ spec.email = ["engineering@bearer.sh"]
10
10
11
11
spec.summary = %q{Bearer Ruby}
12
- spec.description = %q{Bearer Ruby description}
12
+ spec.description = %q{Ruby client to universally call any API using Bearer.sh}
13
13
spec.homepage = "https://www.bearer.sh"
14
14
spec.licenses = ['MIT']
15
15
if spec.respond_to?(:metadata)
@@ -25,11 +25,4 @@ Gem::Specification.new do |spec|
25
25
spec.bindir = "exe"
26
26
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
spec.require_paths = ["lib"]
28
-
29
- spec.add_development_dependency "bundler", "~> 1.16"
30
- spec.add_development_dependency "rake", "~> 10.0"
31
- spec.add_development_dependency "rspec", "~> 3.0"
32
- spec.add_development_dependency "rubocop", "~> 0.65.0"
33
- spec.add_development_dependency "pry"
34
- spec.add_development_dependency "overcommit"
35
28
end
data/bin/setup-credentials ADDED
@@ -0,0 +1,8 @@
1
+ set -e
2
+
3
+ mkdir -p ~/.gem
4
+ echo "---
5
+ https://rubygems.org/: $RUBYGEMS_AUTH_TOKEN
6
+ :rubygems_api_key: $RUBYGEMS_AUTH_TOKEN" > ~/.gem/credentials
7
+
8
+ chmod 0600 ~/.gem/credentials
data/lib/bearer.rb CHANGED
@@ -1,31 +1,44 @@
1
1
# frozen_string_literal: true
2
2
3
- require_relative "./bearer/version"
4
3
require_relative "./bearer/configuration"
5
- require "net/http"
6
- require "json"
4
+ require_relative "./bearer/integration"
5
+ require_relative "./bearer/response"
6
+ require "logger"
7
7
8
- module Bearer
9
- INT_URL = "https://int.bearer.sh/api/v4/functions/backend/"
8
+ # Ruby bindings for Bearer
9
+ class Bearer
10
+ # Create an instance of the Bearer client
11
+ # @param secret_key [String] developer secret Key from https://app.bearer.sh/settings.
12
+ # @param host [String] used internally
13
+ def initialize(secret_key = Bearer::Configuration.secret_key, host: Bearer::Configuration.host)
14
+ @secret_key = secret_key
15
+ @host = host
16
+ end
10
17
11
- class << self
12
- def call(integration_buid, integration_name, params: {}, body: {})
13
- uri = URI("#{INT_URL}#{integration_buid}/#{integration_name}")
14
- uri.query = params.map { |k, v| "#{k}=#{v}" }.join("&")
18
+ # Return an integration client
19
+ #
20
+ # @param http_client_settings [Hash<String,String>] sent as keyword arguments to Net::HTTP.start method
21
+ # @param integration_id [String] bearer api id
22
+ # @return [Bearer::Integration]
23
+ def integration(integration_id, http_client_settings: {})
24
+ Integration.new(
25
+ integration_id: integration_id,
26
+ host: @host,
27
+ secret_key: @secret_key,
28
+ http_client_settings: http_client_settings
29
+ )
30
+ end
15
31
16
- req = Net::HTTP::Post.new(
17
- uri,
18
- "Content-Type" => "application/json",
19
- "Authorization" => Bearer::Configuration.api_key,
20
- "User-Agent" => "Bearer (#{Bearer::VERSION})"
21
- )
32
+ # @see {Bearer#integration}
33
+ # @param (see #integration)
34
+ # @return [Bearer::Integration]
35
+ def self.integration(integration_id, http_client_settings: {})
36
+ new.integration(integration_id, http_client_settings: http_client_settings)
37
+ end
22
- req.body = body.to_json
23
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
24
- http.request(req)
25
- end
26
- JSON.parse(res.body)
27
- end
28
38
29
- alias invoke call
39
+ # @see {Logger}
40
+ # @return [Logger]
41
+ def self.logger
42
+ @logger ||= Logger.new(STDOUT, level: Bearer::Configuration.log_level)
30
43
end
31
44
end
data/lib/bearer/configuration.rb CHANGED
@@ -4,26 +4,170 @@ require "singleton"
4
4
5
5
require_relative "./errors"
6
6
7
- module Bearer
7
+ class Bearer
8
+ # stores global Bearer configuration options
9
+ # @see https://app.bearer.sh/settings
10
+ # @attr_writer [String] secret_key secret key from https://app.bearer.sh/settings
11
+ # @attr_writer [String] publishable_key publishable key from https://app.bearer.sh/settings
12
+ # @attr_writer [String] encryption_key encryption key from https://app.bearer.sh/settings
13
+ # @attr_writer [Hash] http_client_settings options passed as a parameters to Net::HTTP#start
14
+ # @attr_writer [String] host mainly used internally
8
15
class Configuration
9
16
include Singleton
10
17
11
- FIELDS = %i[api_key client_id secret].freeze
18
+ PRODUCTION_INTEGRATION_HOST = "https://proxy.bearer.sh"
19
+
20
+ FIELDS = %i[
21
+ secret_key
22
+ publishable_key
23
+ encryption_key
24
+ host
25
+ http_client_settings
26
+ log_level
27
+ max_network_retries
28
+ max_network_retry_delay
29
+ initial_network_retry_delay
30
+ ].freeze
31
+
32
+ DEPRECATED_FIELDS = %i[
33
+ api_key
34
+ client_id
35
+ secret
36
+ integration_host
37
+ http_client_params
38
+ ].freeze
39
+
40
+ DEFAULT_READ_TIMEOUT = 5
41
+ DEFAULT_OPEN_TIMEOUT = 5
42
+
43
+ DEFAULT_MAX_NETWORK_RETRIES = 0
44
+ DEFAULT_MAX_NETWORK_RETRY_DELAY = 2
45
+ DEFAULT_INITIAL_NETWORK_RETRY_DELAY = 0.5
46
+
47
+ # @return [String]
48
+ def integration_host
49
+ deprecate("integration_host", "host")
50
+ host
51
+ end
52
+
53
+ # @return [Integer]
54
+ def log_level
55
+ @log_level ||= :info
56
+ end
57
+
58
+ # @return [String]
59
+ def host
60
+ @host ||= PRODUCTION_INTEGRATION_HOST
61
+ end
62
+
63
+ # @return [Integer]
64
+ def max_network_retries
65
+ @max_network_retries ||= DEFAULT_MAX_NETWORK_RETRIES
66
+ end
67
+
68
+ # @return [Float]
69
+ def max_network_retry_delay
70
+ @max_network_retry_delay ||= DEFAULT_MAX_NETWORK_RETRY_DELAY
71
+ end
72
+
73
+ # @return [Float]
74
+ def initial_network_retry_delay
75
+ @initial_network_retry_delay ||= DEFAULT_INITIAL_NETWORK_RETRY_DELAY
76
+ end
77
+
78
+ # @return [Hash]
79
+ def http_client_settings
80
+ default_http_client_settings.merge(@http_client_settings || {})
81
+ end
82
+
83
+ # @deprecated use {#http_client_settings} instead
84
+ # @return [Hash<String,String>]
85
+ def http_client_params
86
+ deprecate("http_client_params", "http_client_settings")
87
+ http_client_settings
88
+ end
89
+
90
+ # @return [String]
91
+ def secret_key
92
+ raise_if_missing(:secret_key)
93
+ end
94
+
95
+ # @return [String]
96
+ def publishable_key
97
+ raise_if_missing(:publishable_key)
98
+ end
99
+
100
+ # @return [String]
101
+ def encryption_key
102
+ raise_if_missing(:encryption_key)
103
+ end
104
+
105
+ # @deprecated Use {#secret_key} instead.
106
+ # @return [String]
107
+ def api_key
108
+ deprecate("api_key", "secret_key")
109
+ secret_key
110
+ end
111
+
112
+ # @deprecated Use {#publishable_key} instead.
113
+ # @return [String]
114
+ def client_id
115
+ deprecate("client_id", "publishable_key")
116
+ publishable_key
117
+ end
118
+
119
+ # @deprecated Use {#encryption_key} instead.
120
+ # @return [String]
121
+ def secret
122
+ deprecate("secret", "encryption_key")
123
+ encryption_key
124
+ end
12
125
13
126
attr_writer(*FIELDS)
14
127
15
- FIELDS.each do |field|
16
- define_method field do
17
- value = instance_variable_get(:"@#{field}")
128
+ # @deprecated Use {#secret_key=} instead.
129
+ # @return [void]
130
+ def api_key=(value)
131
+ deprecate("api_key=", "secret_key=")
132
+ @secret_key = value
133
+ end
18
134
19
- raise ::Bearer::Errors::Configuration, "Bearer #{field} is missing!" unless value
135
+ # @deprecated Use {#publishable_key=} instead.
136
+ # @return [void]
137
+ def client_id=(value)
138
+ deprecate("client_id=", "publishable_key=")
139
+ @publishable_key = value
140
+ end
20
141
21
- value
22
- end
142
+ # @deprecated Use {#encryption_key=} instead.
143
+ # @return [void]
144
+ def secret=(value)
145
+ deprecate("secret=", "encryption_key=")
146
+ @encryption_key = value
147
+ end
148
+
149
+ # @deprecated Use {#host=} instead.
150
+ # @return [void]
151
+ def integration_host=(value)
152
+ deprecate("integration_host=", "host=")
153
+ @host = value
154
+ end
155
+
156
+ # @deprecated Use {#http_client_settings=} instead.
157
+ # @return [void]
158
+ def http_client_params=(value)
159
+ deprecate("http_client_params=", "http_client_settings=")
160
+ @http_client_settings = value
161
+ end
162
+
163
+ def log_level=(severity)
164
+ Bearer.logger.level = severity
165
+ @log_level = severity
23
166
end
24
167
25
168
class << self
26
- EXISTING_METHODS = FIELDS.flat_map { |field| [field, "#{field}=".to_sym] }
169
+ ALL_METHODS = [*FIELDS, *DEPRECATED_FIELDS].freeze
170
+ EXISTING_METHODS = ALL_METHODS.flat_map { |field| [field, "#{field}=".to_sym] }
27
171
28
172
def method_missing(name, *args, &block)
29
173
super unless EXISTING_METHODS.include? name
@@ -33,16 +177,51 @@ module Bearer
33
177
def respond_to_missing?(name, include_private = false)
34
178
EXISTING_METHODS.include?(name) || super
35
179
end
36
- end
37
180
38
- def self.reset
39
- FIELDS.each do |field|
40
- instance.public_send("#{field}=", nil)
181
+ def reset
182
+ FIELDS.each do |field|
183
+ value = field == :log_level ? :info : nil
184
+ instance.public_send("#{field}=", value)
185
+ end
41
186
end
187
+
188
+ def setup
189
+ yield(instance)
190
+ end
191
+ end
192
+
193
+ private
194
+
195
+ def raise_if_missing(field)
196
+ value = instance_variable_get(:"@#{field}")
197
+
198
+ raise ::Bearer::Errors::Configuration, "Bearer #{field} is missing!" unless value
199
+
200
+ value
201
+ end
202
+
203
+ def deprecate(old_field, new_field)
204
+ puts "Bearer Deprecation Warning: #{old_field} is deprecated, use #{new_field} instead"
205
+ end
206
+
207
+ # defaults to 5 seconds
208
+ # @return [Integer]
209
+ def open_timeout
210
+ @open_timeout || DEFAULT_READ_TIMEOUT
211
+ end
212
+
213
+ # defaults to 5 seconds
214
+ # @return [Integer]
215
+ def read_timeout
216
+ @read_timeout || DEFAULT_READ_TIMEOUT
42
217
end
43
218
44
- def self.setup
45
- yield(instance)
219
+ # @return [Hash]
220
+ def default_http_client_settings
221
+ {
222
+ read_timeout: read_timeout,
223
+ open_timeout: open_timeout
224
+ }
46
225
end
47
226
end
48
227
end
data/lib/bearer/errors.rb CHANGED
@@ -1,6 +1,6 @@
1
1
# frozen_string_literal: true
2
2
3
- module Bearer
3
+ class Bearer
4
4
module Errors
5
5
class Configuration < StandardError; end
6
6
end
data/lib/bearer/integration.rb ADDED
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ require_relative "./errors"
7
+ require_relative "./version"
8
+
9
+ class Bearer
10
+ class Integration
11
+ # @param integration_id [String] integration id
12
+ # @param host [String] "https://proxy.bearer.sh" | "https://proxy.staging.bearer.sh"
13
+ # @param http_client_settings [Hash<String,String>] http client settings see Net::HTTP#start
14
+ # @param auth_id [String] the auth id used to connect
15
+ # @param setup_id [String] the setup id used to store the credentials
16
+ # @param @deprecated read_timeout [String] use http_client_settings instead
17
+ def initialize(
18
+ integration_id:,
19
+ host:,
20
+ secret_key:,
21
+ http_client_settings: {},
22
+ read_timeout: nil,
23
+ auth_id: nil,
24
+ setup_id: nil
25
+ )
26
+ @integration_id = integration_id
27
+ @host = host
28
+ @secret_key = secret_key
29
+ @auth_id = auth_id
30
+ @read_timeout = read_timeout
31
+ @http_client_settings = http_client_settings
32
+ @setup_id = setup_id
33
+ end
34
+
35
+ # Returns a new integration client instance that will use the given auth id for requests
36
+ # @param auth_id [String] the auth id used to connect
37
+ # @return [Bearer::Integration]
38
+ def auth(auth_id)
39
+ self.class.new(
40
+ integration_id: @integration_id,
41
+ host: @host,
42
+ secret_key: @secret_key,
43
+ auth_id: auth_id
44
+ )
45
+ end
46
+
47
+ # Returns a new integration client instance that will use the given setup id for requests
48
+ # @param setup_id [String] uuid setup id used to store credentials
49
+ # @return [Bearer::Integration]
50
+ def setup(setup_id)
51
+ self.class.new(
52
+ integration_id: @integration_id,
53
+ host: @host,
54
+ secret_key: @secret_key,
55
+ setup_id: setup_id,
56
+ auth_id: @auth_id
57
+ )
58
+ end
59
+
60
+ # An alias for `#auth`
61
+ # @see {#auth}
62
+ def authenticate(auth_id)
63
+ auth(auth_id)
64
+ end
65
+
66
+ # Makes a HEAD request to the API configured for this integration and returns the response
67
+ # @param (see #request)
68
+ def get(endpoint, headers: nil, body: nil, query: nil)
69
+ request("GET", endpoint, headers: headers, body: body, query: query)
70
+ end
71
+
72
+ # Makes a HEAD request to the API configured for this integration and returns the response
73
+ # @param (see #request)
74
+ def head(endpoint, headers: nil, body: nil, query: nil)
75
+ request("HEAD", endpoint, headers: headers, body: body, query: query)
76
+ end
77
+
78
+ # Makes a POST request to the API configured for this integration and returns the response
79
+ # @param (see #request)
80
+ def post(endpoint, headers: nil, body: nil, query: nil)
81
+ request("POST", endpoint, headers: headers, body: body, query: query)
82
+ end
83
+
84
+ # Makes a PUT request to the API configured for this integration and returns the response
85
+ # @param (see #request)
86
+ def put(endpoint, headers: nil, body: nil, query: nil)
87
+ request("PUT", endpoint, headers: headers, body: body, query: query)
88
+ end
89
+
90
+ # Makes a GET request to the API configured for this integration and returns the response
91
+ # @param (see #request)
92
+ def patch(endpoint, headers: nil, body: nil, query: nil)
93
+ request("PATCH", endpoint, headers: headers, body: body, query: query)
94
+ end
95
+
96
+ # Makes a DELETE request to the API configured for this integration and returns the response
97
+ # @param (see #request)
98
+ def delete(endpoint, headers: nil, body: nil, query: nil)
99
+ request("DELETE", endpoint, headers: headers, body: body, query: query)
100
+ end
101
+
102
+ # Makes a request to the API configured for this integration and returns the response
103
+ # @param method [String] GET/HEAD/POST/PUT/PATCH/DELETE
104
+ # @param endpoint [String] the URL relative to the configured API's base URL
105
+ # @param headers [Hash<String, String>] any headers to send to the API
106
+ # @param body [Hash] any request body data to send
107
+ # @param query [Hash<String, String>] parameters to add to the URL's query string
108
+ def request(method, endpoint, headers: nil, body: nil, query: nil)
109
+ pre_headers = {
110
+ "Authorization": @secret_key,
111
+ "User-Agent": "Bearer-Ruby (#{Bearer::VERSION})",
112
+ "Bearer-Auth-Id": @auth_id,
113
+ "Bearer-Setup-Id": @setup_id,
114
+ "Content-Type": "application/json"
115
+ }
116
+
117
+ request_headers = pre_headers.merge(headers || {}).reject { |_k, v| v.nil? }
118
+
119
+ endpoint = endpoint.sub(%r{\A/}, "")
120
+ url = "#{@host}/#{@integration_id}/#{endpoint}"
121
+
122
+ make_request(method: method, url: url, query: query, body: body, headers: request_headers)
123
+ end
124
+
125
+ # Checks if an error is a problem that we should retry on. This includes
126
+ # both socket errors that may represent an intermittent problem and some
127
+ # special HTTP statuses.
128
+ # TODO: handle bearer errors like 500 statuses
129
+ def self.should_retry?(error, num_retries:)
130
+ return false if num_retries >= Bearer::Configuration.max_network_retries
131
+
132
+ case error
133
+ when Net::OpenTimeout, Net::ReadTimeout
134
+ # Retry on timeout-related problems (either on open or read).
135
+ true
136
+ when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET,
137
+ Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError
138
+ # Destination refused the connection, the connection was reset, or a
139
+ # variety of other connection failures. This could occur from a single
140
+ # saturated server, so retry in case it's intermittent.
141
+ true
142
+ else
143
+ false
144
+ end
145
+ end
146
+
147
+ def self.sleep_time(num_retries)
148
+ # Apply exponential backoff with initial_network_retry_delay on the
149
+ # number of num_retries so far as inputs. Do not allow the number to
150
+ # exceed max_network_retry_delay.
151
+ sleep_seconds = [
152
+ Bearer::Configuration.initial_network_retry_delay * (2**(num_retries - 1)),
153
+ Bearer::Configuration.max_network_retry_delay
154
+ ].min
155
+
156
+ # Apply some jitter by randomizing the value in the range of
157
+ # (sleep_seconds / 2) to (sleep_seconds).
158
+ sleep_seconds *= (0.5 * (1 + rand))
159
+
160
+ # But never sleep less than the base sleep seconds.
161
+ sleep_seconds = [Bearer::Configuration.initial_network_retry_delay, sleep_seconds].max
162
+
163
+ sleep_seconds
164
+ end
165
+
166
+ private
167
+
168
+ # @return [Hash]
169
+ def http_client_settings
170
+ Bearer::Configuration.http_client_settings.merge(@http_client_settings)
171
+ end
172
+
173
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
174
+ def make_request(method:, url:, query:, body:, headers:)
175
+ num_retries = 0
176
+ parsed_url = URI(url)
177
+ parsed_url.query = URI.encode_www_form(query) if query
178
+
179
+ debug_request(parsed_url: parsed_url,
180
+ http_client_settings: http_client_settings,
181
+ method: method,
182
+ body: body,
183
+ headers: headers)
184
+ begin
185
+ response = Net::HTTP.start(
186
+ parsed_url.hostname,
187
+ parsed_url.port,
188
+ use_ssl: parsed_url.scheme == "https",
189
+ **http_client_settings
190
+ ) do |http|
191
+ http.send_request(method, parsed_url, body ? body.to_json : nil, headers)
192
+ end.tap(&info_response)
193
+
194
+ Bearer::Response.from_net_http(response)
195
+ rescue StandardError => e
196
+ Bearer.logger.warn("Bearer") do
197
+ <<-WARN.gsub(/^\s+/, "")
198
+ There was an error while trying to make a request to bearer.
199
+ #{e.class.name}: #{e.message}
200
+ WARN
201
+ end
202
+ if self.class.should_retry?(e, num_retries: num_retries)
203
+ num_retries += 1
204
+ Bearer.logger.info("Bearer") { "retrying request" }
205
+ retry
206
+ else
207
+ Bearer.logger.warn("Bearer") do
208
+ "Failed to perform to bearer request number of retries exceeded"
209
+ end
210
+ raise e
211
+ end
212
+ end
213
+ end
214
+ # rubocop:enable Metrics/MethodLength,Metrics/AbcSize
215
+
216
+ # @return [void]
217
+ def debug_request(parsed_url:, http_client_settings:, method:, body:, headers:)
218
+ Bearer.logger.debug("Bearer") do
219
+ <<-DEBUG.gsub(/^\s+/, "")
220
+ sending request
221
+ hostname: #{parsed_url.hostname}
222
+ port: #{parsed_url.port}
223
+ scheme: #{parsed_url.scheme}
224
+ method: #{method}
225
+ http_client_settings: #{http_client_settings.to_json}
226
+ body: #{body ? body.to_json : ''}
227
+ headers: #{headers ? headers.to_json : ''}
228
+ DEBUG
229
+ end
230
+ end
231
+
232
+ # @return [void]
233
+ def info_response
234
+ lambda do |response|
235
+ return unless response
236
+
237
+ Bearer.logger.info("Bearer") do
238
+ <<-INFO.gsub(/^\s+/, "")
239
+ response requestId: #{response.header['bearer-request-id']}
240
+ INFO
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
data/lib/bearer/response.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Bearer
4
+ # Bearer::Response encapsulates some vitals of a response that came back from
5
+ # the bearer proxy.
6
+ class Response
7
+ # Headers provides an access wrapper to an API response's header data. It
8
+ # mainly exists so that we don't need to expose the entire
9
+ # `Net::HTTPResponse` object while still getting some of its benefits like
10
+ # case-insensitive access to header names and flattening of header values.
11
+ class Headers
12
+ # Initializes a Headers object from a Net::HTTP::HTTPResponse object.
13
+ def self.from_net_http(resp)
14
+ new(resp.to_hash)
15
+ end
16
+
17
+ # `hash` is expected to be a hash mapping header names to arrays of
18
+ # header values. This is the default format generated by calling
19
+ # `#to_hash` on a `Net::HTTPResponse` object because headers can be
20
+ # repeated multiple times. Using `#[]` will collapse values down to just
21
+ # the first.
22
+ def initialize(hash)
23
+ if !hash.is_a?(Hash) ||
24
+ !hash.keys.all? { |n| n.is_a?(String) } ||
25
+ !hash.values.all? { |a| a.is_a?(Array) } ||
26
+ !hash.values.all? { |a| a.all? { |v| v.is_a?(String) } }
27
+ raise ArgumentError,
28
+ "expect hash to be a map of string header names to arrays of " \
29
+ "header values"
30
+ end
31
+
32
+ @hash = {}
33
+
34
+ # This shouldn't be strictly necessary because `Net::HTTPResponse` will
35
+ # produce a hash with all headers downcased, but do it anyway just in
36
+ # case an object of this class was constructed manually.
37
+ #
38
+ # Also has the effect of duplicating the hash, which is desirable for a
39
+ # little extra object safety.
40
+ hash.each do |k, v|
41
+ @hash[k.downcase] = v
42
+ end
43
+ end
44
+
45
+ def [](name)
46
+ values = @hash[name.downcase]
47
+ warn("Duplicate header values for `#{name}`; returning only first") if values && values.count > 1
48
+ values ? values.first : nil
49
+ end
50
+ end
51
+
52
+ # The data contained by the HTTP body of the response deserialized from
53
+ # JSON.
54
+ attr_accessor :data
55
+
56
+ # The raw HTTP body of the response.
57
+ attr_accessor :http_body
58
+
59
+ # A Hash of the HTTP headers of the response.
60
+ attr_accessor :http_headers
61
+
62
+ # The integer HTTP status code of the response.
63
+ attr_accessor :http_status
64
+
65
+ # The Bearer request ID of the response.
66
+ attr_accessor :request_id
67
+
68
+ # Initializes a Bearer::Response object from a Net::HTTP::HTTPResponse
69
+ # object.
70
+ def self.from_net_http(http_resp)
71
+ resp = Bearer::Response.new
72
+ resp.data = JSON.parse(http_resp.body, symbolize_names: true)
73
+ resp.http_body = http_resp.body
74
+ resp.http_headers = Headers.from_net_http(http_resp)
75
+ resp.http_status = http_resp.code.to_i
76
+ resp.request_id = http_resp["bearer-request-id"]
77
+ resp
78
+ end
79
+ end
80
+ end
data/lib/bearer/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
# frozen_string_literal: true
2
2
3
- module Bearer
4
- VERSION = "0.2.0"
3
+ class Bearer
4
+ VERSION = "3.0.0"
5
5
end
metadata CHANGED
@@ -1,108 +1,25 @@
1
1
--- !ruby/object:Gem::Specification
2
2
name: bearer
3
3
version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 3.0.0
5
5
platform: ruby
6
6
authors:
7
7
- Bearer Team<engineering@bearer.sh>
8
8
autorequire:
9
9
bindir: exe
10
10
cert_chain: []
11
- date: 2019-04-12 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
11
+ date: 2019-11-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby client to universally call any API using Bearer.sh
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.16'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.16'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '10.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '10.0'
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.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.0'
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.65.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.65.0
69
- - !ruby/object:Gem::Dependency
70
- name: pry
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: overcommit
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
- description: Bearer Ruby description
98
14
email:
99
- - antoine@bearer.sh
15
+ - engineering@bearer.sh
100
16
executables: []
101
17
extensions: []
102
18
extra_rdoc_files: []
103
19
files:
20
+ - ".drone.yml"
21
+ - ".github/CODEOWNERS"
104
22
- ".github/PULL_REQUEST_TEMPLATE.md"
105
- - ".github/main.workflow"
106
23
- ".gitignore"
107
24
- ".jenkins/stack.yml"
108
25
- ".overcommit.yml"
@@ -114,15 +31,19 @@ files:
114
31
- Gemfile
115
32
- Gemfile.lock
116
33
- Jenkinsfile
34
+ - LICENSE
117
35
- README.md
118
36
- Rakefile
119
37
- bearer.gemspec
120
38
- bin/console
121
39
- bin/setup
40
+ - bin/setup-credentials
122
41
- bin/test
123
42
- lib/bearer.rb
124
43
- lib/bearer/configuration.rb
125
44
- lib/bearer/errors.rb
45
+ - lib/bearer/integration.rb
46
+ - lib/bearer/response.rb
126
47
- lib/bearer/version.rb
127
48
homepage: https://www.bearer.sh
128
49
licenses:
@@ -144,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
65
- !ruby/object:Gem::Version
145
66
version: '0'
146
67
requirements: []
147
- rubygems_version: 3.0.1
68
+ rubygems_version: 3.0.3
148
69
signing_key:
149
70
specification_version: 4
150
71
summary: Bearer Ruby