.add(1505, "Make PROJECTS.ORGANIZATION_UUID not nullable", MakeOrganizationUuidOfProjectsNotNullable.class)
.add(1506, "Add index on PROJECTS.ORGANIZATION_UUID", AddIndexOnOrganizationUuidOfProjects.class)
.add(1507, "Drop table RESOURCE_INDEX", DropTableResourceIndex.class)
- .add(1508, "Add columns ORGANIZATIONS.DEFAULT_PERM_TEMPLATE_*", AddDefaultPermTemplateColumnsToOrganizations.class);
+ .add(1508, "Add columns ORGANIZATIONS.DEFAULT_PERM_TEMPLATE_*", AddDefaultPermTemplateColumnsToOrganizations.class)
+ .add(1509, "Populate columns ORGANIZATIONS.DEFAULT_PERM_TEMPLATE_*", PopulateDefaultPermTemplateColumnsOfOrganizations.class);
}
}
public interface DefaultOrganizationUuid {
/**
+ * Retrieves the uuid of the default organization from table INTERNAL_PROPERTIES.
+ *
* @throws IllegalStateException if uuid of the default organization can't be retrieved
*/
String get(DataChange.Context context) throws SQLException;
+
+ /**
+ * Retrieves the uuid of the default organization from table INTERNAL_PROPERTIES and ensure the specified organization
+ * exists in table ORGANIZATIONS.
+ *
+ * @throws IllegalStateException if uuid of the default organization can't be retrieved
+ * @throws IllegalStateException if the default organization does not exist
+ */
+ String getAndCheck(DataChange.Context context) throws SQLException;
}
import static com.google.common.base.Preconditions.checkState;
/**
- * Component which can be injected in steps which provides access to the UUID of the default organization, read directly
- * from the BD.
+ * Component which can be injected in steps which provides access to the UUID of the default organization, it reads
+ * directly from the BD.
*/
public class DefaultOrganizationUuidImpl implements DefaultOrganizationUuid {
checkState(uuid != null, "Default organization uuid is missing");
return uuid;
}
+
+ @Override
+ public String getAndCheck(DataChange.Context context) throws SQLException {
+ String organizationUuid = get(context);
+ Select select = context.prepareSelect("select uuid from organizations where uuid=?")
+ .setString(1, organizationUuid);
+ checkState(select.get(row -> row.getString(1)) != null,
+ "Default organization with uuid '%s' does not exist in table ORGANIZATIONS",
+ organizationUuid);
+ return organizationUuid;
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.v63;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Populate the columns DEFAULT_PERM_TEMPLATE, DEFAULT_PERM_TEMPLATE_PROJECT and DEFAULT_PERM_TEMPLATE_VIEW of table
+ * ORGANIZATIONS, for the default organization exclusively, from the properties holding the default permissions template
+ * uuids. These properties are then deleted.
+ *
+ * <p>
+ * This migration ensures it can run but failing if:
+ * <ul>
+ * <li>there is more than one organizations (because we can't populate the column for those extra organizations)</li>
+ * <li>the global default permission template can't be found (because an organization must have at least this default template)</li>
+ * </ul>
+ * </p>
+ */
+public class PopulateDefaultPermTemplateColumnsOfOrganizations extends DataChange {
+ private static final String DEFAULT_TEMPLATE_PROPERTY = "sonar.permission.template.default";
+ private static final String DEFAULT_PROJECT_TEMPLATE_PROPERTY = "sonar.permission.template.TRK.default";
+ private static final String DEFAULT_VIEW_TEMPLATE_PROPERTY = "sonar.permission.template.VW.default";
+ private static final String DEFAULT_DEV_TEMPLATE_PROPERTY = "sonar.permission.template.DEV.default";
+
+ private final DefaultOrganizationUuid defaultOrganizationUuid;
+
+ public PopulateDefaultPermTemplateColumnsOfOrganizations(Database db, DefaultOrganizationUuid defaultOrganizationUuid) {
+ super(db);
+ this.defaultOrganizationUuid = defaultOrganizationUuid;
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ String defaultOrganizationUuid = this.defaultOrganizationUuid.getAndCheck(context);
+ ensureOnlyDefaultOrganizationExists(context, defaultOrganizationUuid);
+
+ Map<String, String> defaultTemplateProperties = retrieveDefaultTemplateProperties(context);
+
+ String globalDefaultTemplate = defaultTemplateProperties.get(DEFAULT_TEMPLATE_PROPERTY);
+ if (globalDefaultTemplate == null || globalDefaultTemplate.isEmpty()) {
+ // DB has just been created, default template of default organization will be populated by a startup task
+ return;
+ }
+ String projectDefaultTemplate = firstNonNull(defaultTemplateProperties.get(DEFAULT_PROJECT_TEMPLATE_PROPERTY), globalDefaultTemplate);
+ String viewDefaultTemplate = defaultTemplateProperties.get(DEFAULT_VIEW_TEMPLATE_PROPERTY);
+
+ Loggers.get(PopulateDefaultPermTemplateColumnsOfOrganizations.class)
+ .debug("Setting default templates on default organization '{}': project='{}', view='{}'",
+ defaultOrganizationUuid, projectDefaultTemplate, viewDefaultTemplate);
+
+ context.prepareUpsert("update organizations set" +
+ " default_perm_template_project=?," +
+ " default_perm_template_view=?" +
+ " where" +
+ " uuid=?")
+ .setString(1, projectDefaultTemplate)
+ .setString(2, viewDefaultTemplate)
+ .setString(3, defaultOrganizationUuid)
+ .execute()
+ .commit();
+
+ // delete properties
+ context.prepareUpsert("delete from properties where prop_key in (?,?,?,?)")
+ .setString(1, DEFAULT_TEMPLATE_PROPERTY)
+ .setString(2, DEFAULT_PROJECT_TEMPLATE_PROPERTY)
+ .setString(3, DEFAULT_VIEW_TEMPLATE_PROPERTY)
+ .setString(4, DEFAULT_DEV_TEMPLATE_PROPERTY)
+ .execute()
+ .commit();
+ }
+
+ private static void ensureOnlyDefaultOrganizationExists(Context context, String defaultOrganizationUuid) throws SQLException {
+ Integer otherOrganizationCount = context.prepareSelect("select count(*) from organizations where uuid <> ?")
+ .setString(1, defaultOrganizationUuid)
+ .get(row -> row.getInt(1));
+ checkState(otherOrganizationCount == 0,
+ "Can not migrate DB if more than one organization exists. Remove all organizations but the default one which uuid is '%s'",
+ defaultOrganizationUuid);
+ }
+
+ private static Map<String, String> retrieveDefaultTemplateProperties(Context context) throws SQLException {
+ Map<String, String> templates = new HashMap<>(3);
+ context.prepareSelect("select prop_key,is_empty,text_value from properties where prop_key in (?,?,?)")
+ .setString(1, DEFAULT_TEMPLATE_PROPERTY)
+ .setString(2, DEFAULT_PROJECT_TEMPLATE_PROPERTY)
+ .setString(3, DEFAULT_VIEW_TEMPLATE_PROPERTY)
+ .scroll(row -> {
+ String key = row.getString(1);
+ boolean isEmpty = row.getBoolean(2);
+ String textValue = row.getString(3);
+ if (!isEmpty) {
+ templates.put(key, textValue);
+ }
+ });
+ return templates;
+ }
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 9);
+ verifyMigrationCount(underTest, 10);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.v63;
+
+import java.sql.SQLException;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PopulateDefaultPermTemplateColumnsOfOrganizationsTest {
+ private static final String DEFAULT_TEMPLATE_PROPERTY = "sonar.permission.template.default";
+ private static final String DEFAULT_PROJECT_TEMPLATE_PROPERTY = "sonar.permission.template.TRK.default";
+ private static final String DEFAULT_VIEW_TEMPLATE_PROPERTY = "sonar.permission.template.VW.default";
+ private static final String DEFAULT_DEV_TEMPLATE_PROPERTY = "sonar.permission.template.DEV.default";
+ private static final String DEFAULT_ORGANIZATION_UUID = "def org uuid";
+
+ @Rule
+ public DbTester dbTester = DbTester.createForSchema(System2.INSTANCE, PopulateDefaultPermTemplateColumnsOfOrganizationsTest.class, "properties_and_organizations.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private PopulateDefaultPermTemplateColumnsOfOrganizations underTest = new PopulateDefaultPermTemplateColumnsOfOrganizations(dbTester.database(),
+ new DefaultOrganizationUuidImpl());
+
+ @Test
+ public void fails_with_ISE_when_no_default_organization_is_set() throws SQLException {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Default organization uuid is missing");
+
+ underTest.execute();
+ }
+
+ @Test
+ public void fails_with_ISE_when_default_organization_does_not_exist_in_table_ORGANIZATIONS() throws SQLException {
+ insertDefaultOrganizationUuid("blabla");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Default organization with uuid 'blabla' does not exist in table ORGANIZATIONS");
+
+ underTest.execute();
+ }
+
+ @Test
+ public void fails_with_ISE_when_more_than_one_organization_exist() throws SQLException {
+ setupDefaultOrganization();
+
+ insertOrganization("other orga uuid");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Can not migrate DB if more than one organization exists. " +
+ "Remove all organizations but the default one which uuid is '" + DEFAULT_ORGANIZATION_UUID + "'");
+
+ underTest.execute();
+ }
+
+ @Test
+ public void do_nothing_if_global_default_template_property_does_not_exist() throws SQLException {
+ setupDefaultOrganization();
+
+ underTest.execute();
+
+ verifyTemplateColumns(null, null);
+ verifyPropertiesDoNotExist();
+ }
+
+ @Test
+ public void execute_sets_project_perm_template_when_global_default_template_is_defined_in_property() throws SQLException {
+ setupDefaultOrganization();
+ insertProperty(DEFAULT_TEMPLATE_PROPERTY, "foo");
+
+ underTest.execute();
+
+ verifyTemplateColumns("foo", null);
+ verifyPropertiesDoNotExist();
+ }
+
+ @Test
+ public void execute_sets_project_perm_template_from_project_default_template_property_over_global_property() throws SQLException {
+ setupDefaultOrganization();
+ insertProperty(DEFAULT_TEMPLATE_PROPERTY, "foo");
+ insertProperty(DEFAULT_PROJECT_TEMPLATE_PROPERTY, "bar");
+
+ underTest.execute();
+
+ verifyTemplateColumns("bar", null);
+ verifyPropertiesDoNotExist();
+ }
+
+ @Test
+ public void execute_sets_project_perm_template_from_global_property_and_view_perm_template_from_view_property() throws SQLException {
+ setupDefaultOrganization();
+ insertProperty(DEFAULT_TEMPLATE_PROPERTY, "foo");
+ insertProperty(DEFAULT_VIEW_TEMPLATE_PROPERTY, "bar");
+
+ underTest.execute();
+
+ verifyTemplateColumns("foo", "bar");
+ verifyPropertiesDoNotExist();
+ }
+
+ @Test
+ public void execute_sets_project_from_project_property_and_view_from_view_property_when_all_properties_are_defined() throws SQLException {
+ setupDefaultOrganization();
+ insertProperty(DEFAULT_TEMPLATE_PROPERTY, "foo");
+ insertProperty(DEFAULT_PROJECT_TEMPLATE_PROPERTY, "bar");
+ insertProperty(DEFAULT_VIEW_TEMPLATE_PROPERTY, "doh");
+
+ underTest.execute();
+
+ verifyTemplateColumns("bar", "doh");
+ verifyPropertiesDoNotExist();
+ }
+
+ @Test
+ public void execute_deletes_dev_property_when_it_is_defined() throws SQLException {
+ setupDefaultOrganization();
+ insertProperty(DEFAULT_TEMPLATE_PROPERTY, "foo");
+ insertProperty(DEFAULT_DEV_TEMPLATE_PROPERTY, "bar");
+
+ underTest.execute();
+
+ verifyPropertiesDoNotExist();
+ }
+
+ private void verifyTemplateColumns(@Nullable String project, @Nullable String view) {
+ Map<String, Object> row = dbTester.selectFirst("select " +
+ " default_perm_template_project as \"projectDefaultPermTemplate\"," +
+ " default_perm_template_view as \"viewDefaultPermTemplate\"" +
+ " from organizations where uuid='" + DEFAULT_ORGANIZATION_UUID + "'");
+ assertThat(row.get("projectDefaultPermTemplate")).isEqualTo(project);
+ assertThat(row.get("viewDefaultPermTemplate")).isEqualTo(view);
+ }
+
+ private void verifyPropertiesDoNotExist() {
+ verifyPropertyDoesNotExist(DEFAULT_TEMPLATE_PROPERTY);
+ verifyPropertyDoesNotExist(DEFAULT_PROJECT_TEMPLATE_PROPERTY);
+ verifyPropertyDoesNotExist(DEFAULT_VIEW_TEMPLATE_PROPERTY);
+ verifyPropertyDoesNotExist(DEFAULT_DEV_TEMPLATE_PROPERTY);
+ }
+
+ private void verifyPropertyDoesNotExist(String globalPermissionTemplateDefault) {
+ assertThat(dbTester.countSql("select count(*) as \"cnt\" from PROPERTIES where prop_key='" + globalPermissionTemplateDefault + "'"))
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void migration_is_reentrant() throws SQLException {
+ setupDefaultOrganization();
+ insertProperty(DEFAULT_TEMPLATE_PROPERTY, "foo");
+
+ underTest.execute();
+
+ underTest.execute();
+ }
+
+ private void setupDefaultOrganization() {
+ insertDefaultOrganizationUuid(DEFAULT_ORGANIZATION_UUID);
+ insertOrganization(DEFAULT_ORGANIZATION_UUID);
+ }
+
+ private void insertOrganization(String uuid) {
+ dbTester.executeInsert(
+ "ORGANIZATIONS",
+ "UUID", uuid,
+ "KEE", uuid,
+ "NAME", uuid,
+ "CREATED_AT", "1000",
+ "UPDATED_AT", "1000");
+ }
+
+ private void insertDefaultOrganizationUuid(String defaultOrganizationUuid) {
+ dbTester.executeInsert(
+ "INTERNAL_PROPERTIES",
+ "KEE", "organization.default",
+ "IS_EMPTY", "false",
+ "TEXT_VALUE", defaultOrganizationUuid);
+ }
+
+ private void insertProperty(String key, @Nullable String value) {
+ dbTester.executeInsert(
+ "PROPERTIES",
+ "PROP_KEY", key,
+ "IS_EMPTY", String.valueOf(value == null),
+ "TEXT_VALUE", value);
+ }
+}
import static java.util.Objects.requireNonNull;
+/**
+ * Implementation of {@link DefaultOrganizationUuid} which never fails and returns the specified organization uuid.
+ */
public class TestDefaultOrganizationUuid implements DefaultOrganizationUuid {
private final String organizationUuid;
public String get(DataChange.Context context) throws SQLException {
return organizationUuid;
}
+
+ @Override
+ public String getAndCheck(DataChange.Context context) throws SQLException {
+ return organizationUuid;
+ }
}
--- /dev/null
+CREATE TABLE "ORGANIZATIONS" (
+ "UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
+ "KEE" VARCHAR(32) NOT NULL,
+ "NAME" VARCHAR(64) NOT NULL,
+ "DESCRIPTION" VARCHAR(256),
+ "URL" VARCHAR(256),
+ "AVATAR_URL" VARCHAR(256),
+ "DEFAULT_PERM_TEMPLATE" VARCHAR(40),
+ "DEFAULT_PERM_TEMPLATE_PROJECT" VARCHAR(40),
+ "DEFAULT_PERM_TEMPLATE_VIEW" VARCHAR(40),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "PK_ORGANIZATIONS" ON "ORGANIZATIONS" ("UUID");
+CREATE UNIQUE INDEX "ORGANIZATION_KEY" ON "ORGANIZATIONS" ("KEE");
+
+CREATE TABLE "PROPERTIES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROP_KEY" VARCHAR(512) NOT NULL,
+ "RESOURCE_ID" INTEGER,
+ "USER_ID" INTEGER,
+ "IS_EMPTY" BOOLEAN NOT NULL,
+ "TEXT_VALUE" VARCHAR(4000),
+ "CLOB_VALUE" CLOB(2147483647),
+ "CREATED_AT" BIGINT
+);
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");
+
+CREATE TABLE "INTERNAL_PROPERTIES" (
+ "KEE" VARCHAR(50) NOT NULL PRIMARY KEY,
+ "IS_EMPTY" BOOLEAN NOT NULL,
+ "TEXT_VALUE" VARCHAR(4000),
+ "CLOB_VALUE" CLOB,
+ "CREATED_AT" BIGINT
+);
+CREATE UNIQUE INDEX "UNIQ_INTERNAL_PROPERTIES" ON "INTERNAL_PROPERTIES" ("KEE");
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1506');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1507');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1508');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1509');
INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, IS_ROOT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', true, '1418215735482', '1418215735482');
ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;