ibm-cloud-sdk-core 3.20.6__py3-none-any.whl → 3.22.0__py3-none-any.whl

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. ibm_cloud_sdk_core/__init__.py +2 -0
  2. ibm_cloud_sdk_core/authenticators/__init__.py +1 -0
  3. ibm_cloud_sdk_core/authenticators/authenticator.py +1 -0
  4. ibm_cloud_sdk_core/authenticators/basic_authenticator.py +7 -1
  5. ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +6 -1
  6. ibm_cloud_sdk_core/authenticators/container_authenticator.py +1 -0
  7. ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +5 -2
  8. ibm_cloud_sdk_core/authenticators/iam_assume_authenticator.py +146 -0
  9. ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +5 -1
  10. ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +5 -1
  11. ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +5 -1
  12. ibm_cloud_sdk_core/base_service.py +28 -10
  13. ibm_cloud_sdk_core/get_authenticator.py +22 -1
  14. ibm_cloud_sdk_core/logger.py +85 -0
  15. ibm_cloud_sdk_core/token_managers/container_token_manager.py +3 -3
  16. ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +5 -0
  17. ibm_cloud_sdk_core/token_managers/iam_assume_token_manager.py +150 -0
  18. ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +8 -1
  19. ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +7 -1
  20. ibm_cloud_sdk_core/token_managers/token_manager.py +7 -0
  21. ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +4 -5
  22. ibm_cloud_sdk_core/utils.py +6 -2
  23. ibm_cloud_sdk_core/version.py +1 -1
  24. ibm_cloud_sdk_core-3.22.0.dist-info/METADATA +160 -0
  25. ibm_cloud_sdk_core-3.22.0.dist-info/RECORD +37 -0
  26. {ibm_cloud_sdk_core-3.20.6.dist-info → ibm_cloud_sdk_core-3.22.0.dist-info}/WHEEL +1 -1
  27. ibm_cloud_sdk_core-3.20.6.dist-info/METADATA +0 -124
  28. ibm_cloud_sdk_core-3.20.6.dist-info/RECORD +0 -34
  29. {ibm_cloud_sdk_core-3.20.6.dist-info → ibm_cloud_sdk_core-3.22.0.dist-info}/LICENSE +0 -0
  30. {ibm_cloud_sdk_core-3.20.6.dist-info → ibm_cloud_sdk_core-3.22.0.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ classes:
18
18
  BaseService: Abstract class for common functionality between each service.
19
19
  DetailedResponse: The object returned from successful service operations.
20
20
  IAMTokenManager: Requests and refreshes IAM tokens using an apikey, and optionally a client_id and client_secret.
21
+ IAMAssumeTokenManager: Requests and refreshes IAM tokens using an apikey and a trusted profile.
21
22
  JWTTokenManager: Abstract class for common functionality between each JWT token manager.
22
23
  CP4DTokenManager: Requests and refreshes CP4D tokens given a username and password.
23
24
  ApiException: Custom exception class for errors returned from service operations.
@@ -39,6 +40,7 @@ functions:
39
40
  from .base_service import BaseService
40
41
  from .detailed_response import DetailedResponse
41
42
  from .token_managers.iam_token_manager import IAMTokenManager
43
+ from .token_managers.iam_assume_token_manager import IAMAssumeTokenManager
42
44
  from .token_managers.jwt_token_manager import JWTTokenManager
43
45
  from .token_managers.cp4d_token_manager import CP4DTokenManager
44
46
  from .token_managers.container_token_manager import ContainerTokenManager
@@ -39,6 +39,7 @@ from .bearer_token_authenticator import BearerTokenAuthenticator
39
39
  from .container_authenticator import ContainerAuthenticator
40
40
  from .cp4d_authenticator import CloudPakForDataAuthenticator
41
41
  from .iam_authenticator import IAMAuthenticator
42
+ from .iam_assume_authenticator import IAMAssumeAuthenticator
42
43
  from .vpc_instance_authenticator import VPCInstanceAuthenticator
43
44
  from .no_auth_authenticator import NoAuthAuthenticator
44
45
  from .mcsp_authenticator import MCSPAuthenticator
@@ -24,6 +24,7 @@ class Authenticator(ABC):
24
24
  AUTHTYPE_BASIC = 'basic'
25
25
  AUTHTYPE_BEARERTOKEN = 'bearerToken'
26
26
  AUTHTYPE_IAM = 'iam'
27
+ AUTHTYPE_IAM_ASSUME = 'iamAssume'
27
28
  AUTHTYPE_CONTAINER = 'container'
28
29
  AUTHTYPE_CP4D = 'cp4d'
29
30
  AUTHTYPE_VPC = 'vpc'
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2019 IBM All Rights Reserved.
3
+ # Copyright 2019, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -15,11 +15,15 @@
15
15
  # limitations under the License.
16
16
 
17
17
  import base64
18
+
18
19
  from requests import Request
19
20
 
21
+ from ibm_cloud_sdk_core.logger import get_logger
20
22
  from .authenticator import Authenticator
21
23
  from ..utils import has_bad_first_or_last_char
22
24
 
25
+ logger = get_logger()
26
+
23
27
 
24
28
  class BasicAuthenticator(Authenticator):
25
29
  """The BasicAuthenticator is used to add basic authentication information to requests.
@@ -41,6 +45,7 @@ class BasicAuthenticator(Authenticator):
41
45
  self.password = password
42
46
  self.validate()
43
47
  self.authorization_header = self.__construct_basic_auth_header()
48
+ logger.debug('Created new BasicAuthenticator instance!')
44
49
 
45
50
  def authentication_type(self) -> str:
46
51
  """Returns this authenticator's type ('basic')."""
@@ -81,3 +86,4 @@ class BasicAuthenticator(Authenticator):
81
86
  """
82
87
  headers = req.get('headers')
83
88
  headers['Authorization'] = self.authorization_header
89
+ logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2019 IBM All Rights Reserved.
3
+ # Copyright 2019, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -16,8 +16,11 @@
16
16
 
17
17
  from requests import Request
18
18
 
19
+ from ibm_cloud_sdk_core.logger import get_logger
19
20
  from .authenticator import Authenticator
20
21
 
22
+ logger = get_logger()
23
+
21
24
 
22
25
  class BearerTokenAuthenticator(Authenticator):
23
26
  """The BearerTokenAuthenticator will add a user-supplied bearer token
@@ -37,6 +40,7 @@ class BearerTokenAuthenticator(Authenticator):
37
40
  def __init__(self, bearer_token: str) -> None:
38
41
  self.bearer_token = bearer_token
39
42
  self.validate()
43
+ logger.debug('Created BearerTokenAuthenticator instance!')
40
44
 
41
45
  def authentication_type(self) -> str:
42
46
  """Returns this authenticator's type ('bearertoken')."""
@@ -66,6 +70,7 @@ class BearerTokenAuthenticator(Authenticator):
66
70
  """
67
71
  headers = req.get('headers')
68
72
  headers['Authorization'] = 'Bearer {0}'.format(self.bearer_token)
73
+ logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
69
74
 
70
75
  def set_bearer_token(self, bearer_token: str) -> None:
71
76
  """Set a new bearer token to be sent in subsequent service operations.
@@ -66,6 +66,7 @@ class ContainerAuthenticator(IAMRequestBasedAuthenticator):
66
66
 
67
67
  def __init__(
68
68
  self,
69
+ *,
69
70
  cr_token_filename: Optional[str] = None,
70
71
  iam_profile_name: Optional[str] = None,
71
72
  iam_profile_id: Optional[str] = None,
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2019 IBM All Rights Reserved.
3
+ # Copyright 2019, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -15,13 +15,15 @@
15
15
  # limitations under the License.
16
16
 
17
17
  from typing import Dict, Optional
18
-
19
18
  from requests import Request
20
19
 
20
+ from ibm_cloud_sdk_core.logger import get_logger
21
21
  from .authenticator import Authenticator
22
22
  from ..token_managers.cp4d_token_manager import CP4DTokenManager
23
23
  from ..utils import has_bad_first_or_last_char
24
24
 
25
+ logger = get_logger()
26
+
25
27
 
26
28
  class CloudPakForDataAuthenticator(Authenticator):
27
29
  """The CloudPakForDataAuthenticator utilizes a username and password pair to
@@ -133,6 +135,7 @@ class CloudPakForDataAuthenticator(Authenticator):
133
135
  headers = req.get('headers')
134
136
  bearer_token = self.token_manager.get_token()
135
137
  headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
138
+ logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
136
139
 
137
140
  def set_disable_ssl_verification(self, status: bool = False) -> None:
138
141
  """Set the flag that indicates whether verification of the server's SSL certificate should be
@@ -0,0 +1,146 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 2024 IBM All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ from typing import Any, Dict, Optional
18
+
19
+ from ibm_cloud_sdk_core.authenticators.iam_authenticator import IAMAuthenticator
20
+ from ibm_cloud_sdk_core.token_managers.iam_assume_token_manager import IAMAssumeTokenManager
21
+
22
+ from .authenticator import Authenticator
23
+ from .iam_request_based_authenticator import IAMRequestBasedAuthenticator
24
+
25
+
26
+ class IAMAssumeAuthenticator(IAMRequestBasedAuthenticator):
27
+ """IAMAssumeAuthenticator obtains an IAM access token using the IAM "get-token" operation's
28
+ "assume" grant type. The authenticator obtains an initial IAM access token from a
29
+ user-supplied apikey, then exchanges this initial IAM access token for another IAM access token
30
+ that has "assumed the identity" of the specified trusted profile.
31
+
32
+ The bearer token will be sent as an Authorization header in the form:
33
+
34
+ Authorization: Bearer <bearer-token>
35
+
36
+ Args:
37
+ apikey: The IAM api key.
38
+
39
+ Keyword Args:
40
+ iam_profile_id: the ID of the trusted profile
41
+ iam_profile_crn: the CRN of the trusted profile
42
+ iam_profile_name: the name of the trusted profile (must be used together with `iam_account_id`)
43
+ iam_account_id: the ID of the trusted profile (must be used together with `iam_profile_name`)
44
+ url: The URL representing the IAM token service endpoint. If not specified, a suitable default value is used.
45
+ client_id: The client_id and client_secret fields are used to form
46
+ a "basic" authorization header for IAM token requests. Defaults to None.
47
+ client_secret: The client_id and client_secret fields are used to form
48
+ a "basic" authorization header for IAM token requests. Defaults to None.
49
+ disable_ssl_verification: A flag that indicates whether verification of
50
+ the server's SSL certificate should be disabled or not. Defaults to False.
51
+ headers: Default headers to be sent with every IAM token request. Defaults to None.
52
+ proxies: Dictionary for mapping request protocol to proxy URL. Defaults to None.
53
+ proxies.http (optional): The proxy endpoint to use for HTTP requests.
54
+ proxies.https (optional): The proxy endpoint to use for HTTPS requests.
55
+ scope: The "scope" to use when fetching the bearer token from the IAM token server.
56
+ This can be used to obtain an access token with a specific scope.
57
+
58
+ Attributes:
59
+ token_manager (IAMTokenManager): Retrieves and manages IAM tokens from the endpoint specified by the url.
60
+
61
+ Raises:
62
+ TypeError: The `disable_ssl_verification` is not a bool.
63
+ ValueError: The `apikey`, `client_id`, and/or `client_secret` are not valid for IAM token requests or the
64
+ following keyword arguments are incorrectly specified:
65
+ `iam_profile_id`, `iam_profile_crn`, `iam_profile_name`, `iam_account_id`.
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ apikey: str,
71
+ *,
72
+ iam_profile_id: Optional[str] = None,
73
+ iam_profile_crn: Optional[str] = None,
74
+ iam_profile_name: Optional[str] = None,
75
+ iam_account_id: Optional[str] = None,
76
+ url: Optional[str] = None,
77
+ client_id: Optional[str] = None,
78
+ client_secret: Optional[str] = None,
79
+ disable_ssl_verification: bool = False,
80
+ headers: Optional[Dict[str, str]] = None,
81
+ proxies: Optional[Dict[str, str]] = None,
82
+ scope: Optional[str] = None,
83
+ ) -> None:
84
+ # Check the type of `disable_ssl_verification`. Must be a bool.
85
+ if not isinstance(disable_ssl_verification, bool):
86
+ raise TypeError('disable_ssl_verification must be a bool')
87
+
88
+ self.token_manager = IAMAssumeTokenManager(
89
+ apikey,
90
+ iam_profile_id=iam_profile_id,
91
+ iam_profile_crn=iam_profile_crn,
92
+ iam_profile_name=iam_profile_name,
93
+ iam_account_id=iam_account_id,
94
+ url=url,
95
+ client_id=client_id,
96
+ client_secret=client_secret,
97
+ disable_ssl_verification=disable_ssl_verification,
98
+ headers=headers,
99
+ proxies=proxies,
100
+ scope=scope,
101
+ )
102
+
103
+ self.validate()
104
+
105
+ # Disable all setter methods, inherited from the parent class.
106
+ def __getattribute__(self, name: str) -> Any:
107
+ if name.startswith("set_"):
108
+ raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
109
+
110
+ return super().__getattribute__(name)
111
+
112
+ def authentication_type(self) -> str:
113
+ """Returns this authenticator's type ('iamAssume')."""
114
+ return Authenticator.AUTHTYPE_IAM_ASSUME
115
+
116
+ def validate(self) -> None:
117
+ """Validates the provided IAM related arguments.
118
+
119
+ Ensure the following:
120
+ - `apikey` of the IAMTokenManager is not `None`, and has no bad characters
121
+ - both `client_id` and `client_secret` are set if either of them are defined
122
+ - the correct number and type of IAM profile and IAM account options are specified
123
+
124
+ Raises:
125
+ ValueError: The apikey, client_id, and/or client_secret are not valid for IAM token requests.
126
+ """
127
+ # Create a temporary IAM authenticator that we can use to validate our delegate.
128
+ tmp_authenticator = IAMAuthenticator("")
129
+ tmp_authenticator.token_manager = self.token_manager.iam_delegate
130
+ tmp_authenticator.validate()
131
+ del tmp_authenticator
132
+
133
+ # Only one of the following arguments must be specified.
134
+ mutually_exclusive_attributes = [
135
+ self.token_manager.iam_profile_id,
136
+ self.token_manager.iam_profile_crn,
137
+ self.token_manager.iam_profile_name,
138
+ ]
139
+ if list(map(bool, mutually_exclusive_attributes)).count(True) != 1:
140
+ raise ValueError(
141
+ 'Exactly one of `iam_profile_id`, `iam_profile_crn`, or `iam_profile_name` must be specified.'
142
+ )
143
+
144
+ # `iam_account_id` must be specified iff `iam_profile_name` is used.
145
+ if self.token_manager.iam_profile_name and not self.token_manager.iam_account_id:
146
+ raise ValueError('`iam_profile_name` and `iam_account_id` must be provided together, or not at all.')
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2019 IBM All Rights Reserved.
3
+ # Copyright 2019, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -18,8 +18,11 @@ from typing import Dict
18
18
 
19
19
  from requests import Request
20
20
 
21
+ from ibm_cloud_sdk_core.logger import get_logger
21
22
  from .authenticator import Authenticator
22
23
 
24
+ logger = get_logger()
25
+
23
26
 
24
27
  class IAMRequestBasedAuthenticator(Authenticator):
25
28
  """The IAMRequestBasedAuthenticator class contains code that is common to all authenticators
@@ -60,6 +63,7 @@ class IAMRequestBasedAuthenticator(Authenticator):
60
63
  headers = req.get('headers')
61
64
  bearer_token = self.token_manager.get_token()
62
65
  headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
66
+ logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
63
67
 
64
68
  def set_client_id_and_secret(self, client_id: str, client_secret: str) -> None:
65
69
  """Set the client_id and client_secret pair the token manager will use for IAM token requests.
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2023 IBM All Rights Reserved.
3
+ # Copyright 2023, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -18,9 +18,12 @@ from typing import Dict, Optional
18
18
 
19
19
  from requests import Request
20
20
 
21
+ from ibm_cloud_sdk_core.logger import get_logger
21
22
  from .authenticator import Authenticator
22
23
  from ..token_managers.mcsp_token_manager import MCSPTokenManager
23
24
 
25
+ logger = get_logger()
26
+
24
27
 
25
28
  class MCSPAuthenticator(Authenticator):
26
29
  """The MCSPAuthenticator uses an apikey to obtain an access token from the MCSP token server.
@@ -98,6 +101,7 @@ class MCSPAuthenticator(Authenticator):
98
101
  headers = req.get('headers')
99
102
  bearer_token = self.token_manager.get_token()
100
103
  headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
104
+ logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
101
105
 
102
106
  def set_disable_ssl_verification(self, status: bool = False) -> None:
103
107
  """Set the flag that indicates whether verification of the server's SSL certificate should be
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2021 IBM All Rights Reserved.
3
+ # Copyright 2021, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -18,9 +18,12 @@ from typing import Optional
18
18
 
19
19
  from requests import Request
20
20
 
21
+ from ibm_cloud_sdk_core.logger import get_logger
21
22
  from ..token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
22
23
  from .authenticator import Authenticator
23
24
 
25
+ logger = get_logger()
26
+
24
27
 
25
28
  class VPCInstanceAuthenticator(Authenticator):
26
29
  """VPCInstanceAuthenticator implements an authentication scheme in which it
@@ -89,6 +92,7 @@ class VPCInstanceAuthenticator(Authenticator):
89
92
  headers = req.get('headers')
90
93
  bearer_token = self.token_manager.get_token()
91
94
  headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
95
+ logger.debug('Authenticated outbound request (type=%s)', self.authentication_type())
92
96
 
93
97
  def set_iam_profile_crn(self, iam_profile_crn: str) -> None:
94
98
  """Sets CRN of the IAM profile.
@@ -16,9 +16,10 @@
16
16
 
17
17
  import gzip
18
18
  import io
19
- import json as json_import
20
19
  import logging
20
+ import json as json_import
21
21
  from http.cookiejar import CookieJar
22
+ from http import client
22
23
  from os.path import basename
23
24
  from typing import Dict, List, Optional, Tuple, Union
24
25
  from urllib3.util.retry import Retry
@@ -42,13 +43,12 @@ from .utils import (
42
43
  GzipStream,
43
44
  )
44
45
  from .private_helpers import _build_user_agent
46
+ from .logger import (
47
+ get_logger,
48
+ LoggingFilter,
49
+ )
45
50
 
46
- # Uncomment this to enable http debugging
47
- # import http.client as http_client
48
- # http_client.HTTPConnection.debuglevel = 1
49
-
50
-
51
- logger = logging.getLogger(__name__)
51
+ logger = get_logger()
52
52
 
53
53
 
54
54
  # pylint: disable=too-many-instance-attributes
@@ -92,7 +92,7 @@ class BaseService:
92
92
  self,
93
93
  *,
94
94
  service_url: str = None,
95
- authenticator: Authenticator = None,
95
+ authenticator: Optional[Authenticator] = None,
96
96
  disable_ssl_verification: bool = False,
97
97
  enable_gzip_compression: bool = False,
98
98
  ) -> None:
@@ -114,6 +114,12 @@ class BaseService:
114
114
 
115
115
  self.http_client.mount('http://', self.http_adapter)
116
116
  self.http_client.mount('https://', self.http_adapter)
117
+ # If debug logging is requested, then trigger HTTP message logging as well.
118
+ if logger.isEnabledFor(logging.DEBUG):
119
+ client.HTTPConnection.debuglevel = 1
120
+ # Replace the `print` function in the HTTPClient module to
121
+ # use the debug logger instead of the bare Python print.
122
+ client.print = lambda *args: logger.debug(LoggingFilter.filter_message(" ".join(args)))
117
123
 
118
124
  def enable_retries(self, max_retries: int = 4, retry_interval: float = 30.0) -> None:
119
125
  """Enable automatic retries on the underlying http client used by the BaseService instance.
@@ -141,6 +147,7 @@ class BaseService:
141
147
  )
142
148
  self.http_client.mount('http://', self.http_adapter)
143
149
  self.http_client.mount('https://', self.http_adapter)
150
+ logger.debug('Enabled retries; max_retries=%d, max_retry_interval=%f', max_retries, retry_interval)
144
151
 
145
152
  def disable_retries(self):
146
153
  """Remove retry config from http_adapter"""
@@ -148,6 +155,7 @@ class BaseService:
148
155
  self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
149
156
  self.http_client.mount('http://', self.http_adapter)
150
157
  self.http_client.mount('https://', self.http_adapter)
158
+ logger.debug('Disabled retries')
151
159
 
152
160
  def configure_service(self, service_name: str) -> None:
153
161
  """Look for external configuration of a service. Set service properties.
@@ -166,6 +174,8 @@ class BaseService:
166
174
  if not isinstance(service_name, str):
167
175
  raise ValueError('Service_name must be of type string.')
168
176
 
177
+ logger.debug('Configuring BaseService instance with service name: %s', service_name)
178
+
169
179
  config = read_external_sources(service_name)
170
180
  if config.get('URL'):
171
181
  self.set_service_url(config.get('URL'))
@@ -184,6 +194,7 @@ class BaseService:
184
194
 
185
195
  def _set_user_agent_header(self, user_agent_string: str) -> None:
186
196
  self.user_agent_header = {'User-Agent': user_agent_string}
197
+ logger.debug('Set User-Agent: %s', user_agent_string)
187
198
 
188
199
  def set_http_config(self, http_config: dict) -> None:
189
200
  """Sets the http config dictionary.
@@ -225,6 +236,7 @@ class BaseService:
225
236
  )
226
237
  self.http_client.mount('http://', self.http_adapter)
227
238
  self.http_client.mount('https://', self.http_adapter)
239
+ logger.debug('Disabled SSL verification in HTTP client')
228
240
 
229
241
  def set_service_url(self, service_url: str) -> None:
230
242
  """Set the url the service will make HTTP requests too.
@@ -243,6 +255,7 @@ class BaseService:
243
255
  if service_url is not None:
244
256
  service_url = service_url.rstrip('/')
245
257
  self.service_url = service_url
258
+ logger.debug('Set service URL: %s', service_url)
246
259
 
247
260
  def get_http_client(self) -> requests.sessions.Session:
248
261
  """Get the http client session currently used by the service.
@@ -305,7 +318,7 @@ class BaseService:
305
318
  # Check to see if the caller specified the 'stream' argument.
306
319
  stream_response = kwargs.get('stream') or False
307
320
 
308
- # Remove the keys we set manually, don't let the user to overwrite these.
321
+ # Remove the keys we set manually, don't let the user overwrite these.
309
322
  reserved_keys = ['method', 'url', 'headers', 'params', 'cookies']
310
323
  silent_keys = ['headers']
311
324
  for key in reserved_keys:
@@ -314,8 +327,12 @@ class BaseService:
314
327
  if key not in silent_keys:
315
328
  logger.warning('"%s" has been removed from the request', key)
316
329
  try:
330
+ logger.debug('Sending HTTP request message')
331
+
317
332
  response = self.http_client.request(**request, cookies=self.jar, **kwargs)
318
333
 
334
+ logger.debug('Received HTTP response message, status code %d', response.status_code)
335
+
319
336
  # Process a "success" response.
320
337
  if 200 <= response.status_code <= 299:
321
338
  if response.status_code == 204 or request['method'] == 'HEAD':
@@ -455,6 +472,7 @@ class BaseService:
455
472
  file_tuple = (filename, file_tuple[1], file_tuple[2])
456
473
  new_files.append((part_name, file_tuple))
457
474
  request['files'] = new_files
475
+ logger.debug('Prepared request [%s %s]', request['method'], request['url'])
458
476
  return request
459
477
 
460
478
  @staticmethod
@@ -490,4 +508,4 @@ class BaseService:
490
508
 
491
509
  @staticmethod
492
510
  def _encode_path_vars(*args) -> None:
493
- return (requests.utils.quote(x, safe='') for x in args)
511
+ return BaseService.encode_path_vars(*args)
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- # Copyright 2019 IBM All Rights Reserved.
3
+ # Copyright 2019, 2024 IBM All Rights Reserved.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -21,11 +21,15 @@ from .authenticators import (
21
21
  ContainerAuthenticator,
22
22
  CloudPakForDataAuthenticator,
23
23
  IAMAuthenticator,
24
+ IAMAssumeAuthenticator,
24
25
  NoAuthAuthenticator,
25
26
  VPCInstanceAuthenticator,
26
27
  MCSPAuthenticator,
27
28
  )
28
29
  from .utils import read_external_sources
30
+ from .logger import get_logger
31
+
32
+ logger = get_logger()
29
33
 
30
34
 
31
35
  def get_authenticator_from_environment(service_name: str) -> Authenticator:
@@ -42,13 +46,17 @@ def get_authenticator_from_environment(service_name: str) -> Authenticator:
42
46
  Returns:
43
47
  The authenticator found from service information.
44
48
  """
49
+ logger.debug('Get authenticator from environment, key=%s', service_name)
45
50
  authenticator = None
46
51
  config = read_external_sources(service_name)
47
52
  if config:
48
53
  authenticator = __construct_authenticator(config)
54
+ if authenticator is not None:
55
+ logger.debug('Returning authenticator, type=%s', authenticator.authentication_type())
49
56
  return authenticator
50
57
 
51
58
 
59
+ # pylint: disable=too-many-branches
52
60
  def __construct_authenticator(config: dict) -> Authenticator:
53
61
  # Determine the authentication type if not specified explicitly.
54
62
  if config.get('AUTH_TYPE'):
@@ -98,6 +106,19 @@ def __construct_authenticator(config: dict) -> Authenticator:
98
106
  disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
99
107
  scope=config.get('SCOPE'),
100
108
  )
109
+ elif auth_type == Authenticator.AUTHTYPE_IAM_ASSUME.lower():
110
+ authenticator = IAMAssumeAuthenticator(
111
+ apikey=config.get('APIKEY'),
112
+ iam_profile_id=config.get('IAM_PROFILE_ID'),
113
+ iam_profile_crn=config.get('IAM_PROFILE_CRN'),
114
+ iam_profile_name=config.get('IAM_PROFILE_NAME'),
115
+ iam_account_id=config.get('IAM_ACCOUNT_ID'),
116
+ url=config.get('AUTH_URL'),
117
+ client_id=config.get('CLIENT_ID'),
118
+ client_secret=config.get('CLIENT_SECRET'),
119
+ disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
120
+ scope=config.get('SCOPE'),
121
+ )
101
122
  elif auth_type == Authenticator.AUTHTYPE_VPC.lower():
102
123
  authenticator = VPCInstanceAuthenticator(
103
124
  iam_profile_crn=config.get('IAM_PROFILE_CRN'),
@@ -0,0 +1,85 @@
1
+ # coding: utf-8
2
+
3
+ # Copyright 2024 IBM All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import logging
18
+ import re
19
+
20
+
21
+ # This is the name of the primary logger used by the library.
22
+ LOGGER_NAME = 'ibm-cloud-sdk-core'
23
+ # Keywords that are redacted.
24
+ REDACTED_KEYWORDS = [
25
+ "apikey",
26
+ "api_key",
27
+ "passcode",
28
+ "password",
29
+ "token",
30
+ "aadClientId",
31
+ "aadClientSecret",
32
+ "auth",
33
+ "auth_provider_x509_cert_url",
34
+ "auth_uri",
35
+ "client_email",
36
+ "client_id",
37
+ "client_x509_cert_url",
38
+ "key",
39
+ "project_id",
40
+ "secret",
41
+ "subscriptionId",
42
+ "tenantId",
43
+ "thumbprint",
44
+ "token_uri",
45
+ ]
46
+
47
+
48
+ class LoggingFilter:
49
+ """Functions used to filter messages before they are logged."""
50
+
51
+ redacted_tokens = "|".join(REDACTED_KEYWORDS)
52
+ auth_header_pattern = re.compile(r"(?m)(Authorization|X-Auth\S*): ((.*?)(\r\n.*)|(.*))")
53
+ property_settings_pattern = re.compile(r"(?i)(" + redacted_tokens + r")=[^&]*(&|$)")
54
+ json_field_pattern = re.compile(r'(?i)"([^"]*(' + redacted_tokens + r')[^"_]*)":\s*"[^\,]*"')
55
+
56
+ @classmethod
57
+ def redact_secrets(cls, text: str) -> str:
58
+ """Replaces values of potential secret keywords with a placeholder value.
59
+ Args:
60
+ text (str): the string to check and process
61
+
62
+ Returns:
63
+ str: the safe, redacted string with all secrets masked out
64
+ """
65
+
66
+ placeholder = "[redacted]"
67
+ redacted = cls.auth_header_pattern.sub(r"\1: " + placeholder + r"\4", text)
68
+ redacted = cls.property_settings_pattern.sub(r"\1=" + placeholder + r"\2", redacted)
69
+ redacted = cls.json_field_pattern.sub(r'"\1":"' + placeholder + r'"', redacted)
70
+ return redacted
71
+
72
+ @classmethod
73
+ def filter_message(cls, s: str) -> str:
74
+ """Filters 's' prior to logging it as a debug message"""
75
+ # Redact secrets
76
+ s = LoggingFilter.redact_secrets(s)
77
+
78
+ # Replace CRLF characters with an actual newline to make the message more readable.
79
+ s = s.replace('\\r\\n', '\n')
80
+ return s
81
+
82
+
83
+ def get_logger() -> logging.Logger:
84
+ """Returns the primary logger object instance used by the library."""
85
+ return logging.getLogger(LOGGER_NAME)