Coverage for lynceus/devops/factory.py: 100%
46 statements
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-29 08:46 +0000
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-29 08:46 +0000
1from lynceus.core.exchange.lynceus_exchange import LynceusExchange
2from lynceus.core.lynceus import LynceusSession
3from lynceus.core.lynceus_client import LynceusClientClass
4from .devops_analyzer import DevOpsAnalyzer
5from .editor.devops_analyzer_editor import DevOpsAnalyzerEditor
6from .editor.github_devops_analyzer_editor import GithubDevOpsAnalyzerEditor
7from .editor.gitlab_devops_analyzer_editor import GitlabDevOpsAnalyzerEditor
8from .github_devops_analyzer import GithubDevOpsAnalyzer
9from .gitlab_devops_analyzer import GitlabDevOpsAnalyzer
10from ..core.config import (
11 CONFIG_AUTHENTICATION_KEY,
12 CONFIG_PROJECT_CRED_SECRET,
13 CONFIG_PROJECT_CRED_SECRET_GITHUB_DEFAULT,
14 CONFIG_PROJECT_CRED_SECRET_GITLAB_DEFAULT,
15 CONFIG_PROJECT_KEY,
16 CONFIG_PROJECT_URI,
17)
18from ..core.config.lynceus_config import LynceusConfig
21class DevOpsFactory(LynceusClientClass):
22 """
23 Factory class for creating DevOps analyzer instances.
25 This factory provides a unified interface for creating different types of DevOps analyzers
26 (GitLab, GitHub) with both read-only and editor capabilities. It automatically detects
27 the platform type based on the URI and instantiates the appropriate analyzer class.
29 Attributes:
30 DEVOPS_CLASS_MAP: Mapping of (platform, editor_mode) tuples to analyzer classes
31 """
33 # N.B.: use declaration to avoid lower performance with dynamic class loading.
34 DEVOPS_CLASS_MAP: dict[tuple[str, bool], type[DevOpsAnalyzer]] = {
35 ("gitlab", False): GitlabDevOpsAnalyzer,
36 ("gitlab", True): GitlabDevOpsAnalyzerEditor,
37 ("github", False): GithubDevOpsAnalyzer,
38 ("github", True): GithubDevOpsAnalyzerEditor,
39 }
41 def __init__(
42 self, lynceus_session: LynceusSession, lynceus_exchange: LynceusExchange | None
43 ):
44 """
45 Initialize the DevOps factory.
47 Parameters
48 ----------
49 lynceus_session : LynceusSession
50 The Lynceus session instance
51 lynceus_exchange : LynceusExchange or None
52 Exchange instance for data communication (optional)
53 """
54 super().__init__(lynceus_session, "devops", lynceus_exchange)
56 def _create_devops_analyzer_instance(
57 self, *, host: str, editor_mode: bool, uri: str, token: str
58 ) -> DevOpsAnalyzer | DevOpsAnalyzerEditor:
59 """
60 Create a specific DevOps analyzer instance based on platform and mode.
62 Parameters
63 ----------
64 host : str
65 Platform identifier ('gitlab' or 'github')
66 editor_mode : bool
67 Whether to create an editor-capable instance
68 uri : str
69 Platform URI
70 token : str
71 Authentication token
73 Returns
74 -------
75 DevOpsAnalyzer
76 Platform-specific analyzer instance
77 """
78 self._logger.debug(
79 f"Instantiating a {host.title()} implementation, according to uri '{uri}'."
80 )
81 return DevOpsFactory.DEVOPS_CLASS_MAP[(host, editor_mode)](
82 self._lynceus_session, uri, token, self._lynceus_exchange
83 )
85 def _manage_new_devops_analyzer(self, *, editor_mode: bool, uri: str, token: str) -> DevOpsAnalyzer | DevOpsAnalyzerEditor:
86 """
87 Create a new DevOps analyzer by detecting the platform from URI.
89 Parameters
90 ----------
91 editor_mode : bool
92 Whether to create an editor-capable instance
93 uri : str
94 Platform URI (used to detect platform type)
95 token : str
96 Authentication token
98 Returns
99 -------
100 DevOpsAnalyzer
101 Appropriate analyzer instance for the detected platform
103 Raises
104 ------
105 ValueError
106 If the platform cannot be determined from the URI
107 """
108 if "gitlab" in uri:
109 return self._create_devops_analyzer_instance(
110 host="gitlab", editor_mode=editor_mode, uri=uri, token=token
111 )
112 if "github" in uri:
113 return self._create_devops_analyzer_instance(
114 host="github", editor_mode=editor_mode, uri=uri, token=token
115 )
116 raise ValueError(f"Host with uri '{uri}' is not supported by DevOps feature.")
118 def new_devops_analyzer(self, uri: str, token: str) -> DevOpsAnalyzer:
119 """
120 Create a new read-only DevOps analyzer instance.
122 Parameters
123 ----------
124 uri : str
125 Platform URI
126 token : str
127 Authentication token
129 Returns
130 -------
131 DevOpsAnalyzer
132 Read-only analyzer instance
133 """
134 return self._manage_new_devops_analyzer(editor_mode=False, uri=uri, token=token)
136 def new_devops_analyzer_editor(self, uri: str, token: str) -> DevOpsAnalyzerEditor:
137 """
138 Create a new editor-capable DevOps analyzer instance.
140 Parameters
141 ----------
142 uri : str
143 Platform URI
144 token : str
145 Authentication token
147 Returns
148 -------
149 DevOpsAnalyzer
150 Editor-capable analyzer instance
151 """
152 return self._manage_new_devops_analyzer(editor_mode=True, uri=uri, token=token)
154 def get_access_token_simulating_anonymous_access(
155 self, *, uri: str, lynceus_config: LynceusConfig
156 ) -> str:
157 """
158 Get an access token for simulating anonymous access to a DevOps platform.
160 This method retrieves a default credential secret from the configuration
161 based on the platform type detected from the URI.
163 Parameters
164 ----------
165 uri : str
166 Platform URI (used to detect platform type)
167 lynceus_config : LynceusConfig
168 Configuration instance to read credentials from
170 Returns
171 -------
172 str
173 Access token for anonymous access simulation
175 Raises
176 ------
177 ValueError
178 If no appropriate credential secret is found in configuration
179 """
180 if "gitlab" in uri:
181 default_credential_secret_key: str = (
182 CONFIG_PROJECT_CRED_SECRET_GITLAB_DEFAULT
183 )
184 else:
185 default_credential_secret_key: str = (
186 CONFIG_PROJECT_CRED_SECRET_GITHUB_DEFAULT
187 )
189 default_credential_secret: str = lynceus_config.get_config(
190 CONFIG_AUTHENTICATION_KEY, default_credential_secret_key, default=None
191 )
192 if default_credential_secret:
193 self._logger.warning(
194 f'No specific "{CONFIG_PROJECT_CRED_SECRET}" in your [{CONFIG_AUTHENTICATION_KEY}] configuration,'
195 f' using the configured "{default_credential_secret_key}" one.'
196 )
197 lynceus_config[CONFIG_AUTHENTICATION_KEY][
198 CONFIG_PROJECT_CRED_SECRET
199 ] = default_credential_secret
200 return default_credential_secret
202 error_message: str = (
203 f'Unable to find "{CONFIG_PROJECT_CRED_SECRET}" in your [{CONFIG_AUTHENTICATION_KEY}] configuration,'
204 )
205 error_message += f' and there is no "{default_credential_secret_key}" configured. Update your configuration and try again.'
207 self._logger.warning(error_message)
208 raise ValueError(error_message)
210 def check_and_enhance_configuration_for_anonymous_access(
211 self, lynceus_config: LynceusConfig, *, config_key: str = CONFIG_PROJECT_KEY
212 ):
213 """
214 Check and enhance configuration for anonymous access support.
216 This method ensures that the configuration has appropriate credential secrets
217 for anonymous access. If not present, it attempts to use default credentials.
219 Parameters
220 ----------
221 lynceus_config : LynceusConfig
222 Configuration instance to check and modify
223 config_key : str, optional
224 Configuration key to check (defaults to project key)
226 Raises
227 ------
228 ValueError
229 If no appropriate credentials can be found or configured
230 """
231 uri: str = lynceus_config.get_config(config_key, CONFIG_PROJECT_URI)
232 configured_credential_secret: str = lynceus_config.get_config(
233 config_key, CONFIG_PROJECT_CRED_SECRET, default=None
234 )
235 if configured_credential_secret is None:
236 lynceus_config[config_key][CONFIG_PROJECT_CRED_SECRET] = (
237 self.get_access_token_simulating_anonymous_access(
238 uri=uri, lynceus_config=lynceus_config
239 )
240 )