Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ def __export_config_async(endpoint: platform.Platform, what: list[str], **kwargs


def __export_config(endpoint: platform.Platform, what: list[str], **kwargs) -> None:
if kwargs[options.KEYS] or options.WHAT_PROJECTS not in what or kwargs[options.FORMAT] != "json":
"""Exports the configuration of the SonarQube platform"""
if kwargs[options.KEYS] or options.WHAT_PROJECTS not in what or kwargs[options.FORMAT] != "json" or not kwargs[options.REPORT_FILE]:
__export_config_sync(endpoint=endpoint, what=what, **kwargs)
else:
__export_config_async(endpoint=endpoint, what=what, **kwargs)
Expand Down
5 changes: 4 additions & 1 deletion sonar/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ def get_object(cls, endpoint: pf.Platform, key: str) -> Organization:
except HTTPError as e:
if e.response.status_code == HTTPStatus.NOT_FOUND:
raise exceptions.ObjectNotFound(key, f"Organization '{key}' not found")
raise e
if len(data["organizations"]) == 0:
raise exceptions.ObjectNotFound(key, f"Organization '{key}' not found")
return cls.load(endpoint, data["organizations"][0])

@classmethod
Expand All @@ -99,7 +102,7 @@ def load(cls, endpoint: pf.Platform, data: types.ApiPayload) -> Organization:
raise exceptions.UnsupportedOperation(_NOT_SUPPORTED)
uu = sqobject.uuid(data["key"], endpoint.url)
o = _OBJECTS.get(uu, cls(endpoint, data["key"], data["name"]))
o.json_data = data
o._json = data
o.name = data["name"]
o.description = data["description"]
return o
Expand Down
37 changes: 21 additions & 16 deletions sonar/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,27 @@ def __get_branch_export(self, export_settings: types.ConfigSettings) -> Optional
return None
return util.remove_nones(branch_data)

def migration_export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr:
"""Produces the data that is exported for SQ to SC migration"""
json_data = super().migration_export(export_settings)
json_data["detectedCi"] = self.ci()
json_data["revision"] = self.revision()
last_task = self.last_task()
json_data["backgroundTasks"] = {}
if last_task:
ctxt = last_task.scanner_context()
if ctxt:
ctxt = {k: v for k, v in ctxt.items() if k not in _UNNEEDED_CONTEXT_DATA}
t_hist = []
for t in self.task_history():
t_hist.append({k: v for k, v in t._json.items() if k not in _UNNEEDED_TASK_DATA})
json_data["backgroundTasks"] = {
"lastTaskScannerContext": ctxt,
# "lastTaskWarnings": last_task.warnings(),
"taskHistory": t_hist,
}
return json_data

def export(self, export_settings: types.ConfigSettings, settings_list: dict[str, str] = None) -> types.ObjectJsonRepr:
"""Exports the entire project configuration as JSON

Expand Down Expand Up @@ -1018,22 +1039,6 @@ def export(self, export_settings: types.ConfigSettings, settings_list: dict[str,

if export_settings.get("MODE", "") == "MIGRATION":
json_data.update(self.migration_export(export_settings))
json_data["detectedCi"] = self.ci()
json_data["revision"] = self.revision()
last_task = self.last_task()
json_data["backgroundTasks"] = {}
if last_task:
ctxt = last_task.scanner_context()
if ctxt:
ctxt = {k: v for k, v in ctxt.items() if k not in _UNNEEDED_CONTEXT_DATA}
t_hist = []
for t in self.task_history():
t_hist.append({k: v for k, v in t._json.items() if k not in _UNNEEDED_TASK_DATA})
json_data["backgroundTasks"] = {
"lastTaskScannerContext": ctxt,
# "lastTaskWarnings": last_task.warnings(),
"taskHistory": t_hist,
}

settings_dict = settings.get_bulk(endpoint=self.endpoint, component=self, settings_list=settings_list, include_not_set=False)
# json_data.update({s.to_json() for s in settings_dict.values() if include_inherited or not s.inherited})
Expand Down
2 changes: 1 addition & 1 deletion sonar/sqobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def search_objects(endpoint: object, object_class: any, params: types.ApiParams,
objects_list = {}
data = json.loads(endpoint.get(api, params=new_params).text)
for obj in data[returned_field]:
if object_class.__name__ in ("Portfolio", "Group", "QualityProfile", "User", "Application", "Project"):
if object_class.__name__ in ("Portfolio", "Group", "QualityProfile", "User", "Application", "Project", "Organization"):
objects_list[obj[key_field]] = object_class.load(endpoint=endpoint, data=obj)
else:
objects_list[obj[key_field]] = object_class(endpoint, obj[key_field], data=obj)
Expand Down
120 changes: 120 additions & 0 deletions test/test_sonarcloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
#
# sonar-tools tests
# Copyright (C) 2024 Olivier Korach
# mailto:olivier.korach AT gmail DOT com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
""" sonarcloud tests """

import os
import sys
from unittest.mock import patch
import pytest

import utilities as util
from sonar import errcodes, exceptions
from sonar import organizations
import cli.options as opt
from cli import config

CMD = "config.py"
SC_OPTS = [f"-{opt.URL_SHORT}", "https://sonarcloud.io", f"-{opt.TOKEN_SHORT}", os.getenv("SONAR_TOKEN_SONARCLOUD")]

OPTS = [CMD] + SC_OPTS + [f"-{opt.EXPORT_SHORT}", f"-{opt.REPORT_FILE_SHORT}", util.JSON_FILE]
MY_ORG_1 = "okorach"
MY_ORG_2 = "okorach-github"

def test_sc_config_export() -> None:
"""test_sc_config_export"""
util.clean(util.JSON_FILE)
with pytest.raises(SystemExit) as e:
with patch.object(sys, "argv", OPTS + [f"-{opt.ORG_SHORT}", "okorach"]):
config.main()
assert int(str(e.value)) == errcodes.OK
assert util.file_not_empty(util.JSON_FILE)
util.clean(util.JSON_FILE)


def test_sc_config_export_no_org() -> None:
"""test_sc_config_export"""
util.clean(util.JSON_FILE)
with pytest.raises(SystemExit) as e:
with patch.object(sys, "argv", OPTS):
config.main()
assert int(str(e.value)) == errcodes.ARGS_ERROR
assert not os.path.isfile(util.JSON_FILE)


def test_org_search() -> None:
"""test_org_search"""
org_list = organizations.search(endpoint=util.SC)
assert MY_ORG_1 in org_list
assert MY_ORG_2 in org_list


def test_org_get_list() -> None:
"""test_org_search"""
org_list = organizations.get_list(endpoint=util.SC)
assert MY_ORG_1 in org_list
assert MY_ORG_2 in org_list

org_list = organizations.get_list(endpoint=util.SC, key_list=[MY_ORG_1])
assert MY_ORG_1 in org_list
assert MY_ORG_2 not in org_list


def test_org_get_non_existing() -> None:
"""test_org_search_sq"""
with pytest.raises(exceptions.ObjectNotFound):
_ = organizations.Organization.get_object(endpoint=util.SC, key="oko_foo_bar")

with pytest.raises(exceptions.ObjectNotFound):
_ = organizations.get_list(endpoint=util.SC, key_list=["oko_foo_bar"])


def test_org_str() -> None:
"""test_org_str"""
org = organizations.Organization.get_object(endpoint=util.SC, key=MY_ORG_1)
assert str(org) == f"organization key '{MY_ORG_1}'"


def test_org_export() -> None:
"""test_org_export"""
org = organizations.Organization.get_object(endpoint=util.SC, key=MY_ORG_1)
exp = org.export()
assert "newCodePeriod" in exp


def test_org_attr() -> None:
"""test_org_attr"""
org = organizations.Organization.get_object(endpoint=util.SC, key=MY_ORG_1)
assert org.key == MY_ORG_1
assert org.name == "Olivier Korach"
assert org._json["url"] == "https://github.com/okorach"
(nc_type, nc_val) = org.new_code_period()
assert nc_type == "PREVIOUS_VERSION"
assert org.subscription() == "FREE"
assert org.alm()["key"] == "github"


def test_org_search_sq() -> None:
"""test_org_search_sq"""
with pytest.raises(exceptions.UnsupportedOperation):
_ = organizations.search(endpoint=util.SQ)

with pytest.raises(exceptions.UnsupportedOperation):
_ = organizations.get_list(endpoint=util.SQ)
1 change: 1 addition & 0 deletions test/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
CE_OPTS = [f"-{opt.URL_SHORT}", LATEST_CE, f"-{opt.TOKEN_SHORT}", os.getenv("SONAR_TOKEN_ADMIN_USER")]

SQ = platform.Platform(url=os.getenv("SONAR_HOST_URL"), token=os.getenv("SONAR_TOKEN_ADMIN_USER"))
SC = platform.Platform(url="https://sonarcloud.io", token=os.getenv("SONAR_TOKEN_SONARCLOUD"))
TEST_SQ = platform.Platform(url=LATEST_TEST, token=os.getenv("SONAR_TOKEN_ADMIN_USER"))


Expand Down