hybrid_platforms_conductor 33.3.0 → 33.4.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +31 -2
  4. data/docs/config_dsl.md +43 -0
  5. data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
  6. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
  7. data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
  8. data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
  9. data/lib/hybrid_platforms_conductor/credentials.rb +112 -95
  10. data/lib/hybrid_platforms_conductor/deployer.rb +2 -2
  11. data/lib/hybrid_platforms_conductor/github.rb +39 -0
  12. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +4 -2
  13. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
  14. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +2 -1
  15. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
  16. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
  17. data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
  18. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +6 -2
  19. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +6 -2
  20. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
  21. data/lib/hybrid_platforms_conductor/logger_helpers.rb +7 -1
  22. data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
  23. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  24. data/spec/hybrid_platforms_conductor_test.rb +6 -0
  25. data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +247 -0
  26. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +280 -319
  27. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
  28. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
  29. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
  30. metadata +18 -2
@@ -1,6 +1,3 @@
1
- require 'octokit'
2
- require 'hybrid_platforms_conductor/credentials'
3
-
4
1
  module HybridPlatformsConductor
5
2
 
6
3
  module CommonConfigDsl
@@ -8,12 +5,19 @@ module HybridPlatformsConductor
8
5
  # Add common Github config DSL to declare known Github repositories
9
6
  module Github
10
7
 
8
+ # List of Github repositories
9
+ # Array< Hash<Symbol, Object> >
10
+ # * *user* (String): User or organization name, storing repositories
11
+ # * *url* (String): URL to the Github API
12
+ # * *repos* (Array<String> or Symbol): List of repository names from this project, or :all for all
13
+ attr_reader :known_github_repos
14
+
11
15
  # Initialize the DSL
12
16
  def init_github
13
17
  # List of Github repositories definitions
14
18
  # Array< Hash<Symbol, Object> >
15
19
  # Each definition is just mapping the signature of #github_repos
16
- @github_repos = []
20
+ @known_github_repos = []
17
21
  end
18
22
 
19
23
  # Register new Github repositories
@@ -23,39 +27,13 @@ module HybridPlatformsConductor
23
27
  # * *url* (String): URL to the Github API [default: 'https://api.github.com']
24
28
  # * *repos* (Array<String> or Symbol): List of repository names from this project, or :all for all [default: :all]
25
29
  def github_repos(user:, url: 'https://api.github.com', repos: :all)
26
- @github_repos << {
30
+ @known_github_repos << {
27
31
  url: url,
28
32
  user: user,
29
33
  repos: repos
30
34
  }
31
35
  end
32
36
 
33
- # Iterate over each Github repository
34
- #
35
- # Parameters::
36
- # * Proc: Code called for each Github repository:
37
- # * Parameters::
38
- # * *github* (Octokit::Client): The client instance accessing the Github API
39
- # * *repo_info* (Hash<Symbol, Object>): The repository info:
40
- # * *name* (String): Repository name.
41
- # * *slug* (String): Repository slug.
42
- def for_each_github_repo
43
- @github_repos.each do |repo_info|
44
- Octokit.configure do |c|
45
- c.api_endpoint = repo_info[:url]
46
- end
47
- Credentials.with_credentials_for(:github, @logger, @logger_stderr, url: repo_info[:url]) do |_github_user, github_token|
48
- client = Octokit::Client.new(access_token: github_token)
49
- (repo_info[:repos] == :all ? client.repositories(repo_info[:user]).map { |repo| repo[:name] } : repo_info[:repos]).each do |name|
50
- yield client, {
51
- name: name,
52
- slug: "#{repo_info[:user]}/#{name}"
53
- }
54
- end
55
- end
56
- end
57
- end
58
-
59
37
  end
60
38
 
61
39
  end
@@ -7,111 +7,116 @@ require 'hybrid_platforms_conductor/credentials'
7
7
 
8
8
  module HybridPlatformsConductor
9
9
 
10
- # Object used to access Confluence API
11
- class Confluence
10
+ # Mixin used to access Confluence API
11
+ module Confluence
12
12
 
13
- include LoggerHelpers
13
+ include Credentials
14
14
 
15
15
  # Provide a Confluence connector, and make sure the password is being cleaned when exiting.
16
16
  #
17
17
  # Parameters::
18
18
  # * *confluence_url* (String): The Confluence URL
19
- # * *logger* (Logger): Logger to be used
20
- # * *logger_stderr* (Logger): Logger to be used for stderr
21
19
  # * Proc: Code called with the Confluence instance.
22
- # * *confluence* (Confluence): The Confluence instance to use.
23
- def self.with_confluence(confluence_url, logger, logger_stderr)
24
- Credentials.with_credentials_for(:confluence, logger, logger_stderr, url: confluence_url) do |confluence_user, confluence_password|
25
- yield Confluence.new(confluence_url, confluence_user, confluence_password, logger: logger, logger_stderr: logger_stderr)
20
+ # * *confluence* (ConfluenceApi): The Confluence instance to use.
21
+ def with_confluence(confluence_url)
22
+ with_credentials_for(:confluence, resource: confluence_url) do |confluence_user, confluence_password|
23
+ yield ConfluenceApi.new(confluence_url, confluence_user, confluence_password, logger: @logger, logger_stderr: @logger_stderr)
26
24
  end
27
25
  end
28
26
 
29
- # Constructor
30
- #
31
- # Parameters::
32
- # * *confluence_url* (String): The Confluence URL
33
- # * *confluence_user_name* (String): Confluence user name to be used when querying the API
34
- # * *confluence_password* (String): Confluence password to be used when querying the API
35
- # * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
36
- # * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
37
- def initialize(confluence_url, confluence_user_name, confluence_password, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
38
- init_loggers(logger, logger_stderr)
39
- @confluence_url = confluence_url
40
- @confluence_user_name = confluence_user_name
41
- @confluence_password = confluence_password
42
- end
27
+ # Provide an API access on Confluence
28
+ class ConfluenceApi
43
29
 
44
- # Return a Confluence storage format content from a page ID
45
- #
46
- # Parameters::
47
- # * *page_id* (String): Confluence page ID
48
- # Result::
49
- # * Nokogiri::HTML: Storage format content, as a Nokogiri object
50
- def page_storage_format(page_id)
51
- Nokogiri::HTML(call_api("plugins/viewstorage/viewpagestorage.action?pageId=#{page_id}").body)
52
- end
30
+ include LoggerHelpers
53
31
 
54
- # Return some info of a given page ID
55
- #
56
- # Parameters::
57
- # * *page_id* (String): Confluence page ID
58
- # Result::
59
- # * Hash: Page information, as returned by the Confluence API
60
- def page_info(page_id)
61
- JSON.parse(call_api("rest/api/content/#{page_id}").body)
62
- end
32
+ # Constructor
33
+ #
34
+ # Parameters::
35
+ # * *confluence_url* (String): The Confluence URL
36
+ # * *confluence_user_name* (String): Confluence user name to be used when querying the API
37
+ # * *confluence_password* (String): Confluence password to be used when querying the API
38
+ # * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
39
+ # * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
40
+ def initialize(confluence_url, confluence_user_name, confluence_password, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
41
+ init_loggers(logger, logger_stderr)
42
+ @confluence_url = confluence_url
43
+ @confluence_user_name = confluence_user_name
44
+ @confluence_password = confluence_password
45
+ end
63
46
 
64
- # Update a Confluence page to a new content.
65
- #
66
- # Parameters::
67
- # * *page_id* (String): Confluence page ID
68
- # * *content* (String): New content
69
- # * *version* (String or nil): New version, or nil to automatically increase last existing version [default: nil]
70
- def update_page(page_id, content, version: nil)
71
- info = page_info(page_id)
72
- version = info['version']['number'] + 1 if version.nil?
73
- log_debug "Update Confluence page #{page_id}..."
74
- call_api("rest/api/content/#{page_id}", :put) do |request|
75
- request['Content-Type'] = 'application/json'
76
- request.body = {
77
- type: 'page',
78
- title: info['title'],
79
- body: {
80
- storage: {
81
- value: content,
82
- representation: 'storage'
83
- }
84
- },
85
- version: { number: version }
86
- }.to_json
47
+ # Return a Confluence storage format content from a page ID
48
+ #
49
+ # Parameters::
50
+ # * *page_id* (String): Confluence page ID
51
+ # Result::
52
+ # * Nokogiri::HTML: Storage format content, as a Nokogiri object
53
+ def page_storage_format(page_id)
54
+ Nokogiri::HTML(call_api("plugins/viewstorage/viewpagestorage.action?pageId=#{page_id}").body)
87
55
  end
88
- end
89
56
 
90
- private
57
+ # Return some info of a given page ID
58
+ #
59
+ # Parameters::
60
+ # * *page_id* (String): Confluence page ID
61
+ # Result::
62
+ # * Hash: Page information, as returned by the Confluence API
63
+ def page_info(page_id)
64
+ JSON.parse(call_api("rest/api/content/#{page_id}").body)
65
+ end
91
66
 
92
- # Call the Confluence API for a given URL and HTTP verb.
93
- # Provide a simple way to tweak the request with an optional proc.
94
- # Automatically handles authentication, base URL and error handling.
95
- #
96
- # Parameters::
97
- # * *api_path* (String): The API path to query
98
- # * *http_method* (Symbol): HTTP method to be used to create the request [default = :get]
99
- # * Proc: Optional code called to alter the request
100
- # * Parameters::
101
- # * *request* (Net::HTTPRequest): The request
102
- # Result::
103
- # * Net::HTTPResponse: The corresponding response
104
- def call_api(api_path, http_method = :get)
105
- response = nil
106
- page_url = URI.parse("#{@confluence_url}/#{api_path}")
107
- Net::HTTP.start(page_url.host, page_url.port, use_ssl: true) do |http|
108
- request = Net::HTTP.const_get(http_method.to_s.capitalize.to_sym).new(page_url.request_uri)
109
- request.basic_auth @confluence_user_name, @confluence_password
110
- yield request if block_given?
111
- response = http.request(request)
112
- raise "Confluence page API request on #{page_url} returned an error: #{response.code}\n#{response.body}\n===== Request body =====\n#{request.body}" unless response.is_a?(Net::HTTPSuccess)
67
+ # Update a Confluence page to a new content.
68
+ #
69
+ # Parameters::
70
+ # * *page_id* (String): Confluence page ID
71
+ # * *content* (String): New content
72
+ # * *version* (String or nil): New version, or nil to automatically increase last existing version [default: nil]
73
+ def update_page(page_id, content, version: nil)
74
+ info = page_info(page_id)
75
+ version = info['version']['number'] + 1 if version.nil?
76
+ log_debug "Update Confluence page #{page_id}..."
77
+ call_api("rest/api/content/#{page_id}", :put) do |request|
78
+ request['Content-Type'] = 'application/json'
79
+ request.body = {
80
+ type: 'page',
81
+ title: info['title'],
82
+ body: {
83
+ storage: {
84
+ value: content,
85
+ representation: 'storage'
86
+ }
87
+ },
88
+ version: { number: version }
89
+ }.to_json
90
+ end
113
91
  end
114
- response
92
+
93
+ private
94
+
95
+ # Call the Confluence API for a given URL and HTTP verb.
96
+ # Provide a simple way to tweak the request with an optional proc.
97
+ # Automatically handles authentication, base URL and error handling.
98
+ #
99
+ # Parameters::
100
+ # * *api_path* (String): The API path to query
101
+ # * *http_method* (Symbol): HTTP method to be used to create the request [default = :get]
102
+ # * Proc: Optional code called to alter the request
103
+ # * Parameters::
104
+ # * *request* (Net::HTTPRequest): The request
105
+ # Result::
106
+ # * Net::HTTPResponse: The corresponding response
107
+ def call_api(api_path, http_method = :get)
108
+ response = nil
109
+ page_url = URI.parse("#{@confluence_url}/#{api_path}")
110
+ Net::HTTP.start(page_url.host, page_url.port, use_ssl: true) do |http|
111
+ request = Net::HTTP.const_get(http_method.to_s.capitalize.to_sym).new(page_url.request_uri)
112
+ request.basic_auth @confluence_user_name, @confluence_password
113
+ yield request if block_given?
114
+ response = http.request(request)
115
+ raise "Confluence page API request on #{page_url} returned an error: #{response.code}\n#{response.body}\n===== Request body =====\n#{request.body}" unless response.is_a?(Net::HTTPSuccess)
116
+ end
117
+ response
118
+ end
119
+
115
120
  end
116
121
 
117
122
  end
@@ -7,122 +7,139 @@ module HybridPlatformsConductor
7
7
  # Give a secured and harmonized way to access credentials for a given service.
8
8
  # It makes sure to remove passwords from memory for hardened security (this way if a vulnerability allows an attacker to dump the memory it won't get passwords).
9
9
  # It gets credentials from the following sources:
10
+ # * Configuration
10
11
  # * Environment variables
11
12
  # * Netrc file
12
- class Credentials
13
+ module Credentials
13
14
 
14
- include LoggerHelpers
15
+ # Extend the Config DSL
16
+ module ConfigDSLExtension
17
+
18
+ # List of credentials. Each info has the following properties:
19
+ # * *credential_id* (Symbol): Credential ID this rule applies to
20
+ # * *resource* (Regexp): Resource filtering for this rule
21
+ # * *provider* (Proc): The code providing the credentials:
22
+ # * Parameters::
23
+ # * *resource* (String or nil): The resource for which we want credentials, or nil if none
24
+ # * *requester* (Proc): Code to be called to give credentials to:
25
+ # * Parameters::
26
+ # * *user* (String or nil): The user name, or nil if none
27
+ # * *password* (String or nil): The password, or nil if none
28
+ attr_reader :credentials
29
+
30
+ # Mixin initializer
31
+ def init_credentials_config
32
+ @credentials = []
33
+ end
34
+
35
+ # Define a credentials provider
36
+ #
37
+ # Parameters::
38
+ # * *credential_id* (Symbol): Credential ID this rule applies to
39
+ # * *resource* (String or Regexp): Resource filtering for this rule [default: /.*/]
40
+ # * *provider* (Proc): The code providing the credentials:
41
+ # * Parameters::
42
+ # * *resource* (String or nil): The resource for which we want credentials, or nil if none
43
+ # * *requester* (Proc): Code to be called to give credentials to:
44
+ # * Parameters::
45
+ # * *user* (String or nil): The user name, or nil if none
46
+ # * *password* (String or nil): The password, or nil if none
47
+ def credentials_for(credential_id, resource: /.*/, &provider)
48
+ @credentials << {
49
+ credential_id: credential_id,
50
+ resource: resource.is_a?(String) ? /^#{Regexp.escape(resource)}$/ : resource,
51
+ provider: provider
52
+ }
53
+ end
54
+
55
+ end
56
+
57
+ Config.extend_config_dsl_with ConfigDSLExtension, :init_credentials_config
15
58
 
16
59
  # Get access to credentials and make sure they are wiped out from memory when client code ends.
17
60
  # To ensure password safety, never store the password in a scope beyond the client code's Proc.
18
61
  #
19
62
  # Parameters::
20
63
  # * *id* (Symbol): Credential ID
21
- # * *logger* (Logger): Logger to be used
22
- # * *logger_stderr* (Logger): Logger to be used for stderr
23
- # * *url* (String or nil): The URL for which we want the credentials, or nil if not associated to a URL [default: nil]
64
+ # * *resource* (String or nil): The resource for which we want the credentials, or nil if not associated to a resource [default: nil]
24
65
  # * Proc: Client code called with credentials provided
25
66
  # * Parameters::
26
67
  # * *user* (String or nil): User name, or nil if none
27
68
  # * *password* (String or nil): Password, or nil if none.
28
69
  # !!! Never store this password in a scope broader than the client code itself !!!
29
- def self.with_credentials_for(id, logger, logger_stderr, url: nil)
30
- credentials = Credentials.new(id, url: url, logger: logger, logger_stderr: logger_stderr)
31
- begin
32
- yield credentials.user, credentials.password
33
- ensure
34
- credentials.clear_password
35
- end
36
- end
37
-
38
- # Constructor
39
- #
40
- # Parameters::
41
- # * *id* (Symbol): Credential ID
42
- # * *url* (String or nil): The URL for which we want the credentials, or nil if not associated to a URL [default: nil]
43
- # * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
44
- # * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
45
- def initialize(id, url: nil, logger: Logger.new($stdout), logger_stderr: Logger.new($stderr))
46
- init_loggers(logger, logger_stderr)
47
- @id = id
48
- @url = url
49
- @user = nil
50
- @password = nil
51
- @retrieved = false
52
- end
53
-
54
- # Provide a helper to clear password from memory for security.
55
- # To be used when the client knows it won't use the password anymore.
56
- def clear_password
57
- @password&.replace('gotyou!' * 100)
58
- GC.start
59
- end
60
-
61
- # Get the associated user
62
- #
63
- # Result::
64
- # * String or nil: The user name, or nil if none
65
- def user
66
- retrieve_credentials
67
- @user
68
- end
69
-
70
- # Get the associated password
71
- #
72
- # Result::
73
- # * String or nil: The password, or nil if none
74
- def password
75
- retrieve_credentials
76
- @password
77
- end
78
-
79
- private
70
+ def with_credentials_for(id, resource: nil)
71
+ # Get the credentials provider
72
+ provider = nil
80
73
 
81
- # Retrieve credentials in @user and @password.
82
- # Do it only once.
83
- # Make sure the retrieved credentials are not linked to other objects in memory, so that we can remove any other trace of secrets.
84
- def retrieve_credentials
85
- return if @retrieved
74
+ # Check configuration
75
+ # Take the last matching provider, this way we can define several providers for resources matched in a increasingly refined way.
76
+ @config.credentials.each do |credentials_info|
77
+ provider = credentials_info[:provider] if credentials_info[:credential_id] == id && (
78
+ (resource.nil? && credentials_info[:resource] == /.*/) || credentials_info[:resource] =~ resource
79
+ )
80
+ end
86
81
 
87
- # Check environment variables
88
- @user = ENV["hpc_user_for_#{@id}"].dup
89
- @password = ENV["hpc_password_for_#{@id}"].dup
90
- if @user.nil? || @user.empty? || @password.nil? || @password.empty?
91
- log_debug "[ Credentials for #{@id} ] - Credentials not found from environment variables."
92
- if @url.nil?
93
- log_debug "[ Credentials for #{@id} ] - No URL associated to this credentials, so .netrc can't be used."
94
- else
95
- # Check Netrc
96
- netrc = ::Netrc.read
97
- begin
98
- netrc_user, netrc_password = netrc[URI.parse(@url).host.downcase]
99
- if netrc_user.nil?
100
- log_debug "[ Credentials for #{@id} ] - No credentials retrieved from .netrc."
101
- # TODO: Add more credentials source if needed here
102
- log_warn "[ Credentials for #{@id} ] - Unable to get credentials for #{@id} (URL: #{@url})."
103
- else
104
- @user = netrc_user.dup
105
- @password = netrc_password.dup
106
- log_debug "[ Credentials for #{@id} ] - Credentials retrieved from .netrc using #{@url}."
107
- end
108
- ensure
109
- # Make sure the password does not stay in Netrc memory
110
- # Wipe out any memory trace that might contain passwords in clear
111
- netrc.instance_variable_get(:@data).each do |data_line|
112
- data_line.each do |data_string|
113
- data_string.replace('GotYou!!!' * 100)
82
+ provider ||= proc do |requested_resource, requester|
83
+ # Check environment variables
84
+ user = ENV["hpc_user_for_#{id}"].dup
85
+ password = ENV["hpc_password_for_#{id}"].dup
86
+ if user.nil? || user.empty? || password.nil? || password.empty?
87
+ log_debug "[ Credentials for #{id} ] - Credentials not found from environment variables."
88
+ if requested_resource.nil?
89
+ log_debug "[ Credentials for #{id} ] - No resource associated to this credentials, so .netrc can't be used."
90
+ else
91
+ # Check Netrc
92
+ netrc = ::Netrc.read
93
+ begin
94
+ netrc_user, netrc_password = netrc[
95
+ begin
96
+ URI.parse(requested_resource).host.downcase
97
+ rescue URI::InvalidURIError
98
+ requested_resource
99
+ end
100
+ ]
101
+ if netrc_user.nil?
102
+ log_debug "[ Credentials for #{id} ] - No credentials retrieved from .netrc."
103
+ # TODO: Add more credentials source if needed here
104
+ log_warn "[ Credentials for #{id} ] - Unable to get credentials for #{id} (Resource: #{requested_resource})."
105
+ else
106
+ user = netrc_user.dup
107
+ password = netrc_password.dup
108
+ log_debug "[ Credentials for #{id} ] - Credentials retrieved from .netrc using #{requested_resource}."
109
+ end
110
+ ensure
111
+ # Make sure the password does not stay in Netrc memory
112
+ # Wipe out any memory trace that might contain passwords in clear
113
+ netrc.instance_variable_get(:@data).each do |data_line|
114
+ data_line.each do |data_string|
115
+ data_string.replace('GotYou!!!' * 100)
116
+ end
114
117
  end
118
+ # We do this assignment on purpose so that GC can remove sensitive data later
119
+ # rubocop:disable Lint/UselessAssignment
120
+ netrc = nil
121
+ # rubocop:enable Lint/UselessAssignment
115
122
  end
116
- # We don this assignment on purpose so that GC can remove sensitive data later
117
- # rubocop:disable Lint/UselessAssignment
118
- netrc = nil
119
- # rubocop:enable Lint/UselessAssignment
120
123
  end
124
+ else
125
+ log_debug "[ Credentials for #{id} ] - Credentials retrieved from environment variables."
121
126
  end
122
- else
123
- log_debug "[ Credentials for #{@id} ] - Credentials retrieved from environment variables."
127
+ GC.start
128
+ requester.call user, password
129
+ password&.replace('gotyou!' * 100)
130
+ GC.start
124
131
  end
125
- GC.start
132
+
133
+ requester_called = false
134
+ provider.call(
135
+ resource,
136
+ proc do |user, password|
137
+ requester_called = true
138
+ yield user, password
139
+ end
140
+ )
141
+
142
+ raise "Requester not called by the credentials provider for #{id} (resource: #{resource}) - Please check the credentials_for code in your configuration." unless requester_called
126
143
  end
127
144
 
128
145
  end