cumulusci-plus 5.0.25__py3-none-any.whl → 5.0.27__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.
Potentially problematic release.
This version of cumulusci-plus might be problematic. Click here for more details.
- cumulusci/__about__.py +1 -1
- cumulusci/cli/tests/test_error.py +3 -1
- cumulusci/core/flowrunner.py +2 -0
- cumulusci/core/github.py +1 -1
- cumulusci/core/sfdx.py +3 -1
- cumulusci/core/tests/test_flowrunner.py +100 -0
- cumulusci/cumulusci.yml +8 -0
- cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
- cumulusci/salesforce_api/rest_deploy.py +1 -1
- cumulusci/tasks/apex/anon.py +1 -1
- cumulusci/tasks/apex/testrunner.py +6 -1
- cumulusci/tasks/bulkdata/extract.py +0 -1
- cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +6 -0
- cumulusci/tasks/metadata_etl/base.py +7 -3
- cumulusci/tasks/push/README.md +15 -17
- cumulusci/tasks/release_notes/README.md +13 -13
- cumulusci/tasks/robotframework/tests/test_robotframework.py +1 -1
- cumulusci/tasks/salesforce/Deploy.py +5 -1
- cumulusci/tasks/salesforce/composite.py +1 -1
- cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
- cumulusci/tasks/salesforce/enable_prediction.py +5 -1
- cumulusci/tasks/salesforce/sourcetracking.py +1 -1
- cumulusci/tasks/salesforce/update_profile.py +17 -13
- cumulusci/tasks/salesforce/users/permsets.py +16 -9
- cumulusci/tasks/utility/credentialManager.py +256 -0
- cumulusci/tasks/utility/directoryRecreator.py +30 -0
- cumulusci/tasks/utility/secretsToEnv.py +132 -0
- cumulusci/tasks/utility/tests/test_credentialManager.py +564 -0
- cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
- cumulusci/tasks/utility/tests/test_secretsToEnv.py +1091 -0
- cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
- cumulusci/utils/yaml/tests/test_model_parser.py +2 -2
- {cumulusci_plus-5.0.25.dist-info → cumulusci_plus-5.0.27.dist-info}/METADATA +6 -9
- {cumulusci_plus-5.0.25.dist-info → cumulusci_plus-5.0.27.dist-info}/RECORD +39 -33
- {cumulusci_plus-5.0.25.dist-info → cumulusci_plus-5.0.27.dist-info}/WHEEL +0 -0
- {cumulusci_plus-5.0.25.dist-info → cumulusci_plus-5.0.27.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.25.dist-info → cumulusci_plus-5.0.27.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.25.dist-info → cumulusci_plus-5.0.27.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Abstract Base Class for Credential Providers
|
|
8
|
+
class CredentialProvider(ABC):
|
|
9
|
+
"""
|
|
10
|
+
Abstract Base Class that defines the interface for all credential providers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# A class-level dictionary to store a mapping of provider names to provider classes
|
|
14
|
+
_registry = {}
|
|
15
|
+
logger: logging.Logger
|
|
16
|
+
|
|
17
|
+
def __init__(self, **kwargs):
|
|
18
|
+
self.key_prefix = kwargs.get(
|
|
19
|
+
"key_prefix", os.getenv("CUMULUSCI_PREFIX_SECRETS", "").upper()
|
|
20
|
+
)
|
|
21
|
+
self.logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
def __init_subclass__(cls, **kwargs):
|
|
24
|
+
"""
|
|
25
|
+
This method is called automatically when a new class inherits from CredentialProvider.
|
|
26
|
+
It's used to register the new class in our registry.
|
|
27
|
+
"""
|
|
28
|
+
super().__init_subclass__(**kwargs)
|
|
29
|
+
# We'll use an attribute on each class to define its provider type.
|
|
30
|
+
# This will be the key in our registry.
|
|
31
|
+
if hasattr(cls, "provider_type"):
|
|
32
|
+
CredentialProvider._registry[cls.provider_type] = cls
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get_credentials(
|
|
36
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
37
|
+
) -> Any:
|
|
38
|
+
"""
|
|
39
|
+
Retrieves and returns credentials.
|
|
40
|
+
"""
|
|
41
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def get_all_credentials(
|
|
45
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
46
|
+
) -> Any:
|
|
47
|
+
"""
|
|
48
|
+
Retrieves and returns all credentials in a group, for example all secrets in AWS secret.
|
|
49
|
+
"""
|
|
50
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
51
|
+
|
|
52
|
+
def get_key(self, key: str) -> str:
|
|
53
|
+
return f"{self.key_prefix}{key}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Concrete Credential Provider for Local Development
|
|
57
|
+
class DevEnvironmentVariableProvider(CredentialProvider):
|
|
58
|
+
"""
|
|
59
|
+
Retrieves secrets from environment variables.
|
|
60
|
+
This is suitable for local development.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
provider_type = "local"
|
|
64
|
+
|
|
65
|
+
def get_credentials(
|
|
66
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
67
|
+
) -> Any:
|
|
68
|
+
value = options.get("value", None)
|
|
69
|
+
self.logger.info(f"Credentials for {key} from local environment is {value}.")
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
def get_all_credentials(
|
|
73
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
74
|
+
) -> Any:
|
|
75
|
+
"""Local provider doesn't support retrieving all credentials."""
|
|
76
|
+
raise NotImplementedError("Local provider doesn't support get_all_credentials")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Concrete Credential Provider for Local Development
|
|
80
|
+
class EnvironmentVariableProvider(CredentialProvider):
|
|
81
|
+
"""
|
|
82
|
+
Retrieves secrets from environment variables.
|
|
83
|
+
This is suitable for local development.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
provider_type = "environment"
|
|
87
|
+
|
|
88
|
+
def get_credentials(
|
|
89
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
90
|
+
) -> Any:
|
|
91
|
+
value = options.get("value", None)
|
|
92
|
+
ret_value = os.getenv(self.get_key(key))
|
|
93
|
+
if ret_value is None:
|
|
94
|
+
self.logger.info(f"Credentials for {key} from environment is {value}.")
|
|
95
|
+
return ret_value or value
|
|
96
|
+
|
|
97
|
+
def get_all_credentials(
|
|
98
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
99
|
+
) -> Any:
|
|
100
|
+
"""Environment provider doesn't support retrieving all credentials."""
|
|
101
|
+
raise NotImplementedError(
|
|
102
|
+
"Environment provider doesn't support get_all_credentials"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Concrete Credential Provider for Azure Pipelines
|
|
107
|
+
class AwsSecretsManagerProvider(CredentialProvider):
|
|
108
|
+
"""
|
|
109
|
+
Retrieves secrets from AWS Secrets Manager.
|
|
110
|
+
This is designed for use in a CI/CD environment like Azure Pipelines,
|
|
111
|
+
where a Service Connection provides a role to assume.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
provider_type = "aws_secrets"
|
|
115
|
+
secrets_cache: dict[str, dict[str, Any]]
|
|
116
|
+
aws_region: str
|
|
117
|
+
|
|
118
|
+
def __init__(self, **kwargs):
|
|
119
|
+
super().__init__(**kwargs)
|
|
120
|
+
self.secrets_cache = kwargs.get("secrets_cache", {})
|
|
121
|
+
|
|
122
|
+
self.aws_region = kwargs.get("aws_region", os.getenv("AWS_REGION", None))
|
|
123
|
+
if self.aws_region is None:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
"AWS_REGION environment variable or aws_region option is required for AWS Secrets Manager."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def get_credentials(
|
|
129
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
130
|
+
) -> Any:
|
|
131
|
+
"""
|
|
132
|
+
Connects to AWS Secrets Manager to retrieve a secret.
|
|
133
|
+
The boto3 client automatically uses the credentials provided by the
|
|
134
|
+
Azure DevOps AWS Service Connection (e.g., through OIDC or static keys).
|
|
135
|
+
"""
|
|
136
|
+
return self.aws_creds(key, options).get(key, None)
|
|
137
|
+
|
|
138
|
+
def get_all_credentials(
|
|
139
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
140
|
+
) -> Any:
|
|
141
|
+
return self.aws_creds(key, options)
|
|
142
|
+
|
|
143
|
+
def aws_creds(
|
|
144
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
|
+
secret_name = options.get("secret_name", None)
|
|
147
|
+
if secret_name is None:
|
|
148
|
+
raise ValueError("Secret name is required for AWS Secrets Manager.")
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
if secret_name in self.secrets_cache:
|
|
152
|
+
return self.secrets_cache[secret_name]
|
|
153
|
+
|
|
154
|
+
import json
|
|
155
|
+
|
|
156
|
+
import boto3
|
|
157
|
+
from botocore.exceptions import ClientError
|
|
158
|
+
|
|
159
|
+
# Boto3 automatically handles credential lookup. In an Azure Pipeline,
|
|
160
|
+
# it will find the temporary credentials provided by the AWS Service Connection.
|
|
161
|
+
# Create a Secrets Manager client
|
|
162
|
+
session = boto3.session.Session()
|
|
163
|
+
client = session.client(
|
|
164
|
+
service_name="secretsmanager", region_name=self.aws_region
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
|
|
168
|
+
|
|
169
|
+
secret = get_secret_value_response["SecretString"]
|
|
170
|
+
|
|
171
|
+
# Assuming the secret is a JSON string with the credentials
|
|
172
|
+
# We need to check the binary type of the secret at later development
|
|
173
|
+
self.secrets_cache[secret_name] = json.loads(secret)
|
|
174
|
+
|
|
175
|
+
return self.secrets_cache[secret_name]
|
|
176
|
+
except ClientError as e:
|
|
177
|
+
# For a list of exceptions thrown, see
|
|
178
|
+
# https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
|
|
179
|
+
raise e
|
|
180
|
+
except ImportError:
|
|
181
|
+
raise RuntimeError(
|
|
182
|
+
"boto3 is not installed. Please install it using 'pip install boto3' or 'pipx inject cumulusci-plus-azure-devops boto3'."
|
|
183
|
+
)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
raise RuntimeError(f"Failed to retrieve secret '{key}': {e}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Concrete Credential Provider for Azure Variable Groups
|
|
189
|
+
class AzureVariableGroupProvider(CredentialProvider):
|
|
190
|
+
"""
|
|
191
|
+
Retrieves secrets from Azure Pipeline Variable Groups.
|
|
192
|
+
When a variable group is linked, its variables are exposed as
|
|
193
|
+
environment variables in the pipeline job.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
provider_type = "ado_variables"
|
|
197
|
+
|
|
198
|
+
def get_credentials(
|
|
199
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
200
|
+
) -> Any:
|
|
201
|
+
"""
|
|
202
|
+
Looks for AWS credentials in environment variables exposed by
|
|
203
|
+
an Azure variable group.
|
|
204
|
+
"""
|
|
205
|
+
# Azure pipelines convert variable names to uppercase and replace dots with underscores.
|
|
206
|
+
key_env_var = self.get_key(key)
|
|
207
|
+
return os.getenv(key_env_var.upper().replace(".", "_"))
|
|
208
|
+
|
|
209
|
+
def get_all_credentials(
|
|
210
|
+
self, key: str, options: Optional[dict[str, Any]] = None
|
|
211
|
+
) -> Any:
|
|
212
|
+
"""Azure variable group provider doesn't support retrieving all credentials."""
|
|
213
|
+
raise NotImplementedError(
|
|
214
|
+
"Azure variable group provider doesn't support get_all_credentials"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# The CredentialManager to select the right provider
|
|
219
|
+
class CredentialManager:
|
|
220
|
+
"""
|
|
221
|
+
Factory class to determine and return the correct CredentialProvider based on the environment.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
env_secrets_type = "CUMULUSCI_SECRETS_TYPE"
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def load_secrets_type_from_environment() -> str:
|
|
228
|
+
"""Load any secrets specified by environment variables"""
|
|
229
|
+
provider_type = os.getenv(CredentialManager.env_secrets_type)
|
|
230
|
+
|
|
231
|
+
# If no provider type is found, use the dev provider
|
|
232
|
+
return (provider_type or "local").lower()
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def get_provider(
|
|
236
|
+
provider_type: Optional[str] = None, **kwargs
|
|
237
|
+
) -> CredentialProvider:
|
|
238
|
+
"""
|
|
239
|
+
Looks up the provider class in the registry and returns an instance.
|
|
240
|
+
"""
|
|
241
|
+
if not provider_type:
|
|
242
|
+
# If no config is provided, load the secrets from the environment
|
|
243
|
+
provider_type = CredentialManager.load_secrets_type_from_environment()
|
|
244
|
+
|
|
245
|
+
# Check if the requested provider type exists in our registry
|
|
246
|
+
if provider_type not in CredentialProvider._registry:
|
|
247
|
+
raise ValueError(f"Unknown provider type specified: '{provider_type}'")
|
|
248
|
+
|
|
249
|
+
# Get the class from the registry
|
|
250
|
+
ProviderClass = CredentialProvider._registry[provider_type]
|
|
251
|
+
provider = ProviderClass(**kwargs)
|
|
252
|
+
provider.logger.info(
|
|
253
|
+
f'Using "{provider.provider_type}" provider for credentials.'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
return provider
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from cumulusci.core.exceptions import TaskOptionsError
|
|
6
|
+
from cumulusci.core.tasks import BaseTask
|
|
7
|
+
from cumulusci.utils.options import CCIOptions, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DirectoryRecreator(BaseTask):
|
|
11
|
+
class Options(CCIOptions):
|
|
12
|
+
path: Path = Field(..., description="Path to the directory to recreate")
|
|
13
|
+
|
|
14
|
+
parsed_options: Options
|
|
15
|
+
|
|
16
|
+
def _init_options(self, kwargs):
|
|
17
|
+
super()._init_options(kwargs)
|
|
18
|
+
|
|
19
|
+
if os.path.isfile(self.parsed_options.path):
|
|
20
|
+
raise TaskOptionsError(f"Path {self.parsed_options.path} is a file")
|
|
21
|
+
|
|
22
|
+
def _run_task(self):
|
|
23
|
+
created = "created"
|
|
24
|
+
if os.path.exists(self.parsed_options.path):
|
|
25
|
+
shutil.rmtree(self.parsed_options.path)
|
|
26
|
+
created = "removed and created"
|
|
27
|
+
|
|
28
|
+
os.makedirs(self.parsed_options.path, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
self.logger.info(f"Directory {self.parsed_options.path} {created}.")
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from dotenv import dotenv_values
|
|
6
|
+
|
|
7
|
+
from cumulusci.core.tasks import BaseTask
|
|
8
|
+
from cumulusci.utils.options import (
|
|
9
|
+
CCIOptions,
|
|
10
|
+
Field,
|
|
11
|
+
ListOfStringsOption,
|
|
12
|
+
MappingOption,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .credentialManager import CredentialManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SecretsToEnv(BaseTask):
|
|
19
|
+
class Options(CCIOptions):
|
|
20
|
+
env_path: Path = Field("./.env", description="Path to the .env file")
|
|
21
|
+
secrets_provider: str = Field(
|
|
22
|
+
None,
|
|
23
|
+
description='Secrets provider type i.e local, ado_variables, aws_secrets. Default value is None, which will use the secrets type from the environment variable CUMULUSCI_SECRETS_TYPE if it is set, otherwise it will use the "local" provider.',
|
|
24
|
+
)
|
|
25
|
+
provider_options: MappingOption = Field({}, description="Provider options")
|
|
26
|
+
secrets: Union[ListOfStringsOption, MappingOption] = Field(
|
|
27
|
+
[],
|
|
28
|
+
description="List of secret keys to retrieve be it with a list of keys or a mapping of secret name to key. Defaults to empty list.",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
parsed_options: Options
|
|
32
|
+
|
|
33
|
+
def _init_options(self, kwargs):
|
|
34
|
+
super()._init_options(kwargs)
|
|
35
|
+
self.provider = CredentialManager.get_provider(
|
|
36
|
+
self.parsed_options.secrets_provider
|
|
37
|
+
or CredentialManager.load_secrets_type_from_environment(),
|
|
38
|
+
**self.parsed_options.provider_options,
|
|
39
|
+
)
|
|
40
|
+
self.env_values = dotenv_values(self.parsed_options.env_path)
|
|
41
|
+
|
|
42
|
+
def _init_secrets(self):
|
|
43
|
+
if (
|
|
44
|
+
isinstance(self.parsed_options.secrets, list)
|
|
45
|
+
and self.parsed_options.secrets
|
|
46
|
+
):
|
|
47
|
+
try:
|
|
48
|
+
self.secrets = MappingOption.from_str(
|
|
49
|
+
",".join(self.parsed_options.secrets)
|
|
50
|
+
)
|
|
51
|
+
except Exception:
|
|
52
|
+
self.secrets = {
|
|
53
|
+
secret: secret for secret in self.parsed_options.secrets
|
|
54
|
+
}
|
|
55
|
+
elif isinstance(self.parsed_options.secrets, dict):
|
|
56
|
+
self.secrets = self.parsed_options.secrets
|
|
57
|
+
else:
|
|
58
|
+
self.secrets = {}
|
|
59
|
+
|
|
60
|
+
def _run_task(self):
|
|
61
|
+
|
|
62
|
+
self._init_secrets()
|
|
63
|
+
|
|
64
|
+
output_file = self.parsed_options.env_path
|
|
65
|
+
|
|
66
|
+
for secret_key, secret_name in self.secrets.items():
|
|
67
|
+
if secret_key == "*":
|
|
68
|
+
self.env_values.update(
|
|
69
|
+
self._get_all_credentials(secret_key, secret_name=secret_name)
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
safe_value, _ = self._get_credential(
|
|
73
|
+
secret_key, secret_key, secret_name=secret_name
|
|
74
|
+
)
|
|
75
|
+
self.env_values[secret_key] = safe_value
|
|
76
|
+
|
|
77
|
+
# Ensure output directory exists
|
|
78
|
+
os.makedirs(os.path.dirname(output_file) or ".", exist_ok=True)
|
|
79
|
+
|
|
80
|
+
# Write env file with UTF-8 encoding to support Unicode characters
|
|
81
|
+
with open(output_file, "w", encoding="utf-8") as env_file:
|
|
82
|
+
for key, value in self.env_values.items():
|
|
83
|
+
env_file.write(f'{key}="{value}"\n')
|
|
84
|
+
|
|
85
|
+
def _get_credential(
|
|
86
|
+
self,
|
|
87
|
+
credential_key,
|
|
88
|
+
value,
|
|
89
|
+
env_key=None,
|
|
90
|
+
display_value="*****",
|
|
91
|
+
secret_name=None,
|
|
92
|
+
):
|
|
93
|
+
if env_key is None:
|
|
94
|
+
env_key = credential_key
|
|
95
|
+
|
|
96
|
+
cred_secret_value = self.provider.get_credentials(
|
|
97
|
+
credential_key, {"value": value, "secret_name": secret_name}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if cred_secret_value is None:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Failed to retrieve secret {credential_key} from {self.provider.provider_type}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
self.logger.info(
|
|
106
|
+
f"Set secrets env var from {self.provider.provider_type}: {env_key}={display_value}"
|
|
107
|
+
)
|
|
108
|
+
safe_value = cred_secret_value.replace('"', '\\"').replace("\n", "\\n")
|
|
109
|
+
return safe_value, cred_secret_value
|
|
110
|
+
|
|
111
|
+
def _get_all_credentials(
|
|
112
|
+
self, credential_key, display_value="*****", secret_name=None
|
|
113
|
+
):
|
|
114
|
+
|
|
115
|
+
cred_secret_values = self.provider.get_all_credentials(
|
|
116
|
+
credential_key, {"secret_name": secret_name}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if cred_secret_values is None:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"Failed to retrieve secret {credential_key}({secret_name}) from {self.provider.provider_type}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
dict_values = {}
|
|
125
|
+
for key, value in cred_secret_values.items():
|
|
126
|
+
self.logger.info(
|
|
127
|
+
f"Set secrets env var from {self.provider.provider_type}: {key}={display_value}"
|
|
128
|
+
)
|
|
129
|
+
safe_value = value.replace('"', '\\"').replace("\n", "\\n")
|
|
130
|
+
dict_values[key] = safe_value
|
|
131
|
+
|
|
132
|
+
return dict_values
|