Skip to content

Commit 3deceac

Browse files
authored
Improve-json-format-conversion-code (#2072)
* Move stuff in platform_helper * Use helpers and change conversion function names * Change conversion function name * Use the project conversion function * Output report in deterministic order * Add portfolio JSON format conversion * AICode fix is a setting like others * Handle "severity" like "severities" * Add conversion of settings string value into float or int or bool * Remove groups or users with no permissions in export * Remove list inlining * Remove perms field for objects with no permission * Fix export for SQC * Quality pass
1 parent d8681e0 commit 3deceac

19 files changed

+474
-298
lines changed

cli/config.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@
3232
from cli import options
3333
from sonar import exceptions, errcodes, utilities, version
3434
from sonar.util import types, constants as c
35+
from sonar.util import platform_helper as pfhelp
36+
from sonar.util import project_helper as pjhelp
37+
from sonar.util import portfolio_helper as foliohelp
38+
from sonar.util import qualityprofile_helper as qphelp
39+
from sonar.util import rule_helper as rhelp
40+
3541
import sonar.logging as log
3642
from sonar import platform, rules, qualityprofiles, qualitygates, users, groups
3743
from sonar import projects, portfolios, applications
3844
from sonar.util import component_helper
3945

4046
TOOL_NAME = "sonar-config"
4147

42-
DONT_INLINE_LISTS = "dontInlineLists"
4348
FULL_EXPORT = "fullExport"
4449
EXPORT_EMPTY = "exportEmpty"
4550

@@ -104,14 +109,6 @@ def __parse_args(desc: str) -> object:
104109
f"By default the export will show the value as '{utilities.DEFAULT}' "
105110
"and the setting will not be imported at import time",
106111
)
107-
parser.add_argument(
108-
f"--{DONT_INLINE_LISTS}",
109-
required=False,
110-
default=False,
111-
action="store_true",
112-
help="By default, sonar-config exports multi-valued settings as comma separated strings instead of arrays (if there is not comma in values). "
113-
"Set this flag if you want to force export multi valued settings as arrays",
114-
)
115112
parser.add_argument(
116113
f"--{EXPORT_EMPTY}",
117114
required=False,
@@ -210,7 +207,6 @@ def export_config(endpoint: platform.Platform, what: list[str], **kwargs) -> Non
210207
export_settings = kwargs.copy()
211208
export_settings.update(
212209
{
213-
"INLINE_LISTS": not kwargs.get(DONT_INLINE_LISTS, False),
214210
"EXPORT_DEFAULTS": True,
215211
"FULL_EXPORT": kwargs.get(FULL_EXPORT, False),
216212
"MODE": mode,
@@ -268,8 +264,6 @@ def __prep_json_for_write(json_data: types.ObjectJsonRepr, export_settings: type
268264
if not export_settings.get(EXPORT_EMPTY, False):
269265
log.debug("Removing empties")
270266
json_data = utilities.clean_data(json_data, remove_empty=True)
271-
if export_settings.get("INLINE_LISTS", True):
272-
json_data = utilities.inline_lists(json_data, exceptions=("conditions",))
273267
return json_data
274268

275269

@@ -309,22 +303,23 @@ def convert_json(**kwargs) -> dict[str, Any]:
309303
with open(kwargs["convertFrom"], encoding="utf-8") as fd:
310304
old_json = json.loads(fd.read())
311305
mapping = {
312-
"platform": platform.old_to_new_json,
313-
"globalSettings": platform.global_settings_old_to_new_json,
314-
"qualityProfiles": qualityprofiles.old_to_new_json,
315-
"qualityGates": qualitygates.old_to_new_json,
316-
"projects": projects.old_to_new_json,
317-
"portfolios": portfolios.old_to_new_json,
318-
"applications": applications.old_to_new_json,
319-
"users": users.old_to_new_json,
320-
"groups": groups.old_to_new_json,
321-
"rules": rules.old_to_new_json,
306+
"platform": pfhelp.convert_basics_json,
307+
"globalSettings": pfhelp.convert_global_settings_json,
308+
"qualityGates": qualitygates.convert_qgs_json,
309+
"qualityProfiles": qphelp.convert_qps_json,
310+
"projects": pjhelp.convert_projects_json,
311+
"portfolios": foliohelp.convert_portfolios_json,
312+
"applications": applications.convert_apps_json,
313+
"users": users.convert_users_json,
314+
"groups": groups.convert_groups_json,
315+
"rules": rhelp.convert_rules_json,
322316
}
323317
new_json = {}
324318
for k, func in mapping.items():
325319
if k in old_json:
326320
log.info("Converting %s", k)
327321
new_json[k] = func(old_json[k])
322+
new_json = __normalize_json(new_json, remove_empty=False, remove_none=True)
328323
with open(kwargs["convertTo"], mode="w", encoding="utf-8") as fd:
329324
print(utilities.json_dump(new_json), file=fd)
330325
return new_json

sonar/applications.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import sonar.utilities as util
4343
from sonar.audit import rules, problem
4444
import sonar.util.constants as c
45+
from sonar.util import common_json_helper
4546

4647
_CLASS_LOCK = Lock()
4748
_IMPORTABLE_PROPERTIES = ("key", "name", "description", "visibility", "branches", "permissions", "tags")
@@ -344,7 +345,7 @@ def export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:
344345
"tags": self.get_tags(),
345346
}
346347
)
347-
json_data = old_to_new_json_one(json_data)
348+
json_data = convert_app_json(json_data)
348349
return util.filter_export(json_data, _IMPORTABLE_PROPERTIES, export_settings.get("FULL_EXPORT", False))
349350

350351
def set_permissions(self, data: types.JsonPermissions) -> application_permissions.ApplicationPermissions:
@@ -518,7 +519,7 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, **kwarg
518519
"""
519520
check_supported(endpoint)
520521
write_q = kwargs.get("write_q", None)
521-
key_regexp = kwargs.get("key_list", ".*")
522+
key_regexp = kwargs.get("key_list", ".+")
522523

523524
app_list = {k: v for k, v in get_list(endpoint).items() if not key_regexp or re.match(key_regexp, k)}
524525
apps_settings = []
@@ -547,7 +548,7 @@ def audit(endpoint: pf.Platform, audit_settings: types.ConfigSettings, **kwargs)
547548
return []
548549
log.info("--- Auditing applications ---")
549550
problems = []
550-
key_regexp = kwargs.get("key_list", None) or ".*"
551+
key_regexp = kwargs.get("key_list", ".+")
551552
for obj in [o for o in get_list(endpoint).values() if not key_regexp or re.match(key_regexp, o.key)]:
552553
problems += obj.audit(audit_settings, **kwargs)
553554
return problems
@@ -600,19 +601,17 @@ def search_by_name(endpoint: pf.Platform, name: str) -> dict[str, Application]:
600601
return data
601602

602603

603-
def old_to_new_json_one(old_app_json: dict[str, Any]) -> dict[str, Any]:
604+
def convert_app_json(old_app_json: dict[str, Any]) -> dict[str, Any]:
604605
"""Converts sonar-config old JSON report format to new format for a single application"""
605-
new_json = old_app_json.copy()
606-
if "permissions" in old_app_json:
607-
new_json["permissions"] = util.perms_to_list(old_app_json["permissions"])
606+
new_json = common_json_helper.convert_common_fields(old_app_json.copy())
608607
if "branches" in old_app_json:
609608
new_json["branches"] = util.dict_to_list(old_app_json["branches"], "name")
610609
return new_json
611610

612611

613-
def old_to_new_json(old_json: dict[str, Any]) -> dict[str, Any]:
612+
def convert_apps_json(old_json: dict[str, Any]) -> dict[str, Any]:
614613
"""Converts sonar-config old JSON report format to new format"""
615614
new_json = old_json.copy()
616615
for k, v in new_json.items():
617-
new_json[k] = old_to_new_json_one(v)
616+
new_json[k] = convert_app_json(v)
618617
return util.dict_to_list(new_json, "key")

sonar/groups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,6 @@ def exists(endpoint: pf.Platform, name: str) -> bool:
434434
return Group.get_object(endpoint=endpoint, name=name) is not None
435435

436436

437-
def old_to_new_json(old_json: dict[str, Any]) -> dict[str, Any]:
437+
def convert_groups_json(old_json: dict[str, Any]) -> dict[str, Any]:
438438
"""Converts sonar-config old groups JSON report format to new format"""
439439
return util.dict_to_list(old_json, "name", "description")

sonar/permissions/permission_templates.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
"""Abstraction of the SonarQube permission template concept"""
2222

2323
from __future__ import annotations
24-
from typing import Optional
24+
from typing import Optional, Any
2525

2626
import json
2727
import re
2828

2929
import sonar.logging as log
3030
from sonar.util import types, cache
31+
from sonar.util import platform_helper as phelp
3132
from sonar import sqobject, utilities, exceptions
3233
from sonar.permissions import template_permissions
3334
import sonar.platform as pf
@@ -42,8 +43,6 @@
4243
_CREATE_API = "permissions/create_template"
4344
_UPDATE_API = "permissions/update_template"
4445

45-
_IMPORTABLE_PROPERTIES = ("name", "description", "pattern", "defaultFor", "permissions")
46-
4746

4847
class PermissionTemplate(sqobject.SqObject):
4948
"""Abstraction of the Sonar permission template concept"""
@@ -183,7 +182,7 @@ def to_json(self, export_settings: types.ConfigSettings = None) -> types.ObjectJ
183182
json_data.pop("pattern")
184183
json_data["creationDate"] = utilities.date_to_string(self.creation_date)
185184
json_data["lastUpdate"] = utilities.date_to_string(self.last_update)
186-
return utilities.remove_nones(utilities.filter_export(json_data, _IMPORTABLE_PROPERTIES, export_settings.get("FULL_EXPORT", False)))
185+
return phelp.convert_template_json(json_data, export_settings.get("FULL_EXPORT", False))
187186

188187
def _audit_pattern(self, audit_settings: types.ConfigSettings) -> list[pb.Problem]:
189188
log.debug("Auditing %s projectKeyPattern ('%s')", str(self), str(self.project_key_pattern))

sonar/permissions/permissions.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,14 @@ def to_json(self, perm_type: str | None = None, csv: bool = False) -> types.Json
8686
if not csv:
8787
return self.permissions.get(perm_type, {}) if is_valid(perm_type) else self.permissions
8888
perms = {}
89-
for p in normalize(perm_type):
90-
if p not in self.permissions or len(self.permissions[p]) == 0:
89+
for ptype in normalize(perm_type):
90+
for k, v in self.permissions.get(ptype, {}).copy().items():
91+
if len(v) == 0:
92+
self.permissions[ptype].pop(k)
93+
for ptype in normalize(perm_type):
94+
if ptype not in self.permissions or len(self.permissions[ptype]) == 0:
9195
continue
92-
perms[p] = {k: encode(v) for k, v in self.permissions.get(p, {}).items()}
96+
perms[ptype] = {k: encode(v) for k, v in self.permissions.get(ptype, {}).items()}
9397
return perms if len(perms) > 0 else None
9498

9599
def export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:

sonar/platform.py

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import sonar.utilities as util
4141
from sonar.util import types, update_center
4242
import sonar.util.constants as c
43+
import sonar.util.platform_helper as pfhelp
4344

4445
from sonar import errcodes, settings, devops, version, sif, exceptions, organizations
4546
from sonar.permissions import permissions, global_permissions, permission_templates
@@ -262,7 +263,7 @@ def delete(self, api: str, params: types.ApiParams = None, **kwargs) -> requests
262263
def __run_request(self, request: callable, api: str, params: types.ApiParams = None, **kwargs) -> requests.Response:
263264
"""Makes an HTTP request to SonarQube"""
264265
mute = kwargs.pop("mute", ())
265-
api = _normalize_api(api)
266+
api = pfhelp.normalize_api(api)
266267
headers = {"user-agent": self._user_agent, "accept": _APP_JSON} | kwargs.get("headers", {})
267268
params = params or {}
268269
with_org = kwargs.pop("with_organization", True)
@@ -498,21 +499,7 @@ def export(self, export_settings: types.ConfigSettings, full: bool = False) -> t
498499
if not self.is_sonarcloud():
499500
json_data[settings.DEVOPS_INTEGRATION] = devops.export(self, export_settings=export_settings)
500501

501-
# Convert dicts to lists
502-
special_categories = (settings.LANGUAGES_SETTINGS, settings.DEVOPS_INTEGRATION, "permissions", "permissionTemplates")
503-
for categ in [cat for cat in settings.CATEGORIES if cat not in special_categories]:
504-
json_data[categ] = util.sort_list_by_key(util.dict_to_list(json_data[categ], "key"), "key")
505-
for k, v in json_data[settings.LANGUAGES_SETTINGS].items():
506-
json_data[settings.LANGUAGES_SETTINGS][k] = util.sort_list_by_key(util.dict_to_list(v, "key"), "key")
507-
json_data[settings.LANGUAGES_SETTINGS] = util.dict_to_list(json_data[settings.LANGUAGES_SETTINGS], "language", "settings")
508-
json_data[settings.DEVOPS_INTEGRATION] = util.dict_to_list(json_data[settings.DEVOPS_INTEGRATION], "key")
509-
json_data["permissions"] = util.perms_to_list(json_data["permissions"])
510-
for v in json_data["permissionTemplates"].values():
511-
if "permissions" in v:
512-
v["permissions"] = util.perms_to_list(v["permissions"])
513-
json_data["permissionTemplates"] = util.dict_to_list(json_data["permissionTemplates"], "key")
514-
515-
return util.order_dict(json_data, [*settings.CATEGORIES, "permissions", "permissionTemplates"])
502+
return pfhelp.convert_global_settings_json(json_data)
516503

517504
def set_webhooks(self, webhooks_data: types.ObjectJsonRepr) -> bool:
518505
"""Sets global webhooks with a list of webhooks represented as JSON
@@ -791,19 +778,6 @@ def set_standard_experience(self) -> bool:
791778
this.context = Platform(os.getenv("SONAR_HOST_URL", "http://localhost:9000"), os.getenv("SONAR_TOKEN", ""))
792779

793780

794-
def _normalize_api(api: str) -> str:
795-
"""Normalizes an API based on its multiple original forms"""
796-
if api.startswith("/api/"):
797-
pass
798-
elif api.startswith("api/"):
799-
api = "/" + api
800-
elif api.startswith("/"):
801-
api = "/api" + api
802-
else:
803-
api = "/api/" + api
804-
return api
805-
806-
807781
def _audit_setting_value(key: str, platform_settings: dict[str, Any], audit_settings: types.ConfigSettings, url: str) -> list[Problem]:
808782
"""Audits a particular platform setting is set to expected value"""
809783
if (v := _get_multiple_values(4, audit_settings[key], "MEDIUM", "CONFIGURATION")) is None:
@@ -963,29 +937,3 @@ def audit(endpoint: Platform, audit_settings: types.ConfigSettings, **kwargs) ->
963937
pbs = endpoint.audit(audit_settings)
964938
"write_q" in kwargs and kwargs["write_q"].put(pbs)
965939
return pbs
966-
967-
968-
def old_to_new_json(old_json: dict[str, Any]) -> dict[str, Any]:
969-
"""Converts sonar-config "plaform" section old JSON report format to new format"""
970-
if "plugins" in old_json:
971-
old_json["plugins"] = util.dict_to_list(old_json["plugins"], "key")
972-
return old_json
973-
974-
975-
def global_settings_old_to_new_json(old_json: dict[str, Any]) -> dict[str, Any]:
976-
"""Converts sonar-config "globalSettings" section old JSON report format to new format"""
977-
new_json = {}
978-
special_categories = (settings.LANGUAGES_SETTINGS, settings.DEVOPS_INTEGRATION, "permissions", "permissionTemplates")
979-
for categ in [cat for cat in settings.CATEGORIES if cat not in special_categories]:
980-
new_json[categ] = util.sort_list_by_key(util.dict_to_list(old_json[categ], "key"), "key")
981-
for k, v in old_json[settings.LANGUAGES_SETTINGS].items():
982-
new_json[settings.LANGUAGES_SETTINGS] = new_json.get(settings.LANGUAGES_SETTINGS, None) or {}
983-
new_json[settings.LANGUAGES_SETTINGS][k] = util.sort_list_by_key(util.dict_to_list(v, "key"), "key")
984-
new_json[settings.LANGUAGES_SETTINGS] = util.dict_to_list(new_json[settings.LANGUAGES_SETTINGS], "language", "settings")
985-
new_json[settings.DEVOPS_INTEGRATION] = util.dict_to_list(old_json[settings.DEVOPS_INTEGRATION], "key")
986-
new_json["permissions"] = util.perms_to_list(old_json["permissions"])
987-
for v in old_json["permissionTemplates"].values():
988-
if "permissions" in v:
989-
v["permissions"] = util.perms_to_list(v["permissions"])
990-
new_json["permissionTemplates"] = util.dict_to_list(old_json["permissionTemplates"], "key")
991-
return new_json

0 commit comments

Comments
 (0)