diff --git a/cli/config.py b/cli/config.py index bf3432d57..7c5b89de1 100644 --- a/cli/config.py +++ b/cli/config.py @@ -149,34 +149,29 @@ def __export_config(endpoint: platform.Platform, what: list[str], **kwargs) -> N if len(non_existing_projects) > 0: utilities.exit_fatal(f"Project key(s) '{','.join(non_existing_projects)}' do(es) not exist", errcodes.NO_SUCH_KEY) + calls = { + options.WHAT_SETTINGS: [__JSON_KEY_SETTINGS, platform.export], + options.WHAT_RULES: [__JSON_KEY_RULES, rules.export], + options.WHAT_PROFILES: [__JSON_KEY_PROFILES, qualityprofiles.export], + options.WHAT_GATES: [__JSON_KEY_GATES, qualitygates.export], + options.WHAT_PROJECTS: [__JSON_KEY_PROJECTS, projects.export], + options.WHAT_APPS: [__JSON_KEY_APPS, applications.export], + options.WHAT_PORTFOLIOS: [__JSON_KEY_PORTFOLIOS, portfolios.export], + options.WHAT_USERS: [__JSON_KEY_USERS, users.export], + options.WHAT_GROUPS: [__JSON_KEY_GROUPS, groups.export], + } + log.info("Exporting configuration from %s", kwargs[options.URL]) key_list = kwargs[options.KEYS] sq_settings = {__JSON_KEY_PLATFORM: endpoint.basics()} - if options.WHAT_SETTINGS in what: - sq_settings[__JSON_KEY_SETTINGS] = endpoint.export(export_settings=export_settings) - if options.WHAT_RULES in what or options.WHAT_PROFILES in what: - sq_settings[__JSON_KEY_RULES] = rules.export(endpoint, export_settings=export_settings) - if options.WHAT_PROFILES in what: - sq_settings[__JSON_KEY_PROFILES] = qualityprofiles.export(endpoint, export_settings=export_settings) - if options.WHAT_GATES in what: - sq_settings[__JSON_KEY_GATES] = qualitygates.export(endpoint, export_settings=export_settings) - if options.WHAT_PROJECTS in what: - sq_settings[__JSON_KEY_PROJECTS] = projects.export(endpoint, key_list=key_list, export_settings=export_settings) - if options.WHAT_APPS in what: - try: - sq_settings[__JSON_KEY_APPS] = applications.export(endpoint, key_list=key_list, export_settings=export_settings) - except exceptions.UnsupportedOperation as e: - log.warning(e.message) - if options.WHAT_PORTFOLIOS in what: + for what_item, call_data in calls.items(): + if what_item not in what: + continue + ndx, func = call_data try: - sq_settings[__JSON_KEY_PORTFOLIOS] = portfolios.export(endpoint, key_list=key_list, export_settings=export_settings) + sq_settings[ndx] = func(endpoint, export_settings=export_settings, key_list=key_list) except exceptions.UnsupportedOperation as e: log.warning(e.message) - if options.WHAT_USERS in what: - sq_settings[__JSON_KEY_USERS] = users.export(endpoint, export_settings=export_settings) - if options.WHAT_GROUPS in what: - sq_settings[__JSON_KEY_GROUPS] = groups.export(endpoint, export_settings=export_settings) - sq_settings = utilities.remove_empties(sq_settings) if not kwargs["dontInlineLists"]: sq_settings = utilities.inline_lists(sq_settings, exceptions=("conditions",)) @@ -184,41 +179,38 @@ def __export_config(endpoint: platform.Platform, what: list[str], **kwargs) -> N log.info("Exporting configuration from %s completed", kwargs["url"]) -def __import_config(endpoint: platform.Platform, what: list[str], **kwargs) -> None: - """Imports a platform configuration from a JSON file""" - log.info("Importing configuration to %s", kwargs[options.URL]) - key_list = kwargs[options.KEYS] +def __read_input_file(file: str) -> dict[str, any]: try: - with open(kwargs[options.REPORT_FILE], "r", encoding="utf-8") as fd: + with open(file, "r", encoding="utf-8") as fd: data = json.loads(fd.read()) except FileNotFoundError as e: utilities.exit_fatal(f"OS error while reading file: {e}", exit_code=errcodes.OS_ERROR) - if options.WHAT_GROUPS in what: - groups.import_config(endpoint, data) - if options.WHAT_USERS in what: - users.import_config(endpoint, data) - if options.WHAT_GATES in what: - qualitygates.import_config(endpoint, data) - if options.WHAT_RULES in what: - rules.import_config(endpoint, data) - if options.WHAT_PROFILES in what: - if options.WHAT_RULES not in what: - rules.import_config(endpoint, data) - qualityprofiles.import_config(endpoint, data) - if options.WHAT_SETTINGS in what: - endpoint.import_config(data) - if options.WHAT_PROJECTS in what: - projects.import_config(endpoint, data, key_list=key_list) - if options.WHAT_APPS in what: - try: - applications.import_config(endpoint, data, key_list=key_list) - except exceptions.UnsupportedOperation as e: - log.warning(e.message) - if options.WHAT_PORTFOLIOS in what: - try: - portfolios.import_config(endpoint, data, key_list=key_list) - except exceptions.UnsupportedOperation as e: - log.warning(e.message) + return data + + +def __import_config(endpoint: platform.Platform, what: list[str], data: dict[str, any], **kwargs) -> None: + """Imports a platform configuration from a JSON file""" + log.info("Importing configuration to %s", kwargs[options.URL]) + key_list = kwargs[options.KEYS] + + calls = { + options.WHAT_GROUPS: groups.import_config, + options.WHAT_USERS: users.import_config, + options.WHAT_GATES: qualitygates.import_config, + options.WHAT_RULES: rules.import_config, + options.WHAT_PROFILES: qualityprofiles.import_config, + options.WHAT_SETTINGS: platform.import_config, + options.WHAT_PROJECTS: projects.import_config, + options.WHAT_APPS: applications.import_config, + options.WHAT_PORTFOLIOS: portfolios.import_config, + } + + for what_item, func in calls.items(): + if what_item in what: + try: + func(endpoint, data, key_list=key_list) + except exceptions.UnsupportedOperation as e: + log.warning(e.message) log.info("Importing configuration to %s completed", kwargs[options.URL]) @@ -235,6 +227,8 @@ def main() -> None: utilities.exit_fatal(f"One of --{options.EXPORT} or --{options.IMPORT} option must be chosen", exit_code=errcodes.ARGS_ERROR) what = utilities.check_what(kwargs.pop(options.WHAT, None), _EVERYTHING, "exported or imported") + if options.WHAT_PROFILES in what and options.WHAT_RULES not in what: + what.append(options.WHAT_RULES) kwargs[options.FORMAT] = utilities.deduct_format(kwargs[options.FORMAT], kwargs[options.REPORT_FILE], allowed_formats=("json", "yaml")) if kwargs[options.EXPORT]: try: @@ -246,7 +240,7 @@ def main() -> None: if kwargs["import"]: if kwargs["file"] is None: utilities.exit_fatal("--file is mandatory to import configuration", errcodes.ARGS_ERROR) - __import_config(endpoint, what, **kwargs) + __import_config(endpoint, what, __read_input_file(kwargs[options.REPORT_FILE]), **kwargs) utilities.stop_clock(start_time) sys.exit(0) diff --git a/sonar/applications.py b/sonar/applications.py index fcc8d02b7..2b9d9039f 100644 --- a/sonar/applications.py +++ b/sonar/applications.py @@ -498,11 +498,10 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_lis """Exports applications as JSON :param Platform endpoint: Reference to the Sonar platform + :param ConfigSetting export_settings: Options to use for export :param KeyList key_list: list of Application keys to export, defaults to all if None - :param full: Whether to export all attributes, including those that can't be set, defaults to False - :type full: bool :return: Dict of applications settings - :rtype: dict + :rtype: ObjectJsonRepr """ if endpoint.is_sonarcloud(): # log.info("Applications do not exist in SonarCloud, export skipped") diff --git a/sonar/devops.py b/sonar/devops.py index 93c662ae8..61cb20711 100644 --- a/sonar/devops.py +++ b/sonar/devops.py @@ -209,6 +209,8 @@ def get_list(endpoint: platform.Platform) -> dict[str, DevopsPlatform]: :return: List of DevOps platforms :rtype: dict{: } """ + if endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't get list of DevOps platforms on SonarCloud") if endpoint.edition() == "community": return _OBJECTS data = json.loads(endpoint.get(APIS["list"]).text) @@ -253,12 +255,14 @@ def export(endpoint: platform.Platform, export_settings: types.ConfigSettings) - return json_data -def import_config(endpoint: platform.Platform, config_data: types.ObjectJsonRepr) -> None: +def import_config(endpoint: platform.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None: """Imports DevOps platform configuration in SonarQube/Cloud""" devops_settings = config_data.get("devopsIntegration", {}) if len(devops_settings) == 0: log.info("No devops integration settings in config, skipping import...") return + if endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't get import DevOps platforms in SonarCloud") log.info("Importing DevOps config %s", util.json_dump(devops_settings)) if len(_OBJECTS) == 0: get_list(endpoint) diff --git a/sonar/groups.py b/sonar/groups.py index c4f88ca20..7c597b3b6 100644 --- a/sonar/groups.py +++ b/sonar/groups.py @@ -256,15 +256,16 @@ def get_list(endpoint: pf.Platform) -> dict[str, Group]: return search(endpoint) -def export(endpoint: pf.Platform, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr: - """Exports all groups configuration as dict - Default groups (sonar-users) are not exported - - :param endpoint: reference to the SonarQube platform - :type endpoint: pf.Platform - :return: list of groups - :rtype: dict{name: description} +def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr: + """Exports groups representation in JSON + + :param Platform endpoint: reference to the SonarQube platform + :param ConfigSettings export_settings: Export parameters + :param KeyList key_list: List of project keys to export, defaults to None (all projects) + :return: list of groups settings + :rtype: ObjectJsonRepr """ + log.info("Exporting groups") g_list = {} 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 return Group.create(endpoint, name, description) -def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr) -> None: +def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None: """Imports a group configuration in SonarQube :param Platform endpoint: reference to the SonarQube platform diff --git a/sonar/permissions/permission_templates.py b/sonar/permissions/permission_templates.py index 3d12d4b53..1b9875f2e 100644 --- a/sonar/permissions/permission_templates.py +++ b/sonar/permissions/permission_templates.py @@ -21,6 +21,7 @@ from __future__ import annotations import json +from requests.exceptions import HTTPError import sonar.logging as log from sonar.util import types @@ -136,7 +137,10 @@ def set_as_default(self, what_list: list[str]) -> None: if (ed == "community" and qual in ("VW", "APP")) or (ed == "developer" and qual == "VW"): log.warning("Can't set permission template as default for %s on a %s edition", qual, ed) continue - self.post("permissions/set_default_template", params={"templateId": self.key, "qualifier": qual}) + try: + self.post("permissions/set_default_template", params={"templateId": self.key, "qualifier": qual}) + except HTTPError as e: + log.error("HTTP Error: %s", utilities.sonar_error(e.response)) def set_pattern(self, pattern: str) -> PermissionTemplate: """Sets a permission template pattern""" diff --git a/sonar/permissions/permissions.py b/sonar/permissions/permissions.py index 814612093..c1f0de29f 100644 --- a/sonar/permissions/permissions.py +++ b/sonar/permissions/permissions.py @@ -24,8 +24,9 @@ from typing import Optional import json -from http import HTTPStatus from abc import ABC, abstractmethod +from http import HTTPStatus +from requests.exceptions import HTTPError import sonar.logging as log from sonar import utilities, errcodes @@ -258,7 +259,10 @@ def _post_api(self, api: str, set_field: str, perms_dict: types.JsonPermissions, filtered_perms = self._filter_permissions_for_edition(perms) for p in filtered_perms: params["permission"] = p - r = self.endpoint.post(api, params=params) + try: + r = self.endpoint.post(api, params=params) + except HTTPError as e: + log.error("HTTP Error: %s", utilities.sonar_error(e.response)) result = result and r.ok return result diff --git a/sonar/permissions/quality_permissions.py b/sonar/permissions/quality_permissions.py index 9c3d18210..f1ccda54a 100644 --- a/sonar/permissions/quality_permissions.py +++ b/sonar/permissions/quality_permissions.py @@ -101,7 +101,9 @@ def _set_perms(self, new_perms, apis, field, diff_func, **kwargs): def _read_perms(self, apis, field, **kwargs): self.permissions = {p: [] for p in permissions.PERMISSION_TYPES} - if self.concerned_object.is_built_in: + if self.endpoint.is_sonarcloud(): + log.debug("No permissions for %s because it's SonarCloud", str(self)) + elif self.concerned_object.is_built_in: log.debug("No permissions for %s because it's built-in", str(self)) else: for p in permissions.PERMISSION_TYPES: diff --git a/sonar/permissions/qualitygate_permissions.py b/sonar/permissions/qualitygate_permissions.py index e71b469d3..2ff13f5ee 100644 --- a/sonar/permissions/qualitygate_permissions.py +++ b/sonar/permissions/qualitygate_permissions.py @@ -38,14 +38,14 @@ def __str__(self): return f"permissions of {str(self.concerned_object)}" def read(self): - if self.endpoint.version() < (9, 2, 0): + if not self.endpoint.is_sonarcloud() and self.endpoint.version() < (9, 2, 0): log.debug("Can't read %s on SonarQube < 9.2", str(self)) return self self._read_perms(QualityGatePermissions.APIS, QualityGatePermissions.API_GET_FIELD, gateName=self.concerned_object.name) return self def set(self, new_perms): - if self.endpoint.version() < (9, 2, 0): + if not self.endpoint.is_sonarcloud() and self.endpoint.version() < (9, 2, 0): raise exceptions.UnsupportedOperation(f"Can't set {str(self)} on SonarQube < 9.2") return self._set_perms( diff --git a/sonar/platform.py b/sonar/platform.py index 94b00d581..6a3b504ce 100644 --- a/sonar/platform.py +++ b/sonar/platform.py @@ -460,10 +460,16 @@ def import_config(self, config_data: types.ObjectJsonRepr) -> None: if settings.NEW_CODE_PERIOD in config_data["generalSettings"]: (nc_type, nc_val) = settings.decode(settings.NEW_CODE_PERIOD, config_data["generalSettings"][settings.NEW_CODE_PERIOD]) - settings.set_new_code_period(self, nc_type, nc_val) + try: + settings.set_new_code_period(self, nc_type, nc_val) + except exceptions.UnsupportedOperation as e: + log.error(e.message) permission_templates.import_config(self, config_data) global_permissions.import_config(self, config_data) - devops.import_config(self, config_data) + try: + devops.import_config(self, config_data) + except exceptions.UnsupportedOperation as e: + log.warning(e.message) def audit(self, audit_settings: types.ConfigSettings) -> list[Problem]: """Audits a global platform configuration and returns the list of problems found @@ -813,6 +819,17 @@ def latest() -> tuple[int, int, int]: return __lta_and_latest()[1] +def import_config(endpoint: Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None: + """Imports a configuration in SonarQube + + :param Platform endpoint: reference to the SonarQube platform + :param ObjectJsonRepr config_data: the configuration to import + :param KeyList key_list: Unused + :return: Nothing + """ + endpoint.import_config(config_data) + + def _check_for_retry(response: requests.models.Response) -> tuple[bool, str]: """Verifies if a response had a 301 Moved permanently and if so provide the new location""" 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 if "devopsIntegration" in original_json: original_json["devopsIntegration"] = util.dict_to_list(original_json["devopsIntegration"], "name") return original_json + + +def export(endpoint: Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr: + """Exports all or a list of projects configuration as dict + + :param Platform endpoint: reference to the SonarQube platform + :param ConfigSettings export_settings: Export parameters + :param KeyList key_list: Unused + :return: Platform settings + :rtype: ObjectJsonRepr + """ + return endpoint.export(export_settings) diff --git a/sonar/portfolios.py b/sonar/portfolios.py index 3cdc64927..2a9962ba1 100644 --- a/sonar/portfolios.py +++ b/sonar/portfolios.py @@ -728,10 +728,10 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_lis """Exports portfolios as JSON :param Platform endpoint: Reference to the SonarQube platform - :param KeyList key_list: list of portfoliios keys to export as csv or list, defaults to all if None :param ConfigSetting export_settings: Options to use for export - :return: Dict of applications settings - :rtype: dict + :param KeyList key_list: list of portfoliios keys to export as csv or list, defaults to all if None + :return: Dict of portfolio settings + :rtype: ObjectJsonRepr """ check_supported(endpoint) diff --git a/sonar/projects.py b/sonar/projects.py index 03fb198c2..baedd41ed 100644 --- a/sonar/projects.py +++ b/sonar/projects.py @@ -1368,8 +1368,8 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_lis :param Platform endpoint: reference to the SonarQube platform :param ConfigSettings export_settings: Export parameters :param KeyList key_list: List of project keys to export, defaults to None (all projects) - :return: list of projects - :rtype: dict{key: Project} + :return: list of projects settings + :rtype: ObjectJsonRepr """ for qp in qualityprofiles.get_list(endpoint).values(): qp.projects() diff --git a/sonar/qualitygates.py b/sonar/qualitygates.py index 8f1eb155e..43a65e764 100644 --- a/sonar/qualitygates.py +++ b/sonar/qualitygates.py @@ -234,7 +234,10 @@ def set_conditions(self, conditions_list: list[str]) -> bool: return False self.clear_conditions() log.debug("Setting conditions of %s", str(self)) - params = {"gateName": self.name} + if self.endpoint.is_sonarcloud(): + params = {"gateId": self.key} + else: + params = {"gateName": self.name} ok = True for cond in conditions_list: (params["metric"], params["op"], params["error"]) = _decode_condition(cond) @@ -265,7 +268,10 @@ def set_as_default(self) -> bool: :return: Whether setting as default quality gate was successful :rtype: bool """ - r = self.post("qualitygates/set_as_default", params={"name": self.name}) + if self.endpoint.is_sonarcloud(): + r = self.post("qualitygates/set_as_default", params={"id": self.key}) + else: + r = self.post("qualitygates/set_as_default", params={"name": self.name}) if r.ok: self.is_default = True # Turn off default for all other quality gates except the current one @@ -374,16 +380,20 @@ def get_list(endpoint: pf.Platform) -> dict[str, QualityGate]: return qg_list -def export(endpoint: pf.Platform, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr: - """ - :return: The list of quality gates in their JSON representation - :rtype: dict +def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr: + """Exports quality gates as JSON + + :param Platform endpoint: Reference to the Sonar platform + :param ConfigSetting export_settings: Options to use for export + :param KeyList key_list: Unused + :return: Quality gates representations as JSON + :rtype: ObjectJsonRepr """ log.info("Exporting quality gates") return {k: qg.to_json(export_settings) for k, qg in sorted(get_list(endpoint).items())} -def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr) -> bool: +def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> bool: """Imports quality gates in a SonarQube platform, fom sonar-config data Quality gates already existing are updates with the provided configuration @@ -400,7 +410,9 @@ def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr) -> b for name, data in config_data["qualityGates"].items(): try: o = QualityGate.get_object(endpoint, name) + log.debug("Found existing %s", str(o)) except exceptions.ObjectNotFound: + log.debug("QG %s not found, creating it", name) o = QualityGate.create(endpoint, name) ok = ok and o.update(**data) return ok diff --git a/sonar/qualityprofiles.py b/sonar/qualityprofiles.py index 846b36be9..7775c3d6e 100644 --- a/sonar/qualityprofiles.py +++ b/sonar/qualityprofiles.py @@ -579,14 +579,14 @@ def hierarchize(qp_list: dict[str, str], endpoint: pf.Platform) -> types.ObjectJ return qp_list -def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, in_hierarchy: bool = True) -> types.ObjectJsonRepr: - """Exports all quality profiles configuration as dict +def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr: + """Exports all or a list of quality profiles configuration as dict :param Platform endpoint: reference to the SonarQube platform - :param dict export_settings: Export settings - :param bool in_hierarchy: Whether quality profiles dict should be organized hierarchically (following inheritance) - :return: dict structure of all quality profiles - :rtype: dict + :param ConfigSettings export_settings: Export parameters + :param KeyList key_list: Unused + :return: Dict of quality profiles JSON representation + :rtype: ObjectJsonRepr """ log.info("Exporting quality profiles") qp_list = {} @@ -598,8 +598,7 @@ def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, in_hier if lang not in qp_list: qp_list[lang] = {} qp_list[lang][name] = json_data - if in_hierarchy: - qp_list = hierarchize(qp_list, endpoint) + qp_list = hierarchize(qp_list, endpoint) return dict(sorted(qp_list.items())) @@ -647,7 +646,7 @@ def __import_thread(queue: Queue) -> None: queue.task_done() -def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, threads: int = 8) -> None: +def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None: """Imports a configuration in SonarQube :param Platform endpoint: reference to the SonarQube platform @@ -656,6 +655,7 @@ def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, thre :type threads: int :return: Nothing """ + threads = 8 if "qualityProfiles" not in config_data: log.info("No quality profiles to import") return diff --git a/sonar/rules.py b/sonar/rules.py index 8ad6179c2..ebe8ab2eb 100644 --- a/sonar/rules.py +++ b/sonar/rules.py @@ -90,6 +90,8 @@ def get_object(cls, endpoint: platform.Platform, key: str) -> Rule: @classmethod def create(cls, endpoint: platform.Platform, key: str, **kwargs) -> Optional[Rule]: """Creates a rule object""" + if endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't create or extend rules on SonarCloud") params = kwargs.copy() (_, params["customKey"]) = key.split(":") log.debug("Creating rule key '%s'", key) @@ -108,6 +110,8 @@ def load(cls, endpoint: platform.Platform, key: str, data: types.ApiPayload) -> @classmethod def instantiate(cls, endpoint: platform.Platform, key: str, template_key: str, data: types.ObjectJsonRepr) -> Rule: + if endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't instantiate rules on SonarCloud") try: rule = Rule.get_object(endpoint, key) log.info("Rule key '%s' already exists, instantiation skipped...", key) @@ -162,6 +166,8 @@ def reset_tags(self) -> bool: def set_description(self, description: str) -> bool: """Extends rule description""" + if self.endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't extend rules description on SonarCloud") log.debug("Settings custom description of %s to '%s'", str(self), description) ok = self.post(_UPDATE_API, params={"key": self.key, "markdown_note": description}).ok if ok: @@ -281,31 +287,28 @@ def export_needed(endpoint: platform.Platform, instantiated: bool = True, extend return utilities.remove_nones(rule_list) -def export( - endpoint: platform.Platform, export_settings: types.ConfigSettings, instantiated: bool = True, extended: bool = True, standard: bool = False -) -> types.ObjectJsonRepr: +def export(endpoint: platform.Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr: """Returns a dict of rules for export - :return: a dict of rule onbjects indexed with rule key :param Platform endpoint: The SonarQube Platform object to connect to :param ConfigSettings export_settings: parameters to export - :param bool instantiated: Include instantiated rules in the list - :param bool extended: Include extended rules in the list - :param bool standard: Include standard rules in the list - :param full standard: Include full rule information in the export - :rtype: dict{ruleKey: bool: +def import_config(endpoint: platform.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> bool: """Imports a sonar-config configuration""" if "rules" not in config_data: log.info("No customized rules (custom tags, extended description) to import") return True + if endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't import rules in SonarCloud") log.info("Importing customized (custom tags, extended description) rules") get_list(endpoint=endpoint) for key, custom in config_data["rules"].get("extended", {}).items(): diff --git a/sonar/settings.py b/sonar/settings.py index 13f4d8721..d0ac23142 100644 --- a/sonar/settings.py +++ b/sonar/settings.py @@ -25,6 +25,7 @@ import re import json from typing import Union +from http import HTTPStatus from requests.exceptions import HTTPError import sonar.logging as log @@ -115,7 +116,7 @@ ) API_SET = "settings/set" -API_CREATE = "settings/set" +API_CREATE = API_SET API_GET = "settings/values" API_LIST = "settings/list_definitions" API_NEW_CODE_GET = "new_code_periods/show" @@ -447,7 +448,17 @@ def get_new_code_period(endpoint: pf.Platform, project_or_branch: object) -> Set def set_new_code_period(endpoint: pf.Platform, nc_type: str, nc_value: str, project_key: str = None, branch: str = None) -> bool: """Sets the new code period at global level or for a project""" log.debug("Setting new code period for project '%s' branch '%s' to value '%s = %s'", str(project_key), str(branch), str(nc_type), str(nc_value)) - return endpoint.post(API_NEW_CODE_SET, params={"type": nc_type, "value": nc_value, "project": project_key, "branch": branch}).ok + try: + if endpoint.is_sonarcloud(): + ok = endpoint.post(API_SET, params={"key": "sonar.leak.period.type", "value": nc_type, "project": project_key}).ok + ok = ok and endpoint.post(API_SET, params={"key": "sonar.leak.period", "value": nc_value, "project": project_key}).ok + else: + ok = endpoint.post(API_NEW_CODE_SET, params={"type": nc_type, "value": nc_value, "project": project_key, "branch": branch}).ok + except HTTPError as e: + if e.response.status_code == HTTPStatus.BAD_REQUEST: + raise exceptions.UnsupportedOperation(f"Can't set project new code period: {e.response.text}") + raise + return ok def get_visibility(endpoint: pf.Platform, component: object) -> str: @@ -468,12 +479,17 @@ def get_visibility(endpoint: pf.Platform, component: object) -> str: def set_visibility(endpoint: pf.Platform, visibility: str, component: object = None) -> bool: """Sets the platform global default visibility or component visibility""" - if component: - log.debug("Setting setting '%s' of %s to value '%s'", COMPONENT_VISIBILITY, str(component), visibility) - return endpoint.post("projects/update_visibility", params={"project": component.key, "visibility": visibility}).ok - else: - log.debug("Setting setting '%s' to value '%s'", PROJECT_DEFAULT_VISIBILITY, str(visibility)) - return endpoint.post("projects/update_default_visibility", params={"projectVisibility": visibility}).ok + try: + if component: + log.debug("Setting setting '%s' of %s to value '%s'", COMPONENT_VISIBILITY, str(component), visibility) + return endpoint.post("projects/update_visibility", params={"project": component.key, "visibility": visibility}).ok + else: + log.debug("Setting setting '%s' to value '%s'", PROJECT_DEFAULT_VISIBILITY, str(visibility)) + return endpoint.post("projects/update_default_visibility", params={"projectVisibility": visibility}).ok + except HTTPError as e: + if e.response.status_code == HTTPStatus.BAD_REQUEST: + raise exceptions.UnsupportedOperation(f"Can't set project default visibility: {e.response.text}") + raise def set_setting(endpoint: pf.Platform, key: str, value: any, component: object = None) -> bool: @@ -485,10 +501,13 @@ def set_setting(endpoint: pf.Platform, key: str, value: any, component: object = else: try: s.set(value) - return True - except HTTPError: - log.warning("Setting '%s' cannot be set", key) + except HTTPError as e: + log.error("Setting '%s' cannot be set: %s", key, util.sonar_error(e.response)) + return False + except exceptions.UnsupportedOperation as e: + log.error("Setting '%s' cannot be set: %s", key, e.message) return False + return True def decode(setting_key: str, setting_value: any) -> any: diff --git a/sonar/users.py b/sonar/users.py index b44c992c6..fd115ecaf 100644 --- a/sonar/users.py +++ b/sonar/users.py @@ -403,14 +403,14 @@ def search(endpoint: pf.Platform, params: types.ApiParams = None) -> dict[str, U return sqobject.search_objects(endpoint=endpoint, object_class=User, params=params) -def export(endpoint: pf.Platform, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr: - """Exports all users as dict +def export(endpoint: pf.Platform, export_settings: types.ConfigSettings, key_list: types.KeyList = None) -> types.ObjectJsonRepr: + """Exports all users in JSON representation :param Platform endpoint: reference to the SonarQube platform - :param full: Whether to export all settings including those useless for re-import, defaults to False - :type full: bool, optional - :return: list of projects - :rtype: dict{key: Project} + :param ConfigSettings export_settings: Export parameters + :param KeyList key_list: Unused + :return: list of users JSON representation + :rtype: ObjectJsonRepr """ log.info("Exporting users") u_list = {} @@ -455,7 +455,7 @@ def get_login_from_name(endpoint: pf.Platform, name: str) -> Union[str, None]: return list(u_list.keys()).pop(0) -def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr) -> None: +def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr, key_list: types.KeyList = None) -> None: """Imports in SonarQube a complete users configuration described from a sonar-config JSON :param Platform endpoint: reference to the SonarQube platform @@ -465,6 +465,8 @@ def import_config(endpoint: pf.Platform, config_data: types.ObjectJsonRepr) -> N if "users" not in config_data: log.info("No users to import") return + if endpoint.is_sonarcloud(): + raise exceptions.UnsupportedOperation("Can't import users in SonarCloud") log.info("Importing users") for login, data in config_data["users"].items(): data["scm_accounts"] = util.csv_to_list(data.pop("scmAccounts", ""))