diff --git a/.gitignore b/.gitignore index 9fa8570a..74f48b2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/*.iml **/*.so **/*db2jcc4.jar +**/*mssql-jdbc-12.10.1.jre11.jar **/*dxp-license-*.xml **/*hotfix*.zip **/.classpath diff --git a/Dockerfile.helper b/Dockerfile.helper index 2dac7934..81168d34 100644 --- a/Dockerfile.helper +++ b/Dockerfile.helper @@ -2,6 +2,10 @@ FROM busybox:latest ARG DATA_DIRECTORY=data -USER 1000 +COPY --chown=1000:1000 ${DATA_DIRECTORY} /container-data -COPY --chown=1000:1000 ${DATA_DIRECTORY} /container-data \ No newline at end of file +RUN ([ -d /container-data/sqlserver ] && addgroup --gid 10001 mssql && adduser -S -s /usr/sbin/nologin -h /home/mssql --disabled-password -G mssql -u 10001 mssql) || echo + +RUN ([ -d /container-data/sqlserver ] && chown -R 10001:10001 /container-data/sqlserver) || echo + +USER 1000 \ No newline at end of file diff --git a/README.markdown b/README.markdown index bb5b1d1e..5ff7d217 100644 --- a/README.markdown +++ b/README.markdown @@ -29,6 +29,8 @@ To shut down the environment, run `./gradlew stop`. - [Enable MySQL 8.4](#enable-mysql-84) - [Enable PostgreSQL 16.3](#enable-postgresql-163) +- [Enable DB2 11.5](#enable-db2-115) +- [Enable Microsoft SQL Server 2022](#enable-microsoft-sql-server-2022) - [Import a database dump](#import-a-database-dump) - [Enable database partitioning (MySQL and PostgreSQL only)](#enable-database-partitioning-mysql-and-postgresql-only) - [Configure database port](#configure-database-port) @@ -228,6 +230,26 @@ Set the `lr.docker.environment.service.enabled[postgres]` property to `true` or lr.docker.environment.service.enabled[postgres]=true ``` +#### Enable DB2 11.5 + +Set the `lr.docker.environment.service.enabled[db2]` property to `true` or `1` in `gradle.properties`. + +`gradle.properties`: + +```properties +lr.docker.environment.service.enabled[db2]=true +``` + +#### Enable Microsoft SQL Server 2022 + +Set the `lr.docker.environment.service.enabled[sqlserver]` property to `true` or `1` in `gradle.properties`. + +`gradle.properties`: + +```properties +lr.docker.environment.service.enabled[sqlserver]=true +``` + #### Import a database dump Database dump files can be added to the `./dumps` directory at the root of the Workspace. They will automatically be copied into the database container. diff --git a/build.gradle b/build.gradle index 8e6791b1..305c6b8b 100644 --- a/build.gradle +++ b/build.gradle @@ -129,13 +129,13 @@ buildDockerImage { } dependsOn ":checkForLiferayLicense" - dependsOn ":prepareDB2JDBCDriver" + dependsOn ":prepareJDBCDriver" mustRunAfter ":importDatabaseDumps" } clean { - dependsOn ":cleanPrepareDB2JDBCDriver" + dependsOn ":cleanPrepareJDBCDriver" dependsOn ":cleanPrepareHotfixes" dependsOn ":cleanPrepareSelfSignedCert" dependsOn ":cleanPrepareWebserverConfig" @@ -144,7 +144,7 @@ clean { } dockerDeploy { - dependsOn ":prepareDB2JDBCDriver" + dependsOn ":prepareJDBCDriver" dependsOn ":prepareHotfixes" dependsOn ":prepareYourKitAgent" } diff --git a/buildSrc/src/main/groovy/com/liferay/docker/workspace/environments/Config.groovy b/buildSrc/src/main/groovy/com/liferay/docker/workspace/environments/Config.groovy index ca1b0770..eb76e687 100644 --- a/buildSrc/src/main/groovy/com/liferay/docker/workspace/environments/Config.groovy +++ b/buildSrc/src/main/groovy/com/liferay/docker/workspace/environments/Config.groovy @@ -169,28 +169,40 @@ class Config { this.useClustering = this.useLiferay && this.clusterNodes > 0 if (this.services.contains("db2")) { + this.databaseType = "db2" this.useDatabase = true this.useDatabaseDB2 = true this.dockerContainerDatabase = "${this.namespace}-database-db2" } if (this.services.contains("mariadb")) { + this.databaseType = "db2" this.useDatabase = true this.useDatabaseMariaDB = true + this.dockerContainerDatabase = "${this.namespace}-database-mariadb" } if (this.services.contains("mysql")) { + this.databaseType = "mysql" this.useDatabase = true this.useDatabaseMySQL = true this.dockerContainerDatabase = "${this.namespace}-database-mysql" } if (this.services.contains("postgres")) { + this.databaseType = "postgres" this.useDatabase = true this.useDatabasePostgreSQL = true this.dockerContainerDatabase = "${this.namespace}-database-postgres" } + if (this.services.contains("sqlserver")) { + this.databaseType = "sqlserver" + this.useDatabase = true + this.useDatabaseSQLServer = true + this.dockerContainerDatabase = "${this.namespace}-database-sqlserver" + } + if (this.services.contains("webserver_http") && this.services.contains("webserver_https")) { throw new GradleException("Both HTTP and HTTPS are enabled for the webserver service. Only one protocol can be active at a time.") } @@ -277,6 +289,7 @@ class Config { public List> companyVirtualHosts = null public List composeFiles = new ArrayList() public String databaseName = "lportal" + public String databaseType = "" public boolean databasePartitioningEnabled = false public String dataDirectory = "data" public Map defaultCompanyVirtualHost = null @@ -300,6 +313,7 @@ class Config { public boolean useDatabaseMariaDB = false public boolean useDatabaseMySQL = false public boolean useDatabasePostgreSQL = false + public boolean useDatabaseSQLServer = false public boolean useLiferay = false public boolean useWebserverHttp = false public boolean useWebserverHttps = false diff --git a/buildSrc/src/main/groovy/docker-common.gradle b/buildSrc/src/main/groovy/docker-common.gradle index 63a605c0..d432ddd2 100644 --- a/buildSrc/src/main/groovy/docker-common.gradle +++ b/buildSrc/src/main/groovy/docker-common.gradle @@ -50,7 +50,7 @@ ext { return toResultRows(results, /,/) } - if (config.useDatabaseDB2) { + if (config.useDatabaseDB2 || config.useDatabaseSQLServer) { List results = waitForCommand("${getDatabaseAccessCommand(schema)} \"${sql}\"").split("\n").minus("") results.removeLast() @@ -98,7 +98,12 @@ ext { return config.companyVirtualHosts } - config.companyVirtualHosts = executeSQLQuery("select companyId, hostname, webId from VirtualHost inner join Company using (companyId) where layoutSetId = 0", config.databaseName) + if (config.useDatabaseSQLServer) { + config.companyVirtualHosts = executeSQLQuery("select VirtualHost.companyId, hostname, webId from VirtualHost inner join Company on (VirtualHost.companyId = Company.companyId) where layoutSetId = 0", config.databaseName) + } + else { + config.companyVirtualHosts = executeSQLQuery("select companyId, hostname, webId from VirtualHost inner join Company using (companyId) where layoutSetId = 0", config.databaseName) + } return config.companyVirtualHosts } @@ -134,6 +139,10 @@ ext { "docker compose exec --user db2admin -it database /opt/ibm/db2/V11.5/bin/db2" ].join("\n") } + + if (config.useDatabaseSQLServer) { + return "docker compose exec -it database /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -C -P Liferay123 -d ${config.databaseName} -Q" + } } getCompanyDefaultWebId = { diff --git a/buildSrc/src/main/groovy/docker-liferay-bundle.gradle b/buildSrc/src/main/groovy/docker-liferay-bundle.gradle index 81771d50..f8e7b4ec 100644 --- a/buildSrc/src/main/groovy/docker-liferay-bundle.gradle +++ b/buildSrc/src/main/groovy/docker-liferay-bundle.gradle @@ -12,10 +12,12 @@ project.plugins.apply "docker-common" configurations { db2 + sqlserver } dependencies { db2 group: "com.ibm.db2.jcc", name: "db2jcc", version: "db2jcc4" + sqlserver group: "com.microsoft.sqlserver", name: "mssql-jdbc", version: "12.10.1.jre11" } Closure isValidLicenseFile = { @@ -190,15 +192,19 @@ tasks.register("checkForLiferayLicense") { } } -tasks.register("prepareDB2JDBCDriver", Copy) { +Configuration dbDriverConfiguration = configurations.findByName(config.databaseType) + +tasks.register("prepareJDBCDriver", Copy) { onlyIf("using the Liferay service") { config.useLiferay } - onlyIf("using the DB2 database") { - config.useDatabaseDB2 + onlyIf("there is a database driver to install") { + dbDriverConfiguration != null } - from configurations.db2 + from { + dbDriverConfiguration + } into "configs/common/tomcat/webapps/ROOT/WEB-INF/shielded-container-lib" } diff --git a/compose-recipes/sqlserver/Dockerfile b/compose-recipes/sqlserver/Dockerfile new file mode 100644 index 00000000..4748d13d --- /dev/null +++ b/compose-recipes/sqlserver/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/mssql/server:2022-CU21-ubuntu-22.04 + +USER root + +RUN mkdir -p /var/opt/mssql/backups /var/opt/mssql/data +RUN chown -R mssql:mssql /var/opt/mssql/ +RUN touch /startup_log.txt +RUN chown mssql:mssql /startup_log.txt + +COPY entrypoint.sh /init/ +COPY init.sql /init/ +COPY reinit.sql /init +COPY restore.sql /init + +RUN chown -R mssql:mssql /init && \ + chmod a+x /init/entrypoint.sh + +USER mssql + +ENTRYPOINT ["/bin/bash", "/init/entrypoint.sh"] \ No newline at end of file diff --git a/compose-recipes/sqlserver/clustering.sqlserver.yaml b/compose-recipes/sqlserver/clustering.sqlserver.yaml new file mode 100644 index 00000000..f9906d0d --- /dev/null +++ b/compose-recipes/sqlserver/clustering.sqlserver.yaml @@ -0,0 +1,7 @@ +services: + liferay-cluster-node: + environment: + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME=com.microsoft.sqlserver.jdbc.SQLServerDriver + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=Liferay123 + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL=jdbc:sqlserver://database:1433;databaseName=lportal;Encrypt=True;TrustServerCertificate=True + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=sa \ No newline at end of file diff --git a/compose-recipes/sqlserver/entrypoint.sh b/compose-recipes/sqlserver/entrypoint.sh new file mode 100644 index 00000000..ea1926f7 --- /dev/null +++ b/compose-recipes/sqlserver/entrypoint.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +_sqlcmd="/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P ${MSSQL_SA_PASSWORD}" + +_has_backup_file() { + if [[ $(find /var/opt/mssql/backups -type f -iname "*.bak") ]]; then + echo true + fi +} + +_has_database_files() { + local database_name=${1} + + if [[ $(find /var/opt/mssql/backups -type f -iname "${database_name}.*") ]]; then + echo true + fi +} + +_is_database_present() { + local database_name=${1} + + if [[ $(${_sqlcmd} -Q "select name from sys.databases" | grep "${database_name}") ]]; then + + echo true + fi +} + +create_database() { + local database_name=${1} + + if [[ $(_is_database_present ${database_name}) ]]; then + echo "Database ${database_name} is present; skipping database creation" + elif [[ $(_has_backup_file) ]]; then + echo "Database backup found; restoring database ${database_name}..." + + sed -i "s,%DATABASE_NAME%,${database_name},g" /init/restore.sql + + local backup_file=$(find /opt/var/mssql/backups -type f -iname "*.bak") + + sed -i "s,%BACKUP_FILE%,${backup_file},g" /init/restore.sql + + ${_sqlcmd} -i /init/restore.sql + elif [[ $(_has_database_files ${database_name}) ]]; then + echo "Database files found; reattaching database ${database_name}..." + + sed -i "s,%DATABASE_NAME%,${database_name},g" /init/reinit.sql + + ${_sqlcmd} -i /init/reinit.sql + else + echo "Could not find database ${database_name}; creating database..." + + sed -i "s,%DATABASE_NAME%,${database_name},g" /init/init.sql + + ${_sqlcmd} -i /init/init.sql + fi +} + +main() { + until ${_sqlcmd} -Q "SELECT 1"; do + sleep 1 + echo "[entrypoint] Waiting for SQL Server to be available..." + done + + create_database ${COMPOSER_DATABASE_NAME} +} + +main & /opt/mssql/bin/sqlservr + +wait \ No newline at end of file diff --git a/compose-recipes/sqlserver/init.sql b/compose-recipes/sqlserver/init.sql new file mode 100644 index 00000000..735ce181 --- /dev/null +++ b/compose-recipes/sqlserver/init.sql @@ -0,0 +1,2 @@ +CREATE DATABASE %DATABASE_NAME% +GO \ No newline at end of file diff --git a/compose-recipes/sqlserver/liferay.sqlserver.yaml b/compose-recipes/sqlserver/liferay.sqlserver.yaml new file mode 100644 index 00000000..ab7c401d --- /dev/null +++ b/compose-recipes/sqlserver/liferay.sqlserver.yaml @@ -0,0 +1,10 @@ +services: + liferay: + depends_on: + database: + condition: service_healthy + environment: + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME=com.microsoft.sqlserver.jdbc.SQLServerDriver + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=Liferay123 + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL=jdbc:sqlserver://database:1433;databaseName=lportal;Encrypt=True;TrustServerCertificate=True + - LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=sa \ No newline at end of file diff --git a/compose-recipes/sqlserver/reinit.sql b/compose-recipes/sqlserver/reinit.sql new file mode 100644 index 00000000..70eec1cc --- /dev/null +++ b/compose-recipes/sqlserver/reinit.sql @@ -0,0 +1,7 @@ +USE master +GO +CREATE DATABASE %DATABASE_NAME% +ON (FILENAME = /var/opt/mssql/data/%DATABASE_NAME%.mdf), +(FILENAME = /var/opt/mssql/data/%DATABASE_NAME%.ldf) +FOR ATTACH +GO \ No newline at end of file diff --git a/compose-recipes/sqlserver/restore.sql b/compose-recipes/sqlserver/restore.sql new file mode 100644 index 00000000..764c2ce9 --- /dev/null +++ b/compose-recipes/sqlserver/restore.sql @@ -0,0 +1,5 @@ +USE master +GO +RESTORE DATABASE %DATABASE_NAME% +FROM DISK = N'%BACKUP_FILE%' WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 5 +GO \ No newline at end of file diff --git a/compose-recipes/sqlserver/service.sqlserver.yaml b/compose-recipes/sqlserver/service.sqlserver.yaml new file mode 100644 index 00000000..00c8f694 --- /dev/null +++ b/compose-recipes/sqlserver/service.sqlserver.yaml @@ -0,0 +1,30 @@ +services: + data-helper: + volumes: + - sqlserver:/container-data/sqlserver + database: + build: ./compose-recipes/sqlserver + container_name: ${NAMESPACE}-database-sqlserver + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 2G + environment: + - ACCEPT_EULA=Y + - COMPOSER_DATABASE_NAME=${DATABASE_NAME} + - MSSQL_SA_PASSWORD=Liferay123 + healthcheck: + interval: 5s + retries: 50 + start_period: 30s + test: /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P Liferay123 -Q "select name from sys.databases where name = ${DATABASE_NAME}" + timeout: 5s + ports: + - "1433:1433" + volumes: + - dumps:/var/opt/mssql/backups + - sqlserver:/var/opt/mssql/data +volumes: + sqlserver: \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 13253e84..49295b3b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -46,6 +46,7 @@ lr.docker.environment.service.enabled[mail]=false lr.docker.environment.service.enabled[mariadb]=false lr.docker.environment.service.enabled[mysql]=false lr.docker.environment.service.enabled[postgres]=false +lr.docker.environment.service.enabled[sqlserver]=false lr.docker.environment.service.enabled[webserver_http]=false lr.docker.environment.service.enabled[webserver_https]=false