From 4cb51ade62a98b687926100da89c68d2df026c53 Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Wed, 9 Aug 2023 18:52:58 +0200 Subject: [PATCH] SONAR-20021 Migrate impacts for rules on startup --- .../migration/version/v102/DbVersion102.java | 3 +- .../v102/PopulateDefaultImpactsInRules.java | 108 +++++++++ .../PopulateDefaultImpactsInRulesTest.java | 222 ++++++++++++++++++ .../schema.sql | 56 +++++ 4 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRules.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest/schema.sql diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java index c2d8ddc24c9..3d6958146a9 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java @@ -93,6 +93,7 @@ public class DbVersion102 implements DbVersion { .add(10_2_036, "Create 'rules_default_impacts' table", CreateRulesDefaultImpactsTable.class) .add(10_2_037, "Create unique constraint index on 'rules_default_impacts' table", CreateUniqueConstraintOnRulesDefaultImpacts.class) .add(10_2_038, "Create 'issues_impacts' table", CreateIssueImpactsTable.class) - .add(10_2_039, "Create unique constraint index on 'issues_impacts' table", CreateUniqueConstraintOnIssuesImpacts.class); + .add(10_2_039, "Create unique constraint index on 'issues_impacts' table", CreateUniqueConstraintOnIssuesImpacts.class) + .add(10_2_040, "Populate default impacts for existing rules", PopulateDefaultImpactsInRules.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRules.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRules.java new file mode 100644 index 00000000000..8ec998d3b82 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRules.java @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource 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. + */ +package org.sonar.server.platform.db.migration.version.v102; + +import java.sql.SQLException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.internal.ImpactMapper; +import org.sonar.core.util.Uuids; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class PopulateDefaultImpactsInRules extends DataChange { + + private static final Logger LOG = LoggerFactory.getLogger(PopulateDefaultImpactsInRules.class); + + private static final String SELECT_QUERY = """ + SELECT r.uuid, rule_type, priority, ad_hoc_type, ad_hoc_severity, is_ad_hoc + FROM rules r + LEFT JOIN rules_default_impacts rdi ON rdi.rule_uuid = r.uuid + WHERE rdi.uuid IS NULL + """; + + private static final String INSERT_QUERY = """ + INSERT INTO rules_default_impacts (uuid, rule_uuid, software_quality, severity) + VALUES (?, ?, ?, ?) + """; + + public PopulateDefaultImpactsInRules(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select(SELECT_QUERY); + massUpdate.update(INSERT_QUERY); + + massUpdate.execute((row, update, index) -> { + String ruleUuid = row.getString(1); + String ruleType = row.getString(2); + String severity = row.getString(3); + String adhocType = row.getString(4); + String adhocSeverity = row.getString(5); + boolean isAdhoc = row.getBoolean(6); + + SoftwareQuality softwareQuality; + Severity impactSeverity; + + try { + RuleType effectiveType = null; + String effectiveSeverity = null; + if (isAdhoc && adhocType != null && adhocSeverity != null) { + effectiveType = RuleType.valueOf(Integer.valueOf(adhocType)); + effectiveSeverity = adhocSeverity; + } else if (!isAdhoc && ruleType != null && !ruleType.equals("0") && severity != null) { + effectiveType = RuleType.valueOf(Integer.valueOf(ruleType)); + effectiveSeverity = org.sonar.api.rule.Severity.ALL.get(Integer.valueOf(severity)); + } else if (!isAdhoc) { + //When type and severity are missing, we are in the case of a "placeholder" adhoc_rule that was created as default with an external issue. + //In that case, we want to set default values for the impact. Otherwise, we don't populate the impact + return false; + } + if (effectiveType == RuleType.SECURITY_HOTSPOT) { + return false; + } + if (effectiveType != null && effectiveSeverity != null) { + softwareQuality = ImpactMapper.convertToSoftwareQuality(effectiveType); + impactSeverity = ImpactMapper.convertToImpactSeverity(effectiveSeverity); + } else { + softwareQuality = SoftwareQuality.MAINTAINABILITY; + impactSeverity = Severity.MEDIUM; + } + } catch (Exception e) { + LOG.warn("Error while mapping type to impact for rule '%s'".formatted(ruleUuid)); + LOG.debug("Error while mapping type to impact for rule '%s'".formatted(ruleUuid), e); + return false; + } + + update.setString(1, Uuids.create()) + .setString(2, ruleUuid) + .setString(3, softwareQuality.name()) + .setString(4, impactSeverity.name()); + return true; + }); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest.java new file mode 100644 index 00000000000..59d1d6d6e1d --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest.java @@ -0,0 +1,222 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource 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. + */ +package org.sonar.server.platform.db.migration.version.v102; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rules.RuleType; +import org.sonar.api.testfixtures.log.LogTester; +import org.sonar.core.util.Uuids; +import org.sonar.db.CoreDbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.tuple; + +public class PopulateDefaultImpactsInRulesTest { + private static final String TABLE_NAME = "rules"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(PopulateDefaultImpactsInRulesTest.class, "schema.sql"); + + @Rule + public LogTester logTester = new LogTester(); + + private final PopulateDefaultImpactsInRules underTest = new PopulateDefaultImpactsInRules(db.database()); + + @Test + public void execute_whenRulesDoNotExist_shouldNotFail() { + assertThatCode(underTest::execute).doesNotThrowAnyException(); + } + + @Test + public void execute_whenRulesHasTypeAndSeverity_shouldCreateImpact() throws SQLException { + insertRuleWithType("uuid", RuleType.CODE_SMELL, Severity.MAJOR); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"), + stringObjectMap -> stringObjectMap.get("SEVERITY")) + .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name())); + } + + @Test + public void execute_shouldBeReentrant() throws SQLException { + insertRuleWithType("uuid", RuleType.CODE_SMELL, Severity.MAJOR); + underTest.execute(); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .hasSize(1) + .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"), + stringObjectMap -> stringObjectMap.get("SEVERITY")) + .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name())); + + } + + @Test + public void execute_whenAdhocRulesHasTypeAndSeverity_shouldCreateImpact() throws SQLException { + insertRuleWithAdHocType("uuid", RuleType.CODE_SMELL, Severity.MAJOR); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .hasSize(1) + .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"), + stringObjectMap -> stringObjectMap.get("SEVERITY")) + .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name())); + + } + + @Test + public void execute_whenAdhocRulesHasImpactAlready_shouldNotCreateImpact() throws SQLException { + insertRuleWithAdHocType("uuid", RuleType.CODE_SMELL, Severity.MAJOR); + insertImpact("uuid", SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .hasSize(1) + .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"), + stringObjectMap -> stringObjectMap.get("SEVERITY")) + .containsExactly(tuple(SoftwareQuality.SECURITY.name(), org.sonar.api.issue.impact.Severity.HIGH.name())); + + } + + @Test + public void execute_whenNoTypeAndSeverityDefined_shouldNotCreateImpact() throws SQLException { + insertRuleWithType("uuid", null, null); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .isEmpty(); + + } + + @Test + public void execute_whenInvalidValueDefined_shouldNotCreateImpactAndLog() throws SQLException { + insertInvalidRule("uuid"); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .isEmpty(); + assertThat(logTester.logs()).contains("Error while mapping type to impact for rule 'uuid'"); + + } + + @Test + public void execute_whenTypeIsHotspot_shouldNotCreateImpactAndLog() throws SQLException { + insertRuleWithType("uuid", RuleType.SECURITY_HOTSPOT, Severity.MAJOR); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .isEmpty(); + assertThat(logTester.logs()).doesNotContain("Error while mapping type to impact for rule 'uuid'"); + } + + @Test + public void execute_whenRuleHasEmptyFields_shouldCreateADefaultImpact() throws SQLException { + insertPlaceholderAdhocRule("uuid"); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .hasSize(1) + .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"), + stringObjectMap -> stringObjectMap.get("SEVERITY")) + .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name())); + } + + @Test + public void execute_whenStandardRuleHasBothAdhocAndStandardTypeAndSeverity_shouldCreateADefaultImpactWithAdhocTypes() throws SQLException { + insertRule("uuid", RuleType.CODE_SMELL, Severity.CRITICAL, RuleType.VULNERABILITY, Severity.MINOR, true); + underTest.execute(); + + assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts")) + .hasSize(1) + .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"), + stringObjectMap -> stringObjectMap.get("SEVERITY")) + .containsExactly(tuple(SoftwareQuality.SECURITY.name(), org.sonar.api.issue.impact.Severity.LOW.name())); + } + + private void insertRuleWithType(String uuid, @Nullable RuleType ruleType, @Nullable Severity severity) { + insertRule(uuid, ruleType, severity, null, null); + } + + private void insertRuleWithAdHocType(String uuid, @Nullable RuleType adHocType, @Nullable Severity adHocseverity) { + insertRule(uuid, null, null, adHocType, adHocseverity); + } + + + private void insertRule(String uuid, @Nullable RuleType ruleType, @Nullable Severity severity, @Nullable RuleType adHocType, @Nullable Severity adHocseverity) { + insertRule(uuid, ruleType, severity, adHocType, adHocseverity, adHocType != null); + } + + private void insertRule(String uuid, @Nullable RuleType ruleType, @Nullable Severity severity, @Nullable RuleType adHocType, @Nullable Severity adHocseverity, boolean isAdhoc) { + db.executeInsert(TABLE_NAME, + "UUID", uuid, + "PLUGIN_RULE_KEY", "key", + "PLUGIN_NAME", "name", + "SCOPE", "1", + "RULE_TYPE", ruleType != null ? ruleType.getDbConstant() : null, + "PRIORITY", severity != null ? org.sonar.api.rule.Severity.ALL.indexOf(severity.name()) : null, + "AD_HOC_TYPE", adHocType != null ? adHocType.getDbConstant() : null, + "AD_HOC_SEVERITY", adHocseverity != null ? adHocseverity.name() : null, + "IS_TEMPLATE", false, + "IS_AD_HOC", isAdhoc, + "IS_EXTERNAL", isAdhoc); + } + + private void insertInvalidRule(String uuid) { + db.executeInsert(TABLE_NAME, + "UUID", uuid, + "PLUGIN_RULE_KEY", "key", + "PLUGIN_NAME", "name", + "SCOPE", "1", + "RULE_TYPE", "-1", + "PRIORITY", "-1", + "AD_HOC_TYPE", "-1", + "AD_HOC_SEVERITY", "-1", + "IS_TEMPLATE", false, + "IS_AD_HOC", false, + "IS_EXTERNAL", false); + } + + private void insertPlaceholderAdhocRule(String uuid) { + db.executeInsert(TABLE_NAME, + "UUID", uuid, + "PLUGIN_RULE_KEY", "key", + "PLUGIN_NAME", "name", + "SCOPE", "1", + "IS_TEMPLATE", false, + "IS_AD_HOC", true, + "IS_EXTERNAL", false); + } + + private void insertImpact(String ruleUuid, SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) { + db.executeInsert("RULES_DEFAULT_IMPACTS", + "UUID", Uuids.create(), + "RULE_UUID", ruleUuid, + "SOFTWARE_QUALITY", softwareQuality.name(), + "SEVERITY", severity.name()); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest/schema.sql new file mode 100644 index 00000000000..af5f4ff5147 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest/schema.sql @@ -0,0 +1,56 @@ +CREATE TABLE "RULES" +( + "UUID" CHARACTER VARYING(40) NOT NULL, + "NAME" CHARACTER VARYING(200), + "PLUGIN_RULE_KEY" CHARACTER VARYING(200) NOT NULL, + "PLUGIN_KEY" CHARACTER VARYING(200), + "PLUGIN_CONFIG_KEY" CHARACTER VARYING(200), + "PLUGIN_NAME" CHARACTER VARYING(255) NOT NULL, + "SCOPE" CHARACTER VARYING(20) NOT NULL, + "PRIORITY" INTEGER, + "STATUS" CHARACTER VARYING(40), + "LANGUAGE" CHARACTER VARYING(20), + "DEF_REMEDIATION_FUNCTION" CHARACTER VARYING(20), + "DEF_REMEDIATION_GAP_MULT" CHARACTER VARYING(20), + "DEF_REMEDIATION_BASE_EFFORT" CHARACTER VARYING(20), + "GAP_DESCRIPTION" CHARACTER VARYING(4000), + "SYSTEM_TAGS" CHARACTER VARYING(4000), + "IS_TEMPLATE" BOOLEAN DEFAULT FALSE NOT NULL, + "DESCRIPTION_FORMAT" CHARACTER VARYING(20), + "RULE_TYPE" TINYINT, + "SECURITY_STANDARDS" CHARACTER VARYING(4000), + "IS_AD_HOC" BOOLEAN NOT NULL, + "IS_EXTERNAL" BOOLEAN NOT NULL, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT, + "TEMPLATE_UUID" CHARACTER VARYING(40), + "NOTE_DATA" CHARACTER LARGE OBJECT, + "NOTE_USER_UUID" CHARACTER VARYING(255), + "NOTE_CREATED_AT" BIGINT, + "NOTE_UPDATED_AT" BIGINT, + "REMEDIATION_FUNCTION" CHARACTER VARYING(20), + "REMEDIATION_GAP_MULT" CHARACTER VARYING(20), + "REMEDIATION_BASE_EFFORT" CHARACTER VARYING(20), + "TAGS" CHARACTER VARYING(4000), + "AD_HOC_NAME" CHARACTER VARYING(200), + "AD_HOC_DESCRIPTION" CHARACTER LARGE OBJECT, + "AD_HOC_SEVERITY" CHARACTER VARYING(10), + "AD_HOC_TYPE" TINYINT, + "EDUCATION_PRINCIPLES" CHARACTER VARYING(255), + "CLEAN_CODE_ATTRIBUTE" CHARACTER VARYING(40) +); +ALTER TABLE "RULES" + ADD CONSTRAINT "PK_RULES" PRIMARY KEY ("UUID"); +CREATE UNIQUE INDEX "RULES_REPO_KEY" ON "RULES" ("PLUGIN_RULE_KEY" NULLS FIRST, "PLUGIN_NAME" NULLS FIRST); + +CREATE TABLE "RULES_DEFAULT_IMPACTS" +( + "UUID" CHARACTER VARYING(40) NOT NULL, + "RULE_UUID" CHARACTER VARYING(40) NOT NULL, + "SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL, + "SEVERITY" CHARACTER VARYING(40) NOT NULL +); +ALTER TABLE "RULES_DEFAULT_IMPACTS" + ADD CONSTRAINT "PK_RULES_DEFAULT_IMPACTS" PRIMARY KEY ("UUID"); +CREATE UNIQUE INDEX "UNIQ_RUL_UUID_SOF_QUAL" ON "RULES_DEFAULT_IMPACTS" ("RULE_UUID" NULLS FIRST, "SOFTWARE_QUALITY" NULLS FIRST); + -- 2.39.5