diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..5488e6f05 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,28 @@ +FROM bitnami/tomcat:7.0.94 as fineract + +USER root +RUN apt-get update -qq && apt-get install -y git openjdk-8-jdk wget +ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/jre + +RUN git clone https://github.com/apache/fineract.git +COPY build.gradle fineract/fineract-provider + +# To use for local fineract development (instead of installing from the latest code from Github) +# 1) Copy Dockerfile, docker-compose.yml, and the initdb directory to the root of your project (/fineract) +# 2) Edit Dockerfile in /fineract - comment out the 2 lines above (RUN git clone and COPY build.gradle) +# 3) Uncomment the 2 lines below +# 4) Update your local copy of build.gradle and replace all references to localhost with fineractmysql + +#RUN mkdir fineract +#COPY . fineract + +WORKDIR fineract +RUN ./gradlew clean war +RUN mv build/libs/fineract-provider.war /opt/bitnami/tomcat/webapps + +RUN keytool -genkey -keyalg RSA -alias tomcat -keystore /opt/bitnami/tomcat/tomcat.keystore -keypass xyz123 -storepass xyz123 -noprompt -dname "CN=Fineract, OU=Fineract, O=Fineract, L=Unknown, ST=Unknown, C=Unknown" +COPY server.xml /opt/bitnami/tomcat/conf +RUN chmod 664 /opt/bitnami/tomcat/conf/server.xml +WORKDIR /opt/bitnami/tomcat/lib +RUN wget http://central.maven.org/maven2/org/drizzle/jdbc/drizzle-jdbc/1.3/drizzle-jdbc-1.3.jar + diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..aae373960 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,25 @@ +Installing Fineract with Docker and docker-compose + +Prerequisites +============= +* docker and docker-compose installed on your machine + + +Installing a new Fineract instance +================================== + +* Clone the Fineract Github repository +* Navigate to the docker directory +* Run the following commands: + * docker-compose build + * docker-compose up -d +* Fineract will run at https://localhost:8443/fineract-provider + + +Using docker-compose for development +==================================== + +* Copy Dockerfile, docker-compose.yml, and the initdb directory to the root of your project (/fineract) +* Edit Dockerfile in /fineract - comment out the 2 lines (RUN git clone and COPY build.gradle) +* Uncomment the 2 lines in Dockerfile (RUN mkdir fineract, COPY . fineract) +* Update your local copy of build.gradle and replace all references to localhost with fineractmysql diff --git a/docker/build.gradle b/docker/build.gradle new file mode 100644 index 000000000..d0964e8c8 --- /dev/null +++ b/docker/build.gradle @@ -0,0 +1,553 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +description = '''\ +Run as: +gradle clean tomcatrunwar +''' +buildscript { + repositories { + jcenter() + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } + } + + dependencies { + classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0', + 'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0', + 'org.zeroturnaround:gradle-jrebel-plugin:1.1.2', + 'org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE' // also change springDataJpaVersion below + classpath 'org.apache.openjpa:openjpa-all:2.4.1' + classpath 'at.schmutterer.oss.gradle:gradle-openjpa:0.2.0' + classpath 'gradle.plugin.org.nosphere.apache:creadur-rat-gradle:0.2.2' + } +} + +apply plugin: "org.nosphere.apache.rat" +apply plugin: 'rebel' +apply plugin: 'license' +apply plugin: 'war' +apply plugin: 'spring-boot' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'tomcat' +apply plugin: 'project-report' +apply plugin: 'java' +apply plugin: 'openjpa' +apply plugin: 'pmd' +apply plugin: 'findbugs' + +/* define the valid syntax level for source files */ +sourceCompatibility = JavaVersion.VERSION_1_8 +/* define binary compatibility version */ +targetCompatibility = JavaVersion.VERSION_1_8 + +project.ext.springBootVersion = '1.2.8.RELEASE' +project.ext.springVersion = '4.1.9.RELEASE' +project.ext.springOauthVersion = '2.0.4.RELEASE' +project.ext.jerseyVersion = '1.17' +project.ext.springDataJpaVersion = '1.7.0.RELEASE' // also change spring-boot-gradle-plugin version above + +project.ext.mysqlUser='root' +project.ext.mysqlPassword='mysql' + + +group = 'org.apache.fineract' +buildDir = new File(rootProject.projectDir, "../build") +repositories { + jcenter() + // mavenLocal() // useful for local dev using MariaDB4j SNAPSHOTs (not needed for real-world non-SNAPHOT builds) +} +openjpa { + files = fileTree(sourceSets.main.output.classesDir).matching { + include '**/AbstractPersistableCustom.class' + include '**/domain/*.class' + + } + enforcePropertyRestrictions = true +} + +rat { + xmlOutput = false + htmlOutput = false + plainOutput = true + verbose = false +// inputDir = './..' + reportDir = new File(buildDir,'reports/rat') + excludes = [ + '**/licenses/**', + '**/*.md', + '**/*.github/*', + '**/MANIFEST.MF', + '**/*.txt', + '**/*.log', + '**/fineractdev-eclipse-preferences.epf', + '**/template-expected.html', + '**/template.mustache', + '**/.classpath', + '**/.project', + '**/.idea/**', + '**/*.ipr', + '**/*.iws', + '**/.settings/**', + '**/bin/**', + '**/.git/**', + '**/.gitignore', + '**/.gitkeep', + '**/*.iml', + //Notice files + '**/NOTICE_RELEASE', + '**/NOTICE_SOURCE', + // Swagger License + '**/src/main/resources/swagger-ui/**', + // gradle + '**/.gradle/**', + '**/gradlew', + '**/gradlew.bat', + '**/gradle/wrapper/gradle-wrapper.properties', + '**/caches/**', + '**/daemon/**', + '**/native/**', + '**/wrapper/**', + '**/build/**', + + //Api Docs + '**/api-docs/*.*', + '**/docs/system-architecture/.htaccess', + '**/docs/system-architecture/404.html', + '**/docs/system-architecture/index.html', + '**/docs/system-architecture/**/*.xml', + '**/bootstrap-3.0.0/assets/application.js', + '**/system-architecture/js/plugins.js', + + //Apache License + '**/bootstrap-3.0.0/assets/less.js', + '**/css/bootstrap-3.0.0/**/*.*', + + //Public Domain See http://www.JSON.org/js.html + '**/bootstrap-3.0.0/assets/json2.js.htm', + + // MIT License + '**/modernizr-2.6.2.min.js', + '**/css/normalize.css', + '**/assets/filesaver.js', + '**/css/fonts/glyphicons-halflings-regular.svg', + '**/assets/jszip.js', + '**/assets/jquery.js', + '**/api-docs/jquery-1.7.min.js', + '**/css/toc-0.1.2/**/*.*', + '**/docs/system-architecture/css/main.css', + '**/system-architecture/js/vendor/jquery-1.9.1.min.js', + '**/system-architecture/js/vendor/toc-0.1.2/jquery.toc.min.js', + '**/assets/respond.min.js', + '**/assets/html5shiv.js', + + //BSD License + '**/assets/uglify.js', + //Ignore out folder + '**/out/**' + ] +} + +configurations { + providedRuntime // needed for Spring Boot executable WAR + providedCompile + compile() { + exclude module: 'hibernate-entitymanager' + exclude module: 'hibernate-validator' + exclude module: 'activation' + exclude module: 'bcmail-jdk14' + exclude module: 'bcprov-jdk14' + exclude module: 'bctsp-jdk14' + exclude module: 'bval-core' + exclude module: 'org.apache.bval.bundle' + exclude module: 'bval-jsr303' + exclude module: 'c3p0' + exclude module: 'stax-api' + exclude module: 'jaxb-api' + exclude module: 'jaxb-impl' + exclude module: 'jboss-logging' + exclude module: 'itext-rtf' + exclude module: 'classworlds' + exclude module: 'jcl-over-slf4j' + exclude module: 'jul-to-slf4j' + exclude module: 'serp' + } + runtime + all*.exclude group: 'commons-logging' +} +/* Pick up dependencies based on the environemnt, defaults to production */ +if (project.hasProperty('env') && project.getProperty('env') == 'dev') { + apply from: 'dev-dependencies.gradle' +} else { + apply from: 'dependencies.gradle' +} + +/* Enable Oauth2 authentication based on environment, default to HTTP basic auth */ +if (project.hasProperty('security') && project.getProperty('security') == 'oauth') { + if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') { + copy { + from './properties/oauth/twofactor/' + into 'src/main/resources/' + include '*.properties' + } + } else { + copy { + from './properties/oauth/' + into 'src/main/resources/' + include '*.properties' + } + } +} else { + if(project.hasProperty('twofactor') && project.getProperty('twofactor') == 'enabled') { + copy { + from './properties/basicauth/twofactor/' + into 'src/main/resources/' + include '*.properties' + } + } else { + copy { + from './properties/basicauth/' + into 'src/main/resources/' + include '*.properties' + } + } +} + +task dist(type:Zip){ + baseName = 'fineractplatform' + version = qualifyVersionIfNecessary(releaseVersion) + includeEmptyDirs = true + from('../') { + fileMode = 0755 + include '*.md' + } + from('src/main/dist') { + fileMode = 0755 + include '*' + } + from('../apps') { + fileMode = 0755 + include '**/*' + into('apps') + } + from('../api-docs/') { + fileMode = 0755 + include '*' + into('api-docs') + } + from('../fineract-db/') { + fileMode = 0755 + include '*.sql' + into('database') + } + from('src/main/resources/sql/migrations') { + fileMode = 0755 + include '**/*' + into('database/migrations') + } + + from war.outputs.files + into(baseName + '-' + version) +} + +compileJava{ + dependsOn rat +} +pmd { + sourceSets = [sourceSets.main] + ignoreFailures = true +} +findbugs { + ignoreFailures = true + sourceSets = [sourceSets.main] +} + +war { + from('../licenses/binary/') { // notice the parens + into "WEB-INF/licenses/binary/" // no leading slash + } + from('../LICENSE_RELEASE') { // notice the parens + into "WEB-INF/" // no leading slash + } + from('../NOTICE_RELEASE') { // notice the parens + into "WEB-INF/" // no leading slash + } + rename ('LICENSE_RELEASE', 'LICENSE') + rename ('NOTICE_RELEASE', 'NOTICE') + + from('../DISCLAIMER') { // notice the parens + into "WEB-INF/" // no leading slash + } + war.finalizedBy(bootRepackage) +} + +license { + header rootProject.file('../APACHE_LICENSETEXT.md') + excludes(["**/*.html", "**/*.mustache", "**/package-info.java", "**/keystore.jks", "**/swagger-ui/**"]) + strictCheck true +} + +task licenseFormatBuildScripts (type:nl.javadude.gradle.plugins.license.License) { + source = fileTree(dir: '../', includes: ['**/*.bat', '**/*.sh', '**/*.sql']) +} +licenseFormat.dependsOn licenseFormatBuildScripts + +tomcatRun { + httpPort = 8080 + httpsPort = 8443 + stopPort = 8081 + stopKey= 'stopKey' + enableSSL = true + configFile = file('src/test/resources/META-INF/context.xml') +} + +tomcatRunWar { + httpPort = 8080 + httpsPort = 8443 + stopPort = 8081 + stopKey= 'stopKey' + enableSSL = true + keystoreFile = file('src/main/resources/keystore.jks') + keystorePass = 'openmf' + configFile = file('src/test/resources/META-INF/context.xml') +} + +/* http://stackoverflow.com/questions/19653311/jpa-repository-works-in-idea-and-production-but-not-in-gradle */ +sourceSets.main.output.resourcesDir = sourceSets.main.output.classesDir +sourceSets.test.output.resourcesDir = sourceSets.test.output.classesDir + +/* Exclude maria db and embedded tomcat related files for non dev builds */ +if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) { +sourceSets { + main { + java { + exclude '**/Server*' + exclude '**/MariaDB4j*' + exclude '**/EmbeddedTomcatWithSSLConfiguration.java' + } + } + test { + java { + exclude '**/core/boot/tests/**' + } + } +} +} + +sourceSets { + integrationTest { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +task integrationTest(type:Test){ + description = "Run integration tests (located in src/integrationTest/java). Starts tomcat in daemon mode before executing the tests." + it.dependsOn war + doFirst { + tomcatRunWar.daemon = true + tomcatRunWar.execute() + } + testClassesDir = project.sourceSets.integrationTest.output.classesDir + classpath = project.sourceSets.integrationTest.runtimeClasspath +} + + +import groovy.sql.Sql + +repositories { + mavenCentral() +} +configurations { + driver +} +dependencies { + driver 'org.drizzle.jdbc:drizzle-jdbc:1.3' +} + +test { + filter { + //includeTestsMatching "org.apache.fineract.infrastructure.configuration.spring.SpringConfigurationTest.*" + includeTestsMatching "org.apache.fineract.template.TemplateMergeServiceTest.*" + } +} + +URLClassLoader loader = GroovyObject.class.classLoader +configurations.driver.each {File file -> + loader.addURL(file.toURL()) +} + +task createDB<<{ + description= "Creates the Database. Needs database name to be passed (like: -PdbName=someDBname)" + def sql = Sql.newInstance( 'jdbc:mysql:thin://fineractmysql:3306/', mysqlUser, mysqlPassword, 'org.drizzle.jdbc.DrizzleDriver' ) + sql.execute( 'create database '+"`$dbName`" ) +} + +task dropDB<<{ + description= "Drops the specified database. The database name has to be passed (like: -PdbName=someDBname)" + def sql = Sql.newInstance( 'jdbc:mysql:thin://fineractmysql:3306/', mysqlUser, mysqlPassword, 'org.drizzle.jdbc.DrizzleDriver' ) + sql.execute( 'DROP DATABASE '+"`$dbName`") +} +task setBlankPassword<<{ + def sql = Sql.newInstance( 'jdbc:mysql:thin://fineractmysql:3306/', mysqlUser, mysqlPassword, 'org.drizzle.jdbc.DrizzleDriver' ) + sql.execute('USE `mifosplatform-tenants`') + sql.execute('UPDATE mifosplatform-tenants.tenants SET schema_server = \'fineractmysql\', schema_server_port = \'3306\', schema_username = \'mifos\', schema_password = \'mysql\' WHERE id=1;') +} + + +apply plugin: 'flyway' +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath "org.flywaydb:flyway-gradle-plugin:3.0" // version upgraded during Spring Boot & MariaDB4j work, as prev. used v0.2 didn't work well after *.sql moved from fineract-db to fineract-provider/src/main/resources (new version also has clearer errors, e.g. in case of missing DB) + + // NOT classpath 'org.drizzle.jdbc:drizzle-jdbc:1.3' as it is not compatible with Flyway 3.1+ (only up to 3.0) + // We require Flyway 3.1+ because =< 3.0 is not compatible with current Gradle versions (only ancient ones we originally used to use). + // Using LGPL ConnectorJ ()org.mariadb.jdbc:mariadb-java-client) instead of the BSD licensed drizzle-jdbc *IS OK* at Apache.org, + // as long as it's only used as a test library by developers at build time, and not end-user distributed, and optional. + // So it's OK here, but would not be as a default runtime dependency and in the dist JAR/WAR. + // see https://issues.apache.org/jira/browse/LEGAL-462 + // see https://issues.apache.org/jira/browse/FINERACT-761 + // see https://github.com/flyway/flyway/issues/2332 + // see https://github.com/krummas/DrizzleJDBC/issues/46 + // see https://github.com/apache/fineract/pull/525 + // see https://github.com/apache/fineract/pull/550 + classpath 'org.mariadb.jdbc:mariadb-java-client:2.4.1' + } +} + + +flyway { + url = "jdbc:mariadb://fineractmysql:3306/mifostenant-default" + driver = "org.mariadb.jdbc.Driver" + user = mysqlUser + password = mysqlPassword +} + +task migrateTenantDB<<{ + description="Migrates a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)" + + def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/core_db' + def tenantDbName = 'mifostenant-default'; + if (rootProject.hasProperty("dbName")) { + tenantDbName = rootProject.getProperty("dbName") + } + + flyway.url= "jdbc:mariadb://fineractmysql:3306/$tenantDbName" + flyway.driver = "org.mariadb.jdbc.Driver" + flyway.locations= [filePath] + /**We use ${ as the prefix for strecthy reporting, do not want them to be interpreted by Flyway**/ + flyway.placeholderPrefix = "\$\${" + flywayMigrate.execute() +} + +task showTenantDBInfo<<{ + description="Shows the migration info for a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)" + + def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/core_db' + def tenantDbName = 'mifostenant-default'; + if (rootProject.hasProperty("dbName")) { + tenantDbName = rootProject.getProperty("dbName") + } + + flyway.url= "jdbc:mariadb://fineractmysql:3306/$tenantDbName" + flyway.driver = "org.mariadb.jdbc.Driver" + flyway.locations= [filePath] + flywayInfo.execute() +} + + +task migrateTenantListDB<<{ + description="Migrates a Tenant List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)" + + def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db' + def tenantsDbName = 'mifosplatform-tenants'; + if (rootProject.hasProperty("dbName")) { + tenantsDbName = rootProject.getProperty("dbName") + } + + flyway.url= "jdbc:mariadb://fineractmysql:3306/$tenantsDbName" + flyway.driver = "org.mariadb.jdbc.Driver" + flyway.locations= [filePath] + + flywayMigrate.execute() +} + +task showTenantListDBInfo<<{ + description="Shows the migration info for a List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)" + + def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db' + def tenantsDbName = 'mifosplatform-tenants'; + if (rootProject.hasProperty("dbName")) { + tenantsDbName = rootProject.getProperty("dbName") + } + + flyway.url= "jdbc:mariadb://fineractmysql:3306/$tenantsDbName" + flyway.driver = "org.mariadb.jdbc.Driver" + flyway.locations= [filePath] + flywayInfo.execute() +} + +task repairTenantDB<<{ + description="repair migrate" + + def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db' + def tenantsDbName = 'mifosplatform-tenants'; + if (rootProject.hasProperty("dbName")) { + tenantsDbName = rootProject.getProperty("dbName") + } + + flyway.url= "jdbc:mariadb://fineractmysql:3306/$tenantsDbName" + flyway.driver = "org.mariadb.jdbc.Driver" + flyway.locations= [filePath] + flywayRepair.execute() +} + +/* +* Support publication of artifacts versioned by topic branch. +* CI builds supply `-P BRANCH_NAME=` to gradle at build time. +* If starts with 'MIFOSX-', change version +* from BUILD-SNAPSHOT => -SNAPSHOT +* e.g. 1.1.0.BUILD-SNAPSHOT => 1.0.0.MIFOSX-1234-SNAPSHOT +*/ +def qualifyVersionIfNecessary(version) { + + if (rootProject.hasProperty("BRANCH_NAME")) { + def qualifier = rootProject.getProperty("BRANCH_NAME") + if (qualifier.startsWith("MIFOSX-")) { + return version.replace('BUILD', qualifier) + } + } + return version +} + +springBoot { + mainClass = 'org.apache.fineract.ServerWithMariaDB4jApplication' +} +bootRepackage { + mainClass = 'org.apache.fineract.ServerWithMariaDB4jApplication' +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..912124b7a --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.7' +services: + fineractmysql: + image: mysql:5.7 + volumes: + - dbdata:/var/lib/mysql + - ./initdb:/docker-entrypoint-initdb.d + restart: always + environment: + MYSQL_ROOT_PASSWORD: mysql + MYSQL_USER: root + MYSQL_PASSWORD: mysql + ports: + - "3306:3306" + fineract-server: + build: + context: . + target: fineract + ports: + - 443:443 + depends_on: + - fineractmysql +volumes: + dbdata: \ No newline at end of file diff --git a/docker/initdb/01-databases.sql b/docker/initdb/01-databases.sql new file mode 100644 index 000000000..e4fa5fb54 --- /dev/null +++ b/docker/initdb/01-databases.sql @@ -0,0 +1,6 @@ +# create databases +CREATE DATABASE IF NOT EXISTS `mifosplatform-tenants`; +CREATE DATABASE IF NOT EXISTS `mifostenant-default`; + +# create root user and grant rights +GRANT ALL ON *.* TO 'root'@'%'; \ No newline at end of file diff --git a/docker/server.xml b/docker/server.xml new file mode 100644 index 000000000..3b4a0fcba --- /dev/null +++ b/docker/server.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file