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

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 

19 

20 

21class DevOpsFactory(LynceusClientClass): 

22 """ 

23 Factory class for creating DevOps analyzer instances. 

24 

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. 

28 

29 Attributes: 

30 DEVOPS_CLASS_MAP: Mapping of (platform, editor_mode) tuples to analyzer classes 

31 """ 

32 

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 } 

40 

41 def __init__( 

42 self, lynceus_session: LynceusSession, lynceus_exchange: LynceusExchange | None 

43 ): 

44 """ 

45 Initialize the DevOps factory. 

46 

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) 

55 

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. 

61 

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 

72 

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 ) 

84 

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. 

88 

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 

97 

98 Returns 

99 ------- 

100 DevOpsAnalyzer 

101 Appropriate analyzer instance for the detected platform 

102 

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.") 

117 

118 def new_devops_analyzer(self, uri: str, token: str) -> DevOpsAnalyzer: 

119 """ 

120 Create a new read-only DevOps analyzer instance. 

121 

122 Parameters 

123 ---------- 

124 uri : str 

125 Platform URI 

126 token : str 

127 Authentication token 

128 

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) 

135 

136 def new_devops_analyzer_editor(self, uri: str, token: str) -> DevOpsAnalyzerEditor: 

137 """ 

138 Create a new editor-capable DevOps analyzer instance. 

139 

140 Parameters 

141 ---------- 

142 uri : str 

143 Platform URI 

144 token : str 

145 Authentication token 

146 

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) 

153 

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. 

159 

160 This method retrieves a default credential secret from the configuration 

161 based on the platform type detected from the URI. 

162 

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 

169 

170 Returns 

171 ------- 

172 str 

173 Access token for anonymous access simulation 

174 

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 ) 

188 

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 

201 

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.' 

206 

207 self._logger.warning(error_message) 

208 raise ValueError(error_message) 

209 

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. 

215 

216 This method ensures that the configuration has appropriate credential secrets 

217 for anonymous access. If not present, it attempts to use default credentials. 

218 

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) 

225 

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 )