]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20021 Migrate impacts for rules on startup
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Wed, 9 Aug 2023 16:52:58 +0000 (18:52 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 18 Aug 2023 20:02:49 +0000 (20:02 +0000)
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRules.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/PopulateDefaultImpactsInRulesTest/schema.sql [new file with mode: 0644]

index c2d8ddc24c996584e879e4c0f458c47306ae7af7..3d6958146a97df991d186edce7da5aa5da7807e4 100644 (file)
@@ -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 (file)
index 0000000..8ec998d
--- /dev/null
@@ -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 (file)
index 0000000..59d1d6d
--- /dev/null
@@ -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 (file)
index 0000000..af5f4ff
--- /dev/null
@@ -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);
+