Skip to content

Commit 1518c03

Browse files
authored
Merge pull request #1291 from okorach:config-import-scloud-v0.1
Config import sonarcloud alpha
2 parents cb6e123 + 47cb7e8 commit 1518c03

16 files changed

+201
-128
lines changed

cli/config.py

Lines changed: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -149,76 +149,68 @@ def __export_config(endpoint: platform.Platform, what: list[str], **kwargs) -> N
149149
if len(non_existing_projects) > 0:
150150
utilities.exit_fatal(f"Project key(s) '{','.join(non_existing_projects)}' do(es) not exist", errcodes.NO_SUCH_KEY)
151151

152+
calls = {
153+
options.WHAT_SETTINGS: [__JSON_KEY_SETTINGS, platform.export],
154+
options.WHAT_RULES: [__JSON_KEY_RULES, rules.export],
155+
options.WHAT_PROFILES: [__JSON_KEY_PROFILES, qualityprofiles.export],
156+
options.WHAT_GATES: [__JSON_KEY_GATES, qualitygates.export],
157+
options.WHAT_PROJECTS: [__JSON_KEY_PROJECTS, projects.export],
158+
options.WHAT_APPS: [__JSON_KEY_APPS, applications.export],
159+
options.WHAT_PORTFOLIOS: [__JSON_KEY_PORTFOLIOS, portfolios.export],
160+
options.WHAT_USERS: [__JSON_KEY_USERS, users.export],
161+
options.WHAT_GROUPS: [__JSON_KEY_GROUPS, groups.export],
162+
}
163+
152164
log.info("Exporting configuration from %s", kwargs[options.URL])
153165
key_list = kwargs[options.KEYS]
154166
sq_settings = {__JSON_KEY_PLATFORM: endpoint.basics()}
155-
if options.WHAT_SETTINGS in what:
156-
sq_settings[__JSON_KEY_SETTINGS] = endpoint.export(export_settings=export_settings)
157-
if options.WHAT_RULES in what or options.WHAT_PROFILES in what:
158-
sq_settings[__JSON_KEY_RULES] = rules.export(endpoint, export_settings=export_settings)
159-
if options.WHAT_PROFILES in what:
160-
sq_settings[__JSON_KEY_PROFILES] = qualityprofiles.export(endpoint, export_settings=export_settings)
161-
if options.WHAT_GATES in what:
162-
sq_settings[__JSON_KEY_GATES] = qualitygates.export(endpoint, export_settings=export_settings)
163-
if options.WHAT_PROJECTS in what:
164-
sq_settings[__JSON_KEY_PROJECTS] = projects.export(endpoint, key_list=key_list, export_settings=export_settings)
165-
if options.WHAT_APPS in what:
166-
try:
167-
sq_settings[__JSON_KEY_APPS] = applications.export(endpoint, key_list=key_list, export_settings=export_settings)
168-
except exceptions.UnsupportedOperation as e:
169-
log.warning(e.message)
170-
if options.WHAT_PORTFOLIOS in what:
167+
for what_item, call_data in calls.items():
168+
if what_item not in what:
169+
continue
170+
ndx, func = call_data
171171
try:
172-
sq_settings[__JSON_KEY_PORTFOLIOS] = portfolios.export(endpoint, key_list=key_list, export_settings=export_settings)
172+
sq_settings[ndx] = func(endpoint, export_settings=export_settings, key_list=key_list)
173173
except exceptions.UnsupportedOperation as e:
174174
log.warning(e.message)
175-
if options.WHAT_USERS in what:
176-
sq_settings[__JSON_KEY_USERS] = users.export(endpoint, export_settings=export_settings)
177-
if options.WHAT_GROUPS in what:
178-
sq_settings[__JSON_KEY_GROUPS] = groups.export(endpoint, export_settings=export_settings)
179-
180175
sq_settings = utilities.remove_empties(sq_settings)
181176
if not kwargs["dontInlineLists"]:
182177
sq_settings = utilities.inline_lists(sq_settings, exceptions=("conditions",))
183178
__write_export(sq_settings, kwargs[options.REPORT_FILE], kwargs[options.FORMAT])
184179
log.info("Exporting configuration from %s completed", kwargs["url"])
185180

186181

187-
def __import_config(endpoint: platform.Platform, what: list[str], **kwargs) -> None:
188-
"""Imports a platform configuration from a JSON file"""
189-
log.info("Importing configuration to %s", kwargs[options.URL])
190-
key_list = kwargs[options.KEYS]
182+
def __read_input_file(file: str) -> dict[str, any]:
191183
try:
192-
with open(kwargs[options.REPORT_FILE], "r", encoding="utf-8") as fd:
184+
with open(file, "r", encoding="utf-8") as fd:
193185
data = json.loads(fd.read())
194186
except FileNotFoundError as e:
195187
utilities.exit_fatal(f"OS error while reading file: {e}", exit_code=errcodes.OS_ERROR)
196-
if options.WHAT_GROUPS in what:
197-
groups.import_config(endpoint, data)
198-
if options.WHAT_USERS in what:
199-
users.import_config(endpoint, data)
200-
if options.WHAT_GATES in what:
201-
qualitygates.import_config(endpoint, data)
202-
if options.WHAT_RULES in what:
203-
rules.import_config(endpoint, data)
204-
if options.WHAT_PROFILES in what:
205-
if options.WHAT_RULES not in what:
206-
rules.import_config(endpoint, data)
207-
qualityprofiles.import_config(endpoint, data)
208-
if options.WHAT_SETTINGS in what:
209-
endpoint.import_config(data)
210-
if options.WHAT_PROJECTS in what:
211-
projects.import_config(endpoint, data, key_list=key_list)
212-
if options.WHAT_APPS in what:
213-
try:
214-
applications.import_config(endpoint, data, key_list=key_list)
215-
except exceptions.UnsupportedOperation as e:
216-
log.warning(e.message)
217-
if options.WHAT_PORTFOLIOS in what:
218-
try:
219-
portfolios.import_config(endpoint, data, key_list=key_list)
220-
except exceptions.UnsupportedOperation as e:
221-
log.warning(e.message)
188+
return data
189+
190+
191+
def __import_config(endpoint: platform.Platform, what: list[str], data: dict[str, any], **kwargs) -> None:
192+
"""Imports a platform configuration from a JSON file"""
193+
log.info("Importing configuration to %s", kwargs[options.URL])
194+
key_list = kwargs[options.KEYS]
195+
196+
calls = {
197+
options.WHAT_GROUPS: groups.import_config,
198+
options.WHAT_USERS: users.import_config,
199+
options.WHAT_GATES: qualitygates.import_config,
200+
options.WHAT_RULES: rules.import_config,
201+
options.WHAT_PROFILES: qualityprofiles.import_config,
202+
options.WHAT_SETTINGS: platform.import_config,
203+
options.WHAT_PROJECTS: projects.import_config,
204+
options.WHAT_APPS: applications.import_config,
205+
options.WHAT_PORTFOLIOS: portfolios.import_config,
206+
}
207+
208+
for what_item, func in calls.items():
209+
if what_item in what:
210+
try:
211+
func(endpoint, data, key_list=key_list)
212+
except exceptions.UnsupportedOperation as e:
213+
log.warning(e.message)
222214
log.info("Importing configuration to %s completed", kwargs[options.URL])
223215

224216

@@ -235,6 +227,8 @@ def main() -> None:
235227
utilities.exit_fatal(f"One of --{options.EXPORT} or --{options.IMPORT} option must be chosen", exit_code=errcodes.ARGS_ERROR)
236228

237229
what = utilities.check_what(kwargs.pop(options.WHAT, None), _EVERYTHING, "exported or imported")
230+
if options.WHAT_PROFILES in what and options.WHAT_RULES not in what:
231+
what.append(options.WHAT_RULES)
238232
kwargs[options.FORMAT] = utilities.deduct_format(kwargs[options.FORMAT], kwargs[options.REPORT_FILE], allowed_formats=("json", "yaml"))
239233
if kwargs[options.EXPORT]:
240234
try:
@@ -246,7 +240,7 @@ def main() -> None:
246240
if kwargs["import"]:
247241
if kwargs["file"] is None:
248242
utilities.exit_fatal("--file is mandatory to import configuration", errcodes.ARGS_ERROR)
249-
__import_config(endpoint, what, **kwargs)
243+
__import_config(endpoint, what, __read_input_file(kwargs[options.REPORT_FILE]), **kwargs)
250244
utilities.stop_clock(start_time)
251245
sys.exit(0)
252246

sonar/applications.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,10 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_lis
498498
"""Exports applications as JSON
499499
500500
:param Platform endpoint: Reference to the Sonar platform
501+
:param ConfigSetting export_settings: Options to use for export
501502
:param KeyList key_list: list of Application keys to export, defaults to all if None
502-
:param full: Whether to export all attributes, including those that can't be set, defaults to False
503-
:type full: bool
504503
:return: Dict of applications settings
505-
:rtype: dict
504+
:rtype: ObjectJsonRepr
506505
"""
507506
if endpoint.is_sonarcloud():
508507
# log.info("Applications do not exist in SonarCloud, export skipped")

sonar/devops.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ def get_list(endpoint: platform.Platform) -> dict[str, DevopsPlatform]:
209209
:return: List of DevOps platforms
210210
:rtype: dict{<platformKey>: <DevopsPlatform>}
211211
"""
212+
if endpoint.is_sonarcloud():
213+
raise exceptions.UnsupportedOperation("Can't get list of DevOps platforms on SonarCloud")
212214
if endpoint.edition() == "community":
213215
return _OBJECTS
214216
data = json.loads(endpoint.get(APIS["list"]).text)
@@ -253,12 +255,14 @@ def export(endpoint: platform.Platform, export_settings: types.ConfigSettings) -
253255
return json_data
254256

255257

256-
def import_config(endpoint: platform.Platform, config_data: types.ObjectJsonRepr) -> None:
258+
def import_config(endpoint: platform.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None:
257259
"""Imports DevOps platform configuration in SonarQube/Cloud"""
258260
devops_settings = config_data.get("devopsIntegration", {})
259261
if len(devops_settings) == 0:
260262
log.info("No devops integration settings in config, skipping import...")
261263
return
264+
if endpoint.is_sonarcloud():
265+
raise exceptions.UnsupportedOperation("Can't get import DevOps platforms in SonarCloud")
262266
log.info("Importing DevOps config %s", util.json_dump(devops_settings))
263267
if len(_OBJECTS) == 0:
264268
get_list(endpoint)

sonar/groups.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,16 @@ def get_list(endpoint: pf.Platform) -> dict[str, Group]:
256256
return search(endpoint)
257257

258258

259-
def export(endpoint: pf.Platform, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:
260-
"""Exports all groups configuration as dict
261-
Default groups (sonar-users) are not exported
262-
263-
:param endpoint: reference to the SonarQube platform
264-
:type endpoint: pf.Platform
265-
:return: list of groups
266-
:rtype: dict{name: description}
259+
def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr:
260+
"""Exports groups representation in JSON
261+
262+
:param Platform endpoint: reference to the SonarQube platform
263+
:param ConfigSettings export_settings: Export parameters
264+
:param KeyList key_list: List of project keys to export, defaults to None (all projects)
265+
:return: list of groups settings
266+
:rtype: ObjectJsonRepr
267267
"""
268+
268269
log.info("Exporting groups")
269270
g_list = {}
270271
for g_name, g_obj in sorted(search(endpoint=endpoint).items()):
@@ -337,7 +338,7 @@ def create_or_update(endpoint: pf.Platform, name: str, description: str) -> Grou
337338
return Group.create(endpoint, name, description)
338339

339340

340-
def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr) -> None:
341+
def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None:
341342
"""Imports a group configuration in SonarQube
342343
343344
:param Platform endpoint: reference to the SonarQube platform

sonar/permissions/permission_templates.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from __future__ import annotations
2222

2323
import json
24+
from requests.exceptions import HTTPError
2425

2526
import sonar.logging as log
2627
from sonar.util import types
@@ -136,7 +137,10 @@ def set_as_default(self, what_list: list[str]) -> None:
136137
if (ed == "community" and qual in ("VW", "APP")) or (ed == "developer" and qual == "VW"):
137138
log.warning("Can't set permission template as default for %s on a %s edition", qual, ed)
138139
continue
139-
self.post("permissions/set_default_template", params={"templateId": self.key, "qualifier": qual})
140+
try:
141+
self.post("permissions/set_default_template", params={"templateId": self.key, "qualifier": qual})
142+
except HTTPError as e:
143+
log.error("HTTP Error: %s", utilities.sonar_error(e.response))
140144

141145
def set_pattern(self, pattern: str) -> PermissionTemplate:
142146
"""Sets a permission template pattern"""

sonar/permissions/permissions.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
from typing import Optional
2525

2626
import json
27-
from http import HTTPStatus
2827
from abc import ABC, abstractmethod
28+
from http import HTTPStatus
29+
from requests.exceptions import HTTPError
2930

3031
import sonar.logging as log
3132
from sonar import utilities, errcodes
@@ -258,7 +259,10 @@ def _post_api(self, api: str, set_field: str, perms_dict: types.JsonPermissions,
258259
filtered_perms = self._filter_permissions_for_edition(perms)
259260
for p in filtered_perms:
260261
params["permission"] = p
261-
r = self.endpoint.post(api, params=params)
262+
try:
263+
r = self.endpoint.post(api, params=params)
264+
except HTTPError as e:
265+
log.error("HTTP Error: %s", utilities.sonar_error(e.response))
262266
result = result and r.ok
263267
return result
264268

sonar/permissions/quality_permissions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ def _set_perms(self, new_perms, apis, field, diff_func, **kwargs):
101101

102102
def _read_perms(self, apis, field, **kwargs):
103103
self.permissions = {p: [] for p in permissions.PERMISSION_TYPES}
104-
if self.concerned_object.is_built_in:
104+
if self.endpoint.is_sonarcloud():
105+
log.debug("No permissions for %s because it's SonarCloud", str(self))
106+
elif self.concerned_object.is_built_in:
105107
log.debug("No permissions for %s because it's built-in", str(self))
106108
else:
107109
for p in permissions.PERMISSION_TYPES:

sonar/permissions/qualitygate_permissions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ def __str__(self):
3838
return f"permissions of {str(self.concerned_object)}"
3939

4040
def read(self):
41-
if self.endpoint.version() < (9, 2, 0):
41+
if not self.endpoint.is_sonarcloud() and self.endpoint.version() < (9, 2, 0):
4242
log.debug("Can't read %s on SonarQube < 9.2", str(self))
4343
return self
4444
self._read_perms(QualityGatePermissions.APIS, QualityGatePermissions.API_GET_FIELD, gateName=self.concerned_object.name)
4545
return self
4646

4747
def set(self, new_perms):
48-
if self.endpoint.version() < (9, 2, 0):
48+
if not self.endpoint.is_sonarcloud() and self.endpoint.version() < (9, 2, 0):
4949
raise exceptions.UnsupportedOperation(f"Can't set {str(self)} on SonarQube < 9.2")
5050

5151
return self._set_perms(

sonar/platform.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,10 +460,16 @@ def import_config(self, config_data: types.ObjectJsonRepr) -> None:
460460

461461
if settings.NEW_CODE_PERIOD in config_data["generalSettings"]:
462462
(nc_type, nc_val) = settings.decode(settings.NEW_CODE_PERIOD, config_data["generalSettings"][settings.NEW_CODE_PERIOD])
463-
settings.set_new_code_period(self, nc_type, nc_val)
463+
try:
464+
settings.set_new_code_period(self, nc_type, nc_val)
465+
except exceptions.UnsupportedOperation as e:
466+
log.error(e.message)
464467
permission_templates.import_config(self, config_data)
465468
global_permissions.import_config(self, config_data)
466-
devops.import_config(self, config_data)
469+
try:
470+
devops.import_config(self, config_data)
471+
except exceptions.UnsupportedOperation as e:
472+
log.warning(e.message)
467473

468474
def audit(self, audit_settings: types.ConfigSettings) -> list[Problem]:
469475
"""Audits a global platform configuration and returns the list of problems found
@@ -813,6 +819,17 @@ def latest() -> tuple[int, int, int]:
813819
return __lta_and_latest()[1]
814820

815821

822+
def import_config(endpoint: Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None:
823+
"""Imports a configuration in SonarQube
824+
825+
:param Platform endpoint: reference to the SonarQube platform
826+
:param ObjectJsonRepr config_data: the configuration to import
827+
:param KeyList key_list: Unused
828+
:return: Nothing
829+
"""
830+
endpoint.import_config(config_data)
831+
832+
816833
def _check_for_retry(response: requests.models.Response) -> tuple[bool, str]:
817834
"""Verifies if a response had a 301 Moved permanently and if so provide the new location"""
818835
if len(response.history) > 0 and response.history[0].status_code == HTTPStatus.MOVED_PERMANENTLY:
@@ -836,3 +853,15 @@ def convert_for_yaml(original_json: types.ObjectJsonRepr) -> types.ObjectJsonRep
836853
if "devopsIntegration" in original_json:
837854
original_json["devopsIntegration"] = util.dict_to_list(original_json["devopsIntegration"], "name")
838855
return original_json
856+
857+
858+
def export(endpoint: Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr:
859+
"""Exports all or a list of projects configuration as dict
860+
861+
:param Platform endpoint: reference to the SonarQube platform
862+
:param ConfigSettings export_settings: Export parameters
863+
:param KeyList key_list: Unused
864+
:return: Platform settings
865+
:rtype: ObjectJsonRepr
866+
"""
867+
return endpoint.export(export_settings)

sonar/portfolios.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -728,10 +728,10 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_lis
728728
"""Exports portfolios as JSON
729729
730730
:param Platform endpoint: Reference to the SonarQube platform
731-
:param KeyList key_list: list of portfoliios keys to export as csv or list, defaults to all if None
732731
:param ConfigSetting export_settings: Options to use for export
733-
:return: Dict of applications settings
734-
:rtype: dict
732+
:param KeyList key_list: list of portfoliios keys to export as csv or list, defaults to all if None
733+
:return: Dict of portfolio settings
734+
:rtype: ObjectJsonRepr
735735
"""
736736
check_supported(endpoint)
737737

0 commit comments

Comments
 (0)