From e4e1216a854c179bc072bdcf1d2a79fca700ff52 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 2 Aug 2024 10:40:15 +0200 Subject: [PATCH] import-database-users-and-databases (#82) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/82 Reviewed-by: Marc Sandlus --- .../hsadminng/hash/HashGenerator.java | 15 ++ .../hs/hosting/asset/HsHostingAssetType.java | 1 + ...sMariaDbDatabaseHostingAssetValidator.java | 4 +- .../HsMariaDbUserHostingAssetValidator.java | 4 +- ...stgreSqlDatabaseHostingAssetValidator.java | 4 +- ...greSqlDbInstanceHostingAssetValidator.java | 4 +- ...HsPostgreSqlUserHostingAssetValidator.java | 4 +- .../hs/validation/PasswordProperty.java | 8 +- .../hs/validation/StringProperty.java | 8 + .../changelog/9-hs-global/9000-statistics.sql | 23 ++ .../db/changelog/db.changelog-master.yaml | 2 + ...DatabaseHostingAssetValidatorUnitTest.java | 8 +- ...iaDbUserHostingAssetValidatorUnitTest.java | 10 +- ...DatabaseHostingAssetValidatorUnitTest.java | 14 +- ...eSqlUserHostingAssetValidatorUnitTest.java | 10 +- .../hsadminng/hs/migration/CsvDataImport.java | 35 ++- .../hs/migration/ImportHostingAssets.java | 236 +++++++++++++++++- .../resources/migration/hosting/database.csv | 22 ++ .../migration/hosting/database_user.csv | 17 ++ 19 files changed, 388 insertions(+), 41 deletions(-) create mode 100644 src/main/resources/db/changelog/9-hs-global/9000-statistics.sql create mode 100644 src/test/resources/migration/hosting/database.csv create mode 100644 src/test/resources/migration/hosting/database_user.csv diff --git a/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java b/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java index 5bc09cc6..44f41281 100644 --- a/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/hash/HashGenerator.java @@ -27,6 +27,7 @@ public final class HashGenerator { "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789/."; + private static boolean couldBeHashEnabled; // TODO.impl: remove after legacy data is migrated public enum Algorithm { LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"), @@ -59,6 +60,14 @@ public final class HashGenerator { this.algorithm = algorithm; } + public static void enableChouldBeHash(final boolean enable) { + couldBeHashEnabled = enable; + } + + public boolean couldBeHash(final String value) { + return couldBeHashEnabled && value.startsWith(algorithm.prefix); + } + public String hash(final String plaintextPassword) { if (plaintextPassword == null) { throw new IllegalStateException("no password given"); @@ -67,6 +76,12 @@ public final class HashGenerator { return algorithm.implementation.apply(this, plaintextPassword); } + public String hashIfNotYetHashed(final String plaintextPasswordOrHash) { + return couldBeHash(plaintextPasswordOrHash) + ? plaintextPasswordOrHash + : hash(plaintextPasswordOrHash); + } + public static void nextSalt(final String salt) { predefinedSalts.add(salt); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java index 4209a05e..f08248c4 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetType.java @@ -50,6 +50,7 @@ public enum HsHostingAssetType implements Node { inGroup("Webspace"), requiredParent(MANAGED_WEBSPACE)), + // TODO.spec: do we really want to keep email aliases or migrate to unix users with .forward? EMAIL_ALIAS( // named e.g. xyz00-abc inGroup("Webspace"), requiredParent(MANAGED_WEBSPACE)), diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidator.java index 48618be3..823308ed 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidator.java @@ -9,6 +9,8 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsMariaDbDatabaseHostingAssetValidator extends HostingAssetEntityValidator { + final static String HEAD_REGEXP = "^MAD\\|"; + public HsMariaDbDatabaseHostingAssetValidator() { super( MARIADB_DATABASE, @@ -20,6 +22,6 @@ class HsMariaDbDatabaseHostingAssetValidator extends HostingAssetEntityValidator @Override protected Pattern identifierPattern(final HsHostingAsset assetEntity) { final var webspaceIdentifier = assetEntity.getParentAsset().getParentAsset().getIdentifier(); - return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$"); + return Pattern.compile(HEAD_REGEXP+webspaceIdentifier+"$|"+HEAD_REGEXP+webspaceIdentifier+"_[a-zA-Z0-9_]+$"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidator.java index 15ae0b45..58a33520 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidator.java @@ -10,6 +10,8 @@ import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordP class HsMariaDbUserHostingAssetValidator extends HostingAssetEntityValidator { + final static String HEAD_REGEXP = "^MAU\\|"; + public HsMariaDbUserHostingAssetValidator() { super( MARIADB_USER, @@ -28,6 +30,6 @@ class HsMariaDbUserHostingAssetValidator extends HostingAssetEntityValidator { @Override protected Pattern identifierPattern(final HsHostingAsset assetEntity) { final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier(); - return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$"); + return Pattern.compile(HEAD_REGEXP+webspaceIdentifier+"$|"+HEAD_REGEXP+webspaceIdentifier+"_[a-zA-Z0-9_]+$"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidator.java index 57d302d0..830b2fbf 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidator.java @@ -9,6 +9,8 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsPostgreSqlDatabaseHostingAssetValidator extends HostingAssetEntityValidator { + final static String HEAD_REGEXP = "^PGD\\|"; + public HsPostgreSqlDatabaseHostingAssetValidator() { super( PGSQL_DATABASE, @@ -23,6 +25,6 @@ class HsPostgreSqlDatabaseHostingAssetValidator extends HostingAssetEntityValida @Override protected Pattern identifierPattern(final HsHostingAsset assetEntity) { final var webspaceIdentifier = assetEntity.getParentAsset().getParentAsset().getIdentifier(); - return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$"); + return Pattern.compile(HEAD_REGEXP+webspaceIdentifier+"$|"+HEAD_REGEXP+webspaceIdentifier+"_[a-zA-Z0-9_]+$"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDbInstanceHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDbInstanceHostingAssetValidator.java index 36365597..70de55f9 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDbInstanceHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDbInstanceHostingAssetValidator.java @@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset; import java.util.regex.Pattern; import static java.util.Optional.ofNullable; -import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_DATABASE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_INSTANCE; class HsPostgreSqlDbInstanceHostingAssetValidator extends HostingAssetEntityValidator { @@ -13,7 +13,7 @@ class HsPostgreSqlDbInstanceHostingAssetValidator extends HostingAssetEntityVali public HsPostgreSqlDbInstanceHostingAssetValidator() { super( - PGSQL_DATABASE, + PGSQL_INSTANCE, AlarmContact.isOptional(), // TODO.spec: PostgreSQL extensions in database and here? also decide which. Free selection or booleans/checkboxes? diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidator.java index 7d527892..e10b6e6c 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidator.java @@ -10,6 +10,8 @@ import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordP class HsPostgreSqlUserHostingAssetValidator extends HostingAssetEntityValidator { + final static String HEAD_REGEXP = "^PGU\\|"; + public HsPostgreSqlUserHostingAssetValidator() { super( PGSQL_USER, @@ -28,6 +30,6 @@ class HsPostgreSqlUserHostingAssetValidator extends HostingAssetEntityValidator @Override protected Pattern identifierPattern(final HsHostingAsset assetEntity) { final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier(); - return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$"); + return Pattern.compile(HEAD_REGEXP+webspaceIdentifier+"$|"+HEAD_REGEXP+webspaceIdentifier+"_[a-zA-Z0-9_]+$"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java index 083e69ca..ceaf2603 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java @@ -31,6 +31,12 @@ public class PasswordProperty extends StringProperty { @Override protected void validate(final List result, final String propValue, final PropertiesProvider propProvider) { + // TODO.impl: remove after legacy data is migrated + if (HashGenerator.using(hashedUsing).couldBeHash(propValue) && propValue.length() > this.maxLength()) { + // already hashed => do not validate + return; + } + super.validate(result, propValue, propProvider); validatePassword(result, propValue); } @@ -40,7 +46,7 @@ public class PasswordProperty extends StringProperty { computedBy( ComputeMode.IN_PREP, (em, entity) -> ofNullable(entity.getDirectValue(propertyName, String.class)) - .map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password)) + .map(password -> HashGenerator.using(algorithm).withRandomSalt().hashIfNotYetHashed(password)) .orElse(null)); return self(); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java index aa804916..7870ca87 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/StringProperty.java @@ -41,11 +41,19 @@ public class StringProperty

> extends ValidatableProp return self(); } + public Integer minLength() { + return this.minLength; + } + public P maxLength(final int maxLength) { this.maxLength = maxLength; return self(); } + public Integer maxLength() { + return this.maxLength; + } + public P matchesRegEx(final String... regExPattern) { this.matchesRegEx = stream(regExPattern).map(Pattern::compile).toArray(Pattern[]::new); return self(); diff --git a/src/main/resources/db/changelog/9-hs-global/9000-statistics.sql b/src/main/resources/db/changelog/9-hs-global/9000-statistics.sql new file mode 100644 index 00000000..7c4304b3 --- /dev/null +++ b/src/main/resources/db/changelog/9-hs-global/9000-statistics.sql @@ -0,0 +1,23 @@ +--liquibase formatted sql + +-- ============================================================================ +--changeset hs-global-object-statistics:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +CREATE VIEW hs_statistics_view AS +select * + from (select count, "table" as "rbac-table", '' as "hs-table", '' as "type" + from rbacstatisticsview + union all + select to_char(count(*)::int, '9 999 999 999') as "count", 'objects' as "rbac-table", objecttable as "hs-table", '' as "type" + from rbacobject + group by objecttable + union all + select to_char(count(*)::int, '9 999 999 999'), 'objects', 'hs_hosting_asset', type::text + from hs_hosting_asset + group by type + union all + select to_char(count(*)::int, '9 999 999 999'), 'objects', 'hs_booking_item', type::text + from hs_booking_item + group by type + ) as totals order by replace(count, ' ', '')::int desc; +--// diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index a9c6711d..8771ae81 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -151,3 +151,5 @@ databaseChangeLog: file: db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql - include: file: db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql + - include: + file: db/changelog/9-hs-global/9000-statistics.sql diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidatorUnitTest.java index 37c8fb85..7e7c8b5b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbDatabaseHostingAssetValidatorUnitTest.java @@ -40,7 +40,7 @@ class HsMariaDbDatabaseHostingAssetValidatorUnitTest { return HsHostingAssetEntity.builder() .type(MARIADB_DATABASE) .parentAsset(GIVEN_MARIADB_USER) - .identifier("xyz00_temp") + .identifier("MAD|xyz00_temp") .caption("some valid test MariaDB-Database") .config(new HashMap<>(ofEntries( entry("encoding", "latin1") @@ -93,8 +93,8 @@ class HsMariaDbDatabaseHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'MARIADB_DATABASE:xyz00_temp.config.unknown' is not expected but is set to 'wrong'", - "'MARIADB_DATABASE:xyz00_temp.config.encoding' is expected to be of type String, but is of type Integer" + "'MARIADB_DATABASE:MAD|xyz00_temp.config.unknown' is not expected but is set to 'wrong'", + "'MARIADB_DATABASE:MAD|xyz00_temp.config.encoding' is expected to be of type String, but is of type Integer" ); } @@ -111,6 +111,6 @@ class HsMariaDbDatabaseHostingAssetValidatorUnitTest { // then assertThat(result).containsExactly( - "'identifier' expected to match '^xyz00$|^xyz00_[a-z0-9_]+$', but is 'xyz99-temp'"); + "'identifier' expected to match '^MAD\\|xyz00$|^MAD\\|xyz00_[a-zA-Z0-9_]+$', but is 'xyz99-temp'"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidatorUnitTest.java index 97c8429b..70b823c8 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsMariaDbUserHostingAssetValidatorUnitTest.java @@ -32,7 +32,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest { .type(MARIADB_USER) .parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET) .assignedToAsset(GIVEN_MARIADB_INSTANCE) - .identifier("xyz00_temp") + .identifier("MAU|xyz00_temp") .caption("some valid test MariaDB-User") .config(new HashMap<>(ofEntries( entry("password", "Test1234") @@ -101,9 +101,9 @@ class HsMariaDbUserHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'MARIADB_USER:xyz00_temp.config.unknown' is not expected but is set to '100'", - "'MARIADB_USER:xyz00_temp.config.password' length is expected to be at min 8 but length of provided value is 5", - "'MARIADB_USER:xyz00_temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters" + "'MARIADB_USER:MAU|xyz00_temp.config.unknown' is not expected but is set to '100'", + "'MARIADB_USER:MAU|xyz00_temp.config.password' length is expected to be at min 8 but length of provided value is 5", + "'MARIADB_USER:MAU|xyz00_temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters" ); } @@ -120,6 +120,6 @@ class HsMariaDbUserHostingAssetValidatorUnitTest { // then assertThat(result).containsExactly( - "'identifier' expected to match '^xyz00$|^xyz00_[a-z0-9_]+$', but is 'xyz99-temp'"); + "'identifier' expected to match '^MAU\\|xyz00$|^MAU\\|xyz00_[a-zA-Z0-9_]+$', but is 'xyz99-temp'"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidatorUnitTest.java index 35780466..78a59288 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlDatabaseHostingAssetValidatorUnitTest.java @@ -42,7 +42,7 @@ class HsPostgreSqlDatabaseHostingAssetValidatorUnitTest { return HsHostingAssetEntity.builder() .type(PGSQL_DATABASE) .parentAsset(GIVEN_PGSQL_USER) - .identifier("xyz00_db") + .identifier("PGD|xyz00_db") .caption("some valid test PgSql-Database") .config(new HashMap<>(ofEntries( entry("encoding", "LATIN1") @@ -94,9 +94,9 @@ class HsPostgreSqlDatabaseHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'PGSQL_DATABASE:xyz00_db.bookingItem' must be null but is of type CLOUD_SERVER", - "'PGSQL_DATABASE:xyz00_db.parentAsset' must be of type PGSQL_USER but is of type PGSQL_INSTANCE", - "'PGSQL_DATABASE:xyz00_db.assignedToAsset' must be null but is of type PGSQL_INSTANCE" + "'PGSQL_DATABASE:PGD|xyz00_db.bookingItem' must be null but is of type CLOUD_SERVER", + "'PGSQL_DATABASE:PGD|xyz00_db.parentAsset' must be of type PGSQL_USER but is of type PGSQL_INSTANCE", + "'PGSQL_DATABASE:PGD|xyz00_db.assignedToAsset' must be null but is of type PGSQL_INSTANCE" ); } @@ -116,8 +116,8 @@ class HsPostgreSqlDatabaseHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'PGSQL_DATABASE:xyz00_db.config.unknown' is not expected but is set to 'wrong'", - "'PGSQL_DATABASE:xyz00_db.config.encoding' is expected to be of type String, but is of type Integer" + "'PGSQL_DATABASE:PGD|xyz00_db.config.unknown' is not expected but is set to 'wrong'", + "'PGSQL_DATABASE:PGD|xyz00_db.config.encoding' is expected to be of type String, but is of type Integer" ); } @@ -134,6 +134,6 @@ class HsPostgreSqlDatabaseHostingAssetValidatorUnitTest { // then assertThat(result).containsExactly( - "'identifier' expected to match '^xyz00$|^xyz00_[a-z0-9_]+$', but is 'xyz99-temp'"); + "'identifier' expected to match '^PGD\\|xyz00$|^PGD\\|xyz00_[a-zA-Z0-9_]+$', but is 'xyz99-temp'"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidatorUnitTest.java index 588631c2..bb589a7b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsPostgreSqlUserHostingAssetValidatorUnitTest.java @@ -35,7 +35,7 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest { .type(PGSQL_USER) .parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET) .assignedToAsset(GIVEN_PGSQL_INSTANCE) - .identifier("xyz00_temp") + .identifier("PGU|xyz00_temp") .caption("some valid test PgSql-User") .config(new HashMap<>(ofEntries( entry("password", "Test1234") @@ -104,9 +104,9 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest { // then assertThat(result).containsExactlyInAnyOrder( - "'PGSQL_USER:xyz00_temp.config.unknown' is not expected but is set to '100'", - "'PGSQL_USER:xyz00_temp.config.password' length is expected to be at min 8 but length of provided value is 5", - "'PGSQL_USER:xyz00_temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters" + "'PGSQL_USER:PGU|xyz00_temp.config.unknown' is not expected but is set to '100'", + "'PGSQL_USER:PGU|xyz00_temp.config.password' length is expected to be at min 8 but length of provided value is 5", + "'PGSQL_USER:PGU|xyz00_temp.config.password' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters" ); } @@ -123,6 +123,6 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest { // then assertThat(result).containsExactly( - "'identifier' expected to match '^xyz00$|^xyz00_[a-z0-9_]+$', but is 'xyz99-temp'"); + "'identifier' expected to match '^PGU\\|xyz00$|^PGU\\|xyz00_[a-zA-Z0-9_]+$', but is 'xyz99-temp'"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java index 6405d543..2b68e352 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/CsvDataImport.java @@ -48,6 +48,7 @@ import static net.hostsharing.hsadminng.mapper.Array.emptyArray; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assertions.fail; public class CsvDataImport extends ContextBasedTest { @@ -281,6 +282,12 @@ public class CsvDataImport extends ContextBasedTest { }).assertSuccessful(); } + // makes it possible to fail when an expression is expected + T failWith(final String message) { + fail(message); + return null; + } + void logError(final Runnable assertion) { try { assertion.run(); @@ -290,7 +297,9 @@ public class CsvDataImport extends ContextBasedTest { } void logErrors() { - assertThat(errors).isEmpty(); + final var errorsToLog = new ArrayList<>(errors); + errors.clear(); + assertThat(errorsToLog).isEmpty(); } void expectErrors(final String... expectedErrors) { @@ -305,8 +314,16 @@ public class CsvDataImport extends ContextBasedTest { } public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List expected, final List actual) { - final var sortedExpected = expected.stream().map(m -> m.replaceAll("\\s", "")).toList(); - final var sortedActual = actual.stream().map(m -> m.replaceAll("\\s", "")).toArray(String[]::new); + final var sortedExpected = expected.stream() + .map(m -> m.replaceAll("\\s+", " ")) + .map(m -> m.replaceAll("^ ", "")) + .map(m -> m.replaceAll(" $", "")) + .toList(); + final var sortedActual = actual.stream() + .map(m -> m.replaceAll("\\s+", " ")) + .map(m -> m.replaceAll("^ ", "")) + .map(m -> m.replaceAll(" $", "")) + .toArray(String[]::new); assertThat(sortedExpected).containsExactlyInAnyOrder(sortedActual); } @@ -324,11 +341,7 @@ class Columns { } int indexOf(final String columnName) { - int index = columnNames.indexOf(columnName); - if (index < 0) { - throw new RuntimeException("column name '" + columnName + "' not found in: " + columnNames); - } - return index; + return columnNames.indexOf(columnName); } } @@ -342,6 +355,12 @@ class Record { this.row = row; } + String getString(final String columnName, final String defaultValue) { + final var index = columns.indexOf(columnName); + final var value = index >= 0 && index < row.length ? row[index].trim() : null; + return value != null ? value : defaultValue; + } + String getString(final String columnName) { return row[columns.indexOf(columnName)].trim(); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java index 3092dd85..288261e7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -1,6 +1,8 @@ package net.hostsharing.hsadminng.hs.migration; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.hash.HashGenerator; +import net.hostsharing.hsadminng.hash.HashGenerator.Algorithm; import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; @@ -22,22 +24,32 @@ import org.springframework.test.annotation.Commit; import org.springframework.test.annotation.DirtiesContext; import java.io.Reader; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import static java.util.Arrays.stream; import static java.util.Map.entry; +import static java.util.Map.ofEntries; import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ALIAS; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.IPV4_NUMBER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_INSTANCE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_USER; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_DATABASE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_INSTANCE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_USER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static org.assertj.core.api.Assertions.assertThat; @@ -97,6 +109,9 @@ public class ImportHostingAssets extends ImportOfficeData { static final Integer PACKET_ID_OFFSET = 3000000; static final Integer UNIXUSER_ID_OFFSET = 4000000; static final Integer EMAILALIAS_ID_OFFSET = 5000000; + static final Integer DBINSTANCE_ID_OFFSET = 6000000; + static final Integer DBUSER_ID_OFFSET = 7000000; + static final Integer DB_ID_OFFSET = 8000000; record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference serverRef) {} @@ -104,6 +119,7 @@ public class ImportHostingAssets extends ImportOfficeData { static Map bookingItems = new WriteOnceMap<>(); static Map hives = new WriteOnceMap<>(); static Map hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type? + static Map dbUsersByEngineAndName = new WriteOnceMap<>(); @Test @Order(11010) @@ -333,6 +349,95 @@ public class ImportHostingAssets extends ImportOfficeData { """); } + @Test + @Order(15000) + void createDatabaseInstances() { + createDatabaseInstances(hostingAssets.values().stream().filter(ha -> ha.getType()==MANAGED_SERVER).toList()); + } + + @Test + @Order(15009) + void verifyDatabaseInstances() { + assumeThatWeAreImportingControlledTestData(); + + assertThat(firstOfEachType(5, PGSQL_INSTANCE, MARIADB_INSTANCE)).isEqualToIgnoringWhitespace(""" + { + 6000000=HsHostingAssetRawEntity(PGSQL_INSTANCE, vm1061|PgSql.default, vm1061-PostgreSQL default instance, MANAGED_SERVER:vm1061), + 6000001=HsHostingAssetRawEntity(MARIADB_INSTANCE, vm1061|MariaDB.default, vm1061-MariaDB default instance, MANAGED_SERVER:vm1061), + 6000002=HsHostingAssetRawEntity(PGSQL_INSTANCE, vm1050|PgSql.default, vm1050-PostgreSQL default instance, MANAGED_SERVER:vm1050), + 6000003=HsHostingAssetRawEntity(MARIADB_INSTANCE, vm1050|MariaDB.default, vm1050-MariaDB default instance, MANAGED_SERVER:vm1050), + 6000004=HsHostingAssetRawEntity(PGSQL_INSTANCE, vm1068|PgSql.default, vm1068-PostgreSQL default instance, MANAGED_SERVER:vm1068), + 6000005=HsHostingAssetRawEntity(MARIADB_INSTANCE, vm1068|MariaDB.default, vm1068-MariaDB default instance, MANAGED_SERVER:vm1068), + 6000006=HsHostingAssetRawEntity(PGSQL_INSTANCE, vm1093|PgSql.default, vm1093-PostgreSQL default instance, MANAGED_SERVER:vm1093), + 6000007=HsHostingAssetRawEntity(MARIADB_INSTANCE, vm1093|MariaDB.default, vm1093-MariaDB default instance, MANAGED_SERVER:vm1093) + } + """); + } + + @Test + @Order(15010) + void importDatabaseUsers() { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/database_user.csv")) { + final var lines = readAllLines(reader); + importDatabaseUsers(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(15019) + void verifyDatabaseUsers() { + assumeThatWeAreImportingControlledTestData(); + + assertThat(firstOfEachType(5, PGSQL_USER, MARIADB_USER)).isEqualToIgnoringWhitespace(""" + { + 7001857=HsHostingAssetRawEntity(PGSQL_USER, PGU|hsh00, hsh00, MANAGED_WEBSPACE:hsh00, PGSQL_INSTANCE:vm1050|PgSql.default, { "password": "SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$JDiZmaxU+O+ByArLY/CkYZ8HbOk0r/I8LyABnno5gQs=:NI3T500/63dzI1B07Jh3UtQGlukS6JxuS0XoxM/QgAc="}), + 7001858=HsHostingAssetRawEntity(MARIADB_USER, MAU|hsh00, hsh00, MANAGED_WEBSPACE:hsh00, MARIADB_INSTANCE:vm1050|MariaDB.default, { "password": "*59067A36BA197AD0A47D74909296C5B002A0FB9F"}), + 7001859=HsHostingAssetRawEntity(PGSQL_USER, PGU|hsh00_vorstand, hsh00_vorstand, MANAGED_WEBSPACE:hsh00, PGSQL_INSTANCE:vm1050|PgSql.default, { "password": "SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$54Wh+OGx/GaIvAia+I3k78jHGhqmYwe4+iLssmH5zhk=:D4Gq1z2Li2BVSaZrz1azDrs6pwsIzhq4+suK1Hh6ZIg="}), + 7001860=HsHostingAssetRawEntity(PGSQL_USER, PGU|hsh00_hsadmin, hsh00_hsadmin, MANAGED_WEBSPACE:hsh00, PGSQL_INSTANCE:vm1050|PgSql.default, { "password": "SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$54Wh+OGx/GaIvAia+I3k78jHGhqmYwe4+iLssmH5zhk=:D4Gq1z2Li2BVSaZrz1azDrs6pwsIzhq4+suK1Hh6ZIg="}), + 7001861=HsHostingAssetRawEntity(PGSQL_USER, PGU|hsh00_hsadmin_ro, hsh00_hsadmin_ro, MANAGED_WEBSPACE:hsh00, PGSQL_INSTANCE:vm1050|PgSql.default, { "password": "SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$UhJnJJhmKANbcaG+izWK3rz5bmhhluSuiCJFlUmDVI8=:6AC4mbLfJGiGlEOWhpz9BivvMODhLLHOnRnnktJPgn8="}), + 7004908=HsHostingAssetRawEntity(MARIADB_USER, MAU|hsh00_mantis, hsh00_mantis, MANAGED_WEBSPACE:hsh00, MARIADB_INSTANCE:vm1050|MariaDB.default, { "password": "*EA4C0889A22AAE66BBEBC88161E8CF862D73B44F"}), + 7004909=HsHostingAssetRawEntity(MARIADB_USER, MAU|hsh00_mantis_ro, hsh00_mantis_ro, MANAGED_WEBSPACE:hsh00, MARIADB_INSTANCE:vm1050|MariaDB.default, { "password": "*B3BB6D0DA2EC01958616E9B3BCD2926FE8C38383"}), + 7004931=HsHostingAssetRawEntity(PGSQL_USER, PGU|hsh00_phpPgSqlAdmin, hsh00_phpPgSqlAdmin, MANAGED_WEBSPACE:hsh00, PGSQL_INSTANCE:vm1050|PgSql.default, { "password": "SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$UhJnJJhmKANbcaG+izWK3rz5bmhhluSuiCJFlUmDVI8=:6AC4mbLfJGiGlEOWhpz9BivvMODhLLHOnRnnktJPgn8="}), + 7004932=HsHostingAssetRawEntity(MARIADB_USER, MAU|hsh00_phpMyAdmin, hsh00_phpMyAdmin, MANAGED_WEBSPACE:hsh00, MARIADB_INSTANCE:vm1050|MariaDB.default, { "password": "*3188720B1889EF5447C722629765F296F40257C2"}), + 7007520=HsHostingAssetRawEntity(MARIADB_USER, MAU|lug00_wla, lug00_wla, MANAGED_WEBSPACE:lug00, MARIADB_INSTANCE:vm1068|MariaDB.default, { "password": "*11667C0EAC42BF8B0295ABEDC7D2868A835E4DB5"}) + } + """); + } + + @Test + @Order(15020) + void importDatabases() { + try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/database.csv")) { + final var lines = readAllLines(reader); + importDatabases(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(15029) + void verifyDatabases() { + assumeThatWeAreImportingControlledTestData(); + + assertThat(firstOfEachType(5, PGSQL_DATABASE, MARIADB_DATABASE)).isEqualToIgnoringWhitespace(""" + { + 8000077=HsHostingAssetRawEntity(PGSQL_DATABASE, PGD|hsh00_vorstand, hsh00_vorstand, PGSQL_USER:PGU|hsh00_vorstand, { "encoding": "LATIN1"}), + 8000786=HsHostingAssetRawEntity(MARIADB_DATABASE, MAD|hsh00_addr, hsh00_addr, MARIADB_USER:MAU|hsh00, { "encoding": "latin1"}), + 8000805=HsHostingAssetRawEntity(MARIADB_DATABASE, MAD|hsh00_db2, hsh00_db2, MARIADB_USER:MAU|hsh00, { "encoding": "latin1"}), + 8001858=HsHostingAssetRawEntity(PGSQL_DATABASE, PGD|hsh00, hsh00, PGSQL_USER:PGU|hsh00, { "encoding": "LATIN1"}), + 8001860=HsHostingAssetRawEntity(PGSQL_DATABASE, PGD|hsh00_hsadmin, hsh00_hsadmin, PGSQL_USER:PGU|hsh00_hsadmin, { "encoding": "UTF8"}), + 8004908=HsHostingAssetRawEntity(MARIADB_DATABASE, MAD|hsh00_mantis, hsh00_mantis, MARIADB_USER:MAU|hsh00_mantis, { "encoding": "utf8"}), + 8004931=HsHostingAssetRawEntity(PGSQL_DATABASE, PGD|hsh00_phpPgSqlAdmin, hsh00_phpPgSqlAdmin, PGSQL_USER:PGU|hsh00_phpPgSqlAdmin, { "encoding": "UTF8"}), + 8004932=HsHostingAssetRawEntity(PGSQL_DATABASE, PGD|hsh00_phpPgSqlAdmin_new, hsh00_phpPgSqlAdmin_new, PGSQL_USER:PGU|hsh00_phpPgSqlAdmin, { "encoding": "UTF8"}), + 8004941=HsHostingAssetRawEntity(MARIADB_DATABASE, MAD|hsh00_phpMyAdmin, hsh00_phpMyAdmin, MARIADB_USER:MAU|hsh00_phpMyAdmin, { "encoding": "utf8"}), + 8004942=HsHostingAssetRawEntity(MARIADB_DATABASE, MAD|hsh00_phpMyAdmin_old, hsh00_phpMyAdmin_old, MARIADB_USER:MAU|hsh00_phpMyAdmin, { "encoding": "utf8"}) + } + """); + } + // -------------------------------------------------------------------------------------------- @Test @@ -447,6 +552,30 @@ public class ImportHostingAssets extends ImportOfficeData { persistHostingAssetsOfType(EMAIL_ALIAS); } + @Test + @Order(19200) + @Commit + void persistDatabaseInstances() { + System.out.println("PERSISTING db-users to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); + persistHostingAssetsOfType(PGSQL_INSTANCE, MARIADB_INSTANCE); + } + + @Test + @Order(19210) + @Commit + void persistDatabaseUsers() { + System.out.println("PERSISTING db-users to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); + persistHostingAssetsOfType(PGSQL_USER, MARIADB_USER); + } + + @Test + @Order(19220) + @Commit + void persistDatabases() { + System.out.println("PERSISTING databases to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); + persistHostingAssetsOfType(PGSQL_DATABASE, MARIADB_DATABASE); + } + @Test @Order(19900) void verifyPersistedUnixUsersWithUserId() { @@ -483,7 +612,7 @@ public class ImportHostingAssets extends ImportOfficeData { @Order(19920) void verifyHostingAssetsAreActuallyPersisted() { final var haCount = (Integer) em.createNativeQuery("SELECT count(*) FROM hs_hosting_asset", Integer.class).getSingleResult(); - assertThat(haCount).isGreaterThan(isImportingControlledTestData() ? 20 : 10000); + assertThat(haCount).isGreaterThan(isImportingControlledTestData() ? 30 : 10000); } // ============================================================================================ @@ -517,11 +646,12 @@ public class ImportHostingAssets extends ImportOfficeData { // ============================================================================================ - private void persistHostingAssetsOfType(final HsHostingAssetType hsHostingAssetType) { + private void persistHostingAssetsOfType(final HsHostingAssetType... hsHostingAssetTypes) { + final var hsHostingAssetTypeSet = stream(hsHostingAssetTypes).collect(toSet()); jpaAttempt.transacted(() -> { hostingAssets.forEach((key, ha) -> { context(rbacSuperuser); - if (ha.getType() == hsHostingAssetType) { + if (hsHostingAssetTypeSet.contains(ha.getType())) { new HostingAssetEntitySaveProcessor(em, ha) .preprocessEntity() .validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*") @@ -750,7 +880,7 @@ public class ImportHostingAssets extends ImportOfficeData { .identifier(rec.getString("name")) .caption(rec.getString("comment")) .isLoaded(true) // avoid overwriting imported userids with generated ids - .config(new HashMap<>(Map.ofEntries( + .config(new HashMap<>(ofEntries( entry("shell", rec.getString("shell")), // entry("homedir", rec.getString("homedir")), do not import, it's calculated entry("locked", rec.getBoolean("locked")), @@ -806,7 +936,7 @@ public class ImportHostingAssets extends ImportOfficeData { .parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id)) .identifier(rec.getString("name")) .caption(rec.getString("name")) - .config(Map.ofEntries( + .config(ofEntries( entry("target", targets) )) .build(); @@ -814,6 +944,102 @@ public class ImportHostingAssets extends ImportOfficeData { }); } + private void createDatabaseInstances(final List parentAssets) { + final var idRef = new AtomicInteger(0); + parentAssets.forEach(pa -> { + if (pa.getSubHostingAssets() == null) { + pa.setSubHostingAssets(new ArrayList<>()); + } + + final var pgSqlInstanceAsset = HsHostingAssetRawEntity.builder() + .type(PGSQL_INSTANCE) + .parentAsset(pa) + .identifier(pa.getIdentifier() + "|PgSql.default") + .caption(pa.getIdentifier() + "-PostgreSQL default instance") + .build(); + pa.getSubHostingAssets().add(pgSqlInstanceAsset); + hostingAssets.put(DBINSTANCE_ID_OFFSET + idRef.getAndIncrement(), pgSqlInstanceAsset); + + final var mariaDbInstanceAsset = HsHostingAssetRawEntity.builder() + .type(MARIADB_INSTANCE) + .parentAsset(pa) + .identifier(pa.getIdentifier() + "|MariaDB.default") + .caption(pa.getIdentifier() + "-MariaDB default instance") + .build(); + pa.getSubHostingAssets().add(mariaDbInstanceAsset); + hostingAssets.put(DBINSTANCE_ID_OFFSET + idRef.getAndIncrement(), mariaDbInstanceAsset); + }); + } + + private void importDatabaseUsers(final String[] header, final List records) { + HashGenerator.enableChouldBeHash(true); + final var columns = new Columns(header); + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var dbuser_id = rec.getInteger("dbuser_id"); + final var packet_id = rec.getInteger("packet_id"); + final var engine = rec.getString("engine"); + final HsHostingAssetType dbUserAssetType = "mysql".equals(engine) ? MARIADB_USER + : "pgsql".equals(engine) ? PGSQL_USER + : failWith("unknown DB engine " + engine); + final var hash = dbUserAssetType == MARIADB_USER ? Algorithm.MYSQL_NATIVE : Algorithm.SCRAM_SHA256; + final var name = rec.getString("name"); + final var password_hash = rec.getString("password_hash", HashGenerator.using(hash).withRandomSalt().hash("fake pw " + name)); + + final HsHostingAssetType dbInstanceAssetType = "mysql".equals(engine) ? MARIADB_INSTANCE + : "pgsql".equals(engine) ? PGSQL_INSTANCE + : failWith("unknown DB engine " + engine); + final var relatedWebspaceHA = hostingAssets.get(PACKET_ID_OFFSET + packet_id).getParentAsset(); + final var dbInstanceAsset = relatedWebspaceHA.getSubHostingAssets().stream() + .filter(ha -> ha.getType() == dbInstanceAssetType) + .findAny().orElseThrow(); // there is exactly one: the default instance for the given type + + final var dbUserAsset = HsHostingAssetRawEntity.builder() + .type(dbUserAssetType) + .parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id)) + .assignedToAsset(dbInstanceAsset) + .identifier(dbUserAssetType.name().substring(0, 2) + "U|" + name) + .caption(name) + .config(new HashMap<>(ofEntries( + entry("password", password_hash) + ))) + .build(); + dbUsersByEngineAndName.put(engine + ":" + name, dbUserAsset); + hostingAssets.put(DBUSER_ID_OFFSET + dbuser_id, dbUserAsset); + }); + } + + private void importDatabases(final String[] header, final List records) { + final var columns = new Columns(header); + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var database_id = rec.getInteger("database_id"); + final var engine = rec.getString("engine"); + final var owner = rec.getString("owner"); + final var owningDbUserHA = dbUsersByEngineAndName.get(engine + ":" + owner); + assertThat(owningDbUserHA).as("owning user for " + (engine + ":" + owner) + " not found").isNotNull(); + final HsHostingAssetType type = "mysql".equals(engine) ? MARIADB_DATABASE + : "pgsql".equals(engine) ? PGSQL_DATABASE + : failWith("unknown DB engine " + engine); + final var name = rec.getString("name"); + final var encoding = rec.getString("encoding").replaceAll("[-_]+", ""); + final var dbAsset = HsHostingAssetRawEntity.builder() + .type(type) + .parentAsset(owningDbUserHA) + .identifier(type.name().substring(0, 2) + "D|" + name) + .caption(name) + .config(ofEntries( + entry("encoding", type == MARIADB_DATABASE ? encoding.toLowerCase() : encoding.toUpperCase()) + )) + .build(); + hostingAssets.put(DB_ID_OFFSET + database_id, dbAsset); + }); + } + // ============================================================================================ V returning( diff --git a/src/test/resources/migration/hosting/database.csv b/src/test/resources/migration/hosting/database.csv new file mode 100644 index 00000000..e992d086 --- /dev/null +++ b/src/test/resources/migration/hosting/database.csv @@ -0,0 +1,22 @@ +database_id;engine;packet_id;name;owner;encoding + +77;pgsql;630;hsh00_vorstand;hsh00_vorstand;LATIN1 +786;mysql;630;hsh00_addr;hsh00;latin1 +805;mysql;630;hsh00_db2;hsh00;LATIN-1 + +1858;pgsql;630;hsh00;hsh00;LATIN1 +1860;pgsql;630;hsh00_hsadmin;hsh00_hsadmin;UTF8 + +4931;pgsql;630;hsh00_phpPgSqlAdmin;hsh00_phpPgSqlAdmin;UTF8 +4932;pgsql;630;hsh00_phpPgSqlAdmin_new;hsh00_phpPgSqlAdmin;utf8 +4908;mysql;630;hsh00_mantis;hsh00_mantis;UTF-8 +4941;mysql;630;hsh00_phpMyAdmin;hsh00_phpMyAdmin;utf8 +4942;mysql;630;hsh00_phpMyAdmin_old;hsh00_phpMyAdmin;utf8 + +7520;mysql;1094;lug00_wla;lug00_wla;utf8 +7521;mysql;1094;lug00_wla_test;lug00_wla;utf8 +7522;pgsql;1094;lug00_ola;lug00_ola;UTF8 +7523;pgsql;1094;lug00_ola_Test;lug00_ola;UTF8 + +7604;mysql;1112;mim00_test;mim00_test;latin1 +7605;pgsql;1112;mim00_office;mim00_office;UTF8 diff --git a/src/test/resources/migration/hosting/database_user.csv b/src/test/resources/migration/hosting/database_user.csv new file mode 100644 index 00000000..33018673 --- /dev/null +++ b/src/test/resources/migration/hosting/database_user.csv @@ -0,0 +1,17 @@ +dbuser_id;engine;packet_id;name;password_hash + +1857;pgsql;630;hsh00;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$JDiZmaxU+O+ByArLY/CkYZ8HbOk0r/I8LyABnno5gQs=:NI3T500/63dzI1B07Jh3UtQGlukS6JxuS0XoxM/QgAc= +1858;mysql;630;hsh00;*59067A36BA197AD0A47D74909296C5B002A0FB9F +1859;pgsql;630;hsh00_vorstand;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$54Wh+OGx/GaIvAia+I3k78jHGhqmYwe4+iLssmH5zhk=:D4Gq1z2Li2BVSaZrz1azDrs6pwsIzhq4+suK1Hh6ZIg= +1860;pgsql;630;hsh00_hsadmin;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$54Wh+OGx/GaIvAia+I3k78jHGhqmYwe4+iLssmH5zhk=:D4Gq1z2Li2BVSaZrz1azDrs6pwsIzhq4+suK1Hh6ZIg= +1861;pgsql;630;hsh00_hsadmin_ro;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$UhJnJJhmKANbcaG+izWK3rz5bmhhluSuiCJFlUmDVI8=:6AC4mbLfJGiGlEOWhpz9BivvMODhLLHOnRnnktJPgn8= +4931;pgsql;630;hsh00_phpPgSqlAdmin;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$UhJnJJhmKANbcaG+izWK3rz5bmhhluSuiCJFlUmDVI8=:6AC4mbLfJGiGlEOWhpz9BivvMODhLLHOnRnnktJPgn8= +4908;mysql;630;hsh00_mantis;*EA4C0889A22AAE66BBEBC88161E8CF862D73B44F +4909;mysql;630;hsh00_mantis_ro;*B3BB6D0DA2EC01958616E9B3BCD2926FE8C38383 +4932;mysql;630;hsh00_phpMyAdmin;*3188720B1889EF5447C722629765F296F40257C2 + +7520;mysql;1094;lug00_wla;*11667C0EAC42BF8B0295ABEDC7D2868A835E4DB5 +7522;pgsql;1094;lug00_ola;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$tir+cV3ZzOZeEWurwAJk+8qkvsTAWaBfwx846oYMOr4=:p4yk/4hHkfSMAFxSuTuh3RIrbSpHNBh7h6raVa3nt1c= + +7604;mysql;1112;mim00_test;*156CFD94A0594A5C3F4C6742376DDF4B8C5F6D90 +7605;pgsql;1112;mim00_office;SCRAM-SHA-256$4096:Zml4ZWQgc2FsdA==$43jziwd1o+nkfjE0zFbks24Zy5GK+km87B7vzEQt4So=:xRQntZxBxdo1JJbhkegnUFKHT0T8MDW75hkQs2S3z6k=