FINERACT-1470: Fineract release plugin

This commit is contained in:
Aleksandar Vidakovic
2022-05-04 15:11:53 +02:00
committed by Aleksandar Vidakovic
parent b185937962
commit 40eb547a5c
160 changed files with 4954 additions and 638 deletions
@@ -0,0 +1,183 @@
/**
* 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.
*/
apply plugin: 'io.spring.dependency-management'
// Configuration for the dependency management plugin
// https://github.com/spring-gradle-plugins/dependency-management-plugin
dependencyManagement {
imports {
mavenBom 'org.springframework:spring-framework-bom:5.3.19'
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.7'
mavenBom 'org.jetbrains.kotlin:kotlin-bom:1.6.21'
mavenBom 'org.junit:junit-bom:5.8.2'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.13.2.1'
mavenBom 'io.cucumber:cucumber-bom:7.3.3'
mavenBom 'io.netty:netty-bom:4.1.76.Final'
mavenBom 'org.mockito:mockito-bom:4.5.1'
mavenBom 'io.github.swagger2markup:swagger2markup-bom:1.3.4'
}
dependencies {
// We use fixed versions, instead of inheriting them from the Spring BOM, to be able to be on more recent ones.
// We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things.
// We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available.
dependency 'org.slf4j:slf4j-api:1.7.36'
dependency 'org.slf4j:slf4j-simple:1.7.36'
dependency 'org.slf4j:jcl-over-slf4j:1.7.36'
dependency 'org.slf4j:jul-to-slf4j:1.7.36'
dependency 'org.slf4j:log4j-over-slf4j:1.7.36'
dependency 'ch.qos.logback:logback-core:1.2.11'
dependency 'ch.qos.logback:logback-classic:1.2.11'
dependency 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10'
dependency 'com.google.guava:guava:31.1-jre'
dependency 'com.google.code.gson:gson:2.9.0'
dependency 'com.google.truth:truth:1.1.3'
dependency 'com.google.truth.extensions:truth-java8-extension:1.1.3'
dependency 'com.google.googlejavaformat:google-java-format:1.15.0'
dependency ('org.apache.commons:commons-email:1.5') {
exclude 'com.sun.mail:javax.mail'
exclude 'javax.activation:activation'
}
dependency 'commons-io:commons-io:2.11.0'
dependency 'com.github.librepdf:openpdf:1.3.27'
dependency ('org.mnode.ical4j:ical4j:3.2.2') {
exclude 'com.sun.mail:javax.mail'
}
dependency 'org.quartz-scheduler:quartz:2.3.2'
dependency 'com.amazonaws:aws-java-sdk-s3:1.12.210'
dependency 'org.ehcache:ehcache:3.10.0'
dependency 'com.github.spullara.mustache.java:compiler:0.9.10'
dependency 'com.jayway.jsonpath:json-path:2.7.0'
dependency 'org.apache.tika:tika-core:2.3.0'
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
dependency 'jakarta.management.j2ee:jakarta.management.j2ee-api:1.1.4'
dependency 'jakarta.jms:jakarta.jms-api:2.0.3'
dependency 'org.glassfish.jaxb:jaxb-runtime:2.3.6' // Swagger needs exactly this version
dependency 'org.apache.bval:org.apache.bval.bundle:2.0.5'
dependency 'joda-time:joda-time:2.10.14'
dependency 'io.github.classgraph:classgraph:4.8.146'
dependency 'org.awaitility:awaitility:4.2.0'
dependency 'com.github.spotbugs:spotbugs-annotations:4.6.0'
dependency 'javax.cache:cache-api:1.1.1'
dependency 'org.mock-server:mockserver-junit-jupiter:5.13.2'
dependency 'org.webjars:webjars-locator-core:0.50'
dependency 'com.icegreen:greenmail-junit5:1.6.8'
dependency 'com.intuit.karate:karate-junit5:1.2.0.RC6'
// fineract client dependencies
dependency "com.squareup.retrofit2:retrofit:2.9.0"
dependency "com.squareup.retrofit2:retrofit-mock:2.9.0"
dependency "com.squareup.retrofit2:adapter-java8:2.9.0"
dependency "com.squareup.retrofit2:adapter-rxjava2:2.9.0"
dependency "com.squareup.retrofit2:adapter-rxjava3:2.9.0"
dependency "com.squareup.retrofit2:adapter-guava:2.9.0"
dependency "com.squareup.retrofit2:converter-wire:2.9.0"
dependency "com.squareup.retrofit2:converter-jackson:2.9.0"
dependency "com.squareup.retrofit2:converter-simplexml:2.9.0"
dependency "com.squareup.retrofit2:converter-jaxb:2.9.0"
dependency "com.squareup.retrofit2:converter-java8:2.9.0"
dependency "com.squareup.retrofit2:converter-scalars:2.9.0"
dependency "com.squareup.retrofit2:converter-gson:2.9.0"
dependency "com.squareup.retrofit2:converter-protobuf:2.9.0"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.common:1.0.1"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.1"
dependency "org.apache.oltu.oauth2:org.apache.oltu.oauth2.httpclient4:1.0.1"
dependency "com.squareup.okhttp3:okhttp:4.9.3"
dependency "com.squareup.okhttp3:okcurl:4.9.3"
dependency "com.squareup.okhttp3:logging-interceptor:4.9.3"
dependency "com.squareup.okhttp3:okhttp-apache:4.9.3"
dependency "com.squareup.okhttp3:okhttp-android-support:4.9.3"
dependency "com.squareup.okhttp3:okhttp-urlconnection:4.9.3"
dependency "com.squareup.okhttp3:okhttp-sse:4.9.3"
dependency "io.gsonfire:gson-fire:1.8.5"
dependency "com.google.code.findbugs:jsr305:3.0.2"
dependency "commons-codec:commons-codec:1.15"
dependency "org.projectlombok:lombok:1.18.24"
dependency 'org.bouncycastle:bcpkix-jdk15to18:1.70'
dependency 'org.bouncycastle:bcprov-jdk15to18:1.70'
dependency 'org.bouncycastle:bcprov-jdk15on:1.70'
dependency 'org.bouncycastle:bcpg-jdk15on:1.70'
dependency 'org.eclipse.jgit:org.eclipse.jgit:6.1.0.202203080745-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.1.0.202203080745-r'
dependency 'org.tmatesoft.svnkit:svnkit:1.10.5'
dependency 'com.vdurmont:semver4j:3.1.0'
dependency 'org.beryx:text-io:3.4.1'
dependency 'org.springdoc:springdoc-openapi-ui:1.6.8'
dependency 'org.springdoc:springdoc-openapi-webmvc-core:1.6.8'
dependency 'org.springdoc:springdoc-openapi-common:1.6.8'
dependency 'org.springdoc:springdoc-openapi-security:1.6.8'
dependency 'org.springdoc:springdoc-openapi-javadoc:1.6.8'
dependency 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.5.0'
dependency ('org.apache.activemq:activemq-broker:5.17.1') {
exclude 'javax.annotation:javax.annotation-api'
}
dependency 'io.swagger:swagger-annotations:1.6.6'
dependency 'io.swagger.core.v3:swagger-annotations:2.2.0'
dependency ('io.swagger.core.v3:swagger-jaxrs2:2.2.0') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency ('io.swagger.core.v3:swagger-core:2.2.0') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency "jakarta.annotation:jakarta.annotation-api:1.3.5"
dependency 'jakarta.activation:jakarta.activation-api:1.2.2'
dependency ('com.sun.mail:jakarta.mail:1.6.7') {
exclude 'com.sun.activation:jakarta.activation'
}
dependency ('jakarta.xml.bind:jakarta.xml.bind-api:2.3.3') {
exclude 'jakarta.activation:jakarta.activation-api'
}
dependency ('org.liquibase:liquibase-core:4.9.1') {
exclude 'javax.xml.bind:jaxb-api'
}
dependency ('org.dom4j:dom4j:2.1.3') {
exclude 'relaxngDatatype:relaxngDatatype' // already in com.sun.xml.bind:jaxb-osgi:2.3.0.1
// FINERACT-940 && FINERACT-966 https://github.com/spotbugs/spotbugs/issues/1128
exclude 'xpp3:xpp3'
exclude 'pull-parser:pull-parser'
}
dependency 'org.owasp.esapi:esapi:2.4.0.0'
dependencySet(group: 'org.apache.poi', version: '5.2.2') {
entry 'poi'
entry 'poi-ooxml'
entry 'poi-ooxml-schemas'
}
dependencySet(group: 'io.rest-assured', version: '4.5.1') {
entry 'rest-assured'
entry 'json-path'
entry 'xml-path'
}
}
}
@@ -0,0 +1,263 @@
/**
* 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.
*/
apply plugin: 'org.apache.fineract.gradle'
apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle"
fineract {
config {
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
doc {
url = 'git@github.com:apache/fineract-site.git'
directory = "${System.getProperty("java.io.tmpdir")}/fineract-site"
branch = "asf-site"
}
git {
dir = "${projectDir.absolutePath}/.git"
sections = [
[
section: "user",
name: "name",
value: "${findProperty('fineract.config.name')}",
],
[
section: "user",
name: "email",
value: "${findProperty('fineract.config.email')}",
],
[
section: "user",
name: "signingkey",
value: "${findProperty('fineract.config.gnupg.keyName')}",
],
[
section: "commit",
name: "gpgsign",
value: "true",
],
]
}
template {
templateDir = "${projectDir}/buildSrc/src/main/resources"
}
gpg {
keyName = "${findProperty('fineract.config.gnupg.keyName')}"
publicKeyring = "${findProperty('fineract.config.gnupg.publicKeyring')}"
secretKeyring = "${findProperty('fineract.config.gnupg.secretKeyring')}"
password = "${findProperty('fineract.config.gnupg.password')}"
}
smtp {
host = 'smtp.gmail.com'
username = "${findProperty('fineract.config.smtp.username')}"
password = "${findProperty('fineract.config.smtp.password')}"
tls = true
ssl = true
}
subversion {
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
revision = 'HEAD'
}
jira {
url = 'https://issues.apache.org/jira/rest/api/2/'
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
}
confluence {
url = 'https://cwiki.apache.org/confluence/rest/api/'
username = "${findProperty('fineract.config.username')}"
password = "${findProperty('fineract.config.password')}"
}
}
steps = [
test: [
order: 0,
description: 'Test',
email: [
from: "${findProperty('fineract.config.email')}",
to: "${findProperty('fineract.config.email')}",
mime: 'text/plain',
subject: "[FINERACT] [TEST] 📣 🏁 🗳️ 🚀 📈 🔀 🌠 Propose new release ${version}",
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl"
]
],
subversion: [
url: 'https://dist.apache.org/repos/dist/dev/fineract',
directory: "${System.getProperty("java.io.tmpdir")}"
],
jira: [
query: "project in (\'FINERACT\') and fixVersion = ${findProperty('fineract.release.version')} and status in (CLOSED, RESOLVED)"
],
template: [
templateFile: "${projectDir}/buildSrc/src/main/resources/jira/changelog.txt.ftl"
],
gpg: [
file: "${projectDir}/buildSrc/build/libs/buildSrc.jar"
]
],
step1: [
order: 1,
description: 'Send heads-up email to dev mailing list',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step01.headsup.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step01.headsup.message.ftl"
]
]
],
step2: [
order: 2,
description: 'Clean up JIRA',
],
step3: [
order: 3,
description: 'Create a release branch',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step03.branch.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step03.branch.message.ftl"
]
]
],
step4: [
order: 4,
description: 'Freeze JIRA version',
],
step5: [
order: 5,
description: 'Create release tag',
git: [
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/git/release.step05.tag.message.ftl"
]
]
],
step6: [
order: 6,
description: 'Create a distribution',
],
step7: [
order: 7,
description: 'Sign the distribution artifacts',
gpg: [
file: "${rootDir}/fineract-war/build/distributions/apache-fineract-binary-${project.version}.tar.gz"
]
],
step8: [
order: 8,
description: 'Upload distribution artifacts to staging environment',
subversion: [
url: 'https://dist.apache.org/repos/dist/dev/fineract',
directory: "${System.getProperty("java.io.tmpdir")}/fineract-dist-dev"
],
],
step9: [
order: 9,
description: 'Verify distribution in staging environment',
],
step10: [
order: 10,
description: 'Start voting on the dev mailing list',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step10.vote.message.ftl"
]
]
],
step11: [
order: 11,
description: 'Conclude the vote and announce results',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step11.vote.message.ftl"
]
]
],
step12: [
order: 12,
description: 'Upload distribution artifacts to release environment',
subversion: [
url: 'https://dist.apache.org/repos/dist/release/fineract',
directory: "${System.getProperty("java.io.tmpdir")}/fineract-dist-release"
],
],
step13: [
order: 13,
description: 'Close release branch and merge to \'develop\'',
],
step14: [
order: 14,
description: 'Update website (not yet automated)',
],
step15: [
order: 15,
description: 'Send email to announcement mailing list',
jira: [
projectId: "12319420",
query: "project in (\'FINERACT\') and fixVersion = ${findProperty('fineract.release.version')} and status in (CLOSED, RESOLVED, WONTFIX)"
],
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step15.announce.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step15.announce.message.ftl"
]
]
]
]
}
@@ -0,0 +1,581 @@
/**
* 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.
*/
package org.apache.fineract.gradle
import groovy.json.JsonSlurper
import org.apache.commons.io.FileUtils
import org.apache.commons.io.filefilter.NotFileFilter
import org.apache.commons.io.filefilter.PrefixFileFilter
import org.apache.fineract.gradle.service.*
import org.beryx.textio.TextIO
import org.beryx.textio.TextIoFactory
import org.beryx.textio.TextTerminal
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class FineractPlugin implements Plugin<Project> {
private static final Logger log = LoggerFactory.getLogger(FineractPlugin.class)
private JiraService jiraService
private ConfluenceService confluenceService
private SubversionService subversionService
private EmailService emailService
private GpgService gpgService
private TemplateService templateService
private GitService gitService
private Map<String, ?> context;
static {
System.setProperty("org.beryx.textio.TextTerminal", "org.beryx.textio.system.SystemTextTerminal")
}
void apply(Project project) {
def extension = project.extensions.create("fineract", FineractPluginExtension, project)
project.fineract.extensions.create("config", FineractPluginExtension.FineractPluginConfig)
project.fineract.config.extensions.create("doc", FineractPluginExtension.FineractPluginConfigDoc)
project.fineract.config.extensions.create("template", FineractPluginExtension.FineractPluginConfigTemplate)
project.fineract.config.extensions.create("smtp", FineractPluginExtension.FineractPluginConfigSmtp)
project.fineract.config.extensions.create("subversion", FineractPluginExtension.FineractPluginConfigSubversion)
project.fineract.config.extensions.create("jira", FineractPluginExtension.FineractPluginConfigJira)
project.fineract.config.extensions.create("confluence", FineractPluginExtension.FineractPluginConfigConfluence)
project.fineract.config.extensions.create("gpg", FineractPluginExtension.FineractPluginConfigGpg)
project.fineract.config.extensions.create("git", FineractPluginExtension.FineractPluginConfigGit)
project.afterEvaluate {
this.templateService = new TemplateService(extension.config.template)
this.jiraService = new JiraService(extension.config.jira)
this.confluenceService = new ConfluenceService(extension.config.confluence)
this.subversionService = new SubversionService(extension.config.subversion)
this.emailService = new EmailService(extension.config.smtp)
this.gpgService = new GpgService(extension.config.gpg)
this.gitService = new GitService(extension.config.git, extension.config.gpg)
this.context = context(project)
}
project.tasks.register("fineractDocPublish") {
dependsOn(":fineract-doc:doc")
doLast {
log.warn("Fineract Publish Doc...")
def url = extension.config.doc.url?:"git@github.com:apache/fineract-site.git";
def branch = extension.config.doc.branch?:"asf-site"
def directory = extension.config.doc.directory?:System.getProperty("java.io.tmpdir")+"/fineract-site"
def d = new File(directory)
if(d.exists()) {
gitService.pull(directory)
} else {
gitService.clone(url, branch, directory)
gitService.config(directory, extension.config.git.sections)
}
def source = new File("fineract-doc/build/docs/html/en");
def target = new File(directory + "/docs/current")
if(!target.name) {
target.mkdirs()
}
FileUtils.copyDirectory(source, target, new NotFileFilter(new PrefixFileFilter(".asciidoctor")), true)
gitService.commit(directory)
}
}
project.tasks.register("fineractReleaseTest") {
doFirst {
// emojis:
// https://gist.github.com/parmentf/035de27d6ed1dce0b36a
// https://emojipedia.org/
// https://unicode.org/emoji/charts/full-emoji-list.html
FineractPluginExtension.FineractPluginStep step = step(extension, "test")
// TODO:
// - jira:
// - move tickets to new version
// jira query
FineractPluginExtension.FineractPluginJiraParams result = jiraService.search(step.jira)
log.warn(">>>>>>>>>>>>>>>> ISSUE: ${result.result[0]?.id} - ${result.result[0]?.key} - ${result.result[0]?.fields?['summary']} - ${result.result[0]?.fields?['fixVersions']}")
/*
// confluence create
def content = new ConfluenceService.ConfluenceContent(title: "1.7.0 - Apache Fineract",
space: new ConfluenceService.ConfluenceSpace(key: "FINERACT"),
ancestors: [new ConfluenceService.ConfluenceParent(id: "75974324")],
body: new ConfluenceService.ConfluenceBody(storage: new ConfluenceService.ConfluenceStorage(value: "<h1>Release Artifacts</h1>")))
confluenceService.createContent(content)
// confluence get
ConfluenceService.ConfluenceContent content = confluenceService.getContent("211881930")
log.warn(">>>>>>>>>>>>>>>>>>>>> CONFLUENCE: ${content.id} - ${content.title} - ${content.body?.storage?.value}")
// confluence update
content.body?.storage?.value = "<h1>Release Artifacts</h1><p><ac:structured-macro ac:macro-id=\"ac1cb3cb-84d4-45aa-820b-05cc42d39dd9\" ac:name=\"jira\" ac:schema-version=\"1\"><ac:parameter ac:name=\"server\">ASF JIRA</ac:parameter><ac:parameter ac:name=\"columnIds\">issuekey,summary,issuetype,created,updated,duedate,assignee,reporter,customfield_12311032,customfield_12311037,customfield_12311022,customfield_12311027,priority,status,resolution</ac:parameter><ac:parameter ac:name=\"columns\">key,summary,type,created,updated,due,assignee,reporter,Priority,Priority,Priority,Priority,priority,status,resolution</ac:parameter><ac:parameter ac:name=\"maximumIssues\">50</ac:parameter><ac:parameter ac:name=\"jqlQuery\">project = FINERACT AND fixVersion = 1.7.0 AND status not in (Open)</ac:parameter><ac:parameter ac:name=\"serverId\">5aa69414-a9e9-3523-82ec-879b028fb15b</ac:parameter></ac:structured-macro></p><p><br/></p>"
content.space = new ConfluenceService.ConfluenceSpace(key: "FINERACT")
content.status = null
content.version = new ConfluenceService.ConfluenceVersion(number: 3)
// confluence update
content = confluenceService.updateContent(content.id, content)
log.warn(">>>>>>>>>>>>>>>>>>>>> CONFLUENCE: ${content.id} - ${content.title} - ${content.body?.storage?.value}")
// confluence delete
ConfluenceService.ConfluenceResponse response = confluenceService.deleteContent("211881930")
log.warn(">>>>>>>>>>>>>>>>>>>>> CONFLUENCE: ${response?.statusCode} - ${response?.message}")
// jira versions
def versions = jiraService.getProjectVersions("12319420")
versions.findAll {
log.warn(">>>> VERSION: ${it.id} - ${it.name} - ${it.description}")
}
def version = jiraService.getVersion("12351431")
log.warn(">>>> !!! VERSION: ${version.id} - ${version.name} - ${version.description} - ${version.archived}")
version.archived = !version.archived
jiraService.updateVersion(version)
version = jiraService.getVersion("12351431")
log.warn(">>>> === VERSION: ${version.id} - ${version.name} - ${version.description} - ${version.archived}")
// jira projects
def projects = jiraService.getProjects()
projects.findAll {
log.warn(">>>>>>>>>>>> PROJECT: ${it.id} - ${it.key} - ${it.name}")
}
// jira info
Map<String, Object> info = jiraService.serverInfo()
log.warn(">>>>>>>>>>>>>>>> INFO: ${info}")
// jira query
FineractPluginExtension.FineractPluginJiraParams result = jiraService.search(step.jira)
log.warn(">>>>>>>>>>>>>>>> INFO: ${result.result.size()}")
// format with template
def tmp = templateService.render(step.template, [issues: result.result])
log.warn(tmp.output)
*/
}
}
// TODO: step for creating release Jira ticket missing!
// step 1
project.tasks.register("fineractReleaseStep1") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step1")
String version = project.properties?['fineract.release.version']
String issue = project.properties?['fineract.release.issue']
String date = project.properties?['fineract.release.date']
if(!version || !issue || !date) {
TextIO textIO = TextIoFactory.getTextIO()
if(!version) {
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
if(!issue) {
issue = textIO.newStringInputReader()
.withMaxLength(4)
.withPattern("\\d+")
.read("Jira Issue");
}
if(!date) {
date = textIO.newStringInputReader()
.withMaxLength(10)
.withPattern("\\d\\d\\d\\d-\\d\\d-\\d\\d")
.read("Date");
}
TextTerminal terminal = textIO.getTextTerminal()
terminal.printf("\nResult: %s - FINERACT-%s - %s", version, issue, date)
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.issue'] = issue
this.context?.project?['fineract.release.date'] = date
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 2
project.tasks.register("fineractReleaseStep2") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step2")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step2")
}
}
// step 3
project.tasks.register("fineractReleaseStep3") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step3")
String version = project.properties?['fineract.release.version']
String date = project.properties?['fineract.release.date']
if(!version || !date) {
TextIO textIO = TextIoFactory.getTextIO()
if(!version) {
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
if(!date) {
date = textIO.newStringInputReader()
.withMaxLength(10)
.withPattern("\\d\\d\\d\\d-\\d\\d-\\d\\d")
.read("Date");
}
TextTerminal terminal = textIO.getTextTerminal()
terminal.printf("\nResult: %s - %s", version, date)
}
// TODO: input validation, see FINERACT-1610
// TODO: create release branch, see FINERACT-1611
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.date'] = date
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 4
project.tasks.register("fineractReleaseStep4") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step4")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step4")
}
}
// step 5
project.tasks.register("fineractReleaseStep5") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step5")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
if (!version) {
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
}
this.context?.project?['fineract.release.version'] = version
def params = processGitParams(step.git, this.context)
params.tag = params.tag?:version
gitService.createTag(params.tag, params.message)
}
}
// step 6
project.tasks.register("fineractReleaseStep6") {
dependsOn(":fineract-war:binaryDistTar")
}
// step 7
project.tasks.register("fineractReleaseStep7") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step7")
gpgService.sign(step.gpg)
def md5 = gpgService.md5(step.gpg)
def md5File = new File("${step.gpg.file}.md5")
md5File.write md5
def sha512 = gpgService.sha512(step.gpg)
def sha512File = new File("${step.gpg.file}.sha512")
sha512File.write sha512
}
}
// step 8
project.tasks.register("fineractReleaseStep8") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step8")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
subversionService.checkout(step.subversion)
def directory = step.subversion.directory?:System.getProperty("java.io.tmpdir") + "/fineract-dist-dev"
def source = new File("fineract-war/build/distributions")
def target = new File("${directory}/${version}")
FileUtils.copyDirectory(source, target, true)
subversionService.commit(step.subversion)
}
}
// step 9
project.tasks.register("fineractReleaseStep9") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step9")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step9")
}
}
// step 10
project.tasks.register("fineractReleaseStep10") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step10")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO();
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 11
project.tasks.register("fineractReleaseStep11") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step11")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
def jsonSlurper = new JsonSlurper()
def data = jsonSlurper.parse(new File("buildSrc/src/main/resources/vote/result.${version}.json"))
this.context?.project?['fineract.vote'] = data
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
// step 12
project.tasks.register("fineractReleaseStep12") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step12")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
subversionService.checkout(step.subversion)
def directory = step.subversion.directory?:System.getProperty("java.io.tmpdir") + "/fineract-dist-release"
def source = new File("fineract-war/build/distributions")
def target = new File("${directory}/${version}")
FileUtils.copyDirectory(source, target, true)
subversionService.commit(step.subversion)
}
}
// step 13
project.tasks.register("fineractReleaseStep13") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step13")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step13")
}
}
// step 14
project.tasks.register("fineractReleaseStep14") {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step14")
// TODO: implement this, see FINERACT-1611
printInstructions(project, "step14")
}
}
// step 15
project.tasks.register("fineractReleaseStep15") {
doFirst {
log.warn("Release step 15: send email to announcement mailing list")
FineractPluginExtension.FineractPluginStep step = step(extension, "step15")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
FineractPluginExtension.FineractPluginJiraParams issues = jiraService.search(step.jira)
def versions = jiraService.getProjectVersions(step.jira.projectId)
def filteredVersions = versions.findAll {
log.warn(">>>> VERSION: ${it.id} - ${it.name} - ${it.description}")
it.name == version
}
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.issues'] = issues.result
this.context?.project?['fineract.release.projectId'] = step.jira.projectId
this.context?.project?['fineract.release.versionId'] = filteredVersions[0]?.id
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
}
}
}
private FineractPluginExtension.FineractPluginStep step(FineractPluginExtension extension, String id) {
FineractPluginExtension.FineractPluginStep step = extension.steps[id]
if(step) {
log.warn("Fineract release step ${step.order}: ${step.description}.")
} else {
throw new RuntimeException("Could not find any parameters for step with ID '${id}'")
}
return step
}
private FineractPluginExtension.FineractPluginEmailParams processEmailParams(FineractPluginExtension.FineractPluginEmailParams params, Object data) {
if(params.subjectTemplate) {
def result = templateService.render(params.subjectTemplate, data)
params.subject = result.output
}
if(params.messageTemplate) {
def result = templateService.render(params.messageTemplate, data)
params.message = result.output
}
return params
}
private FineractPluginExtension.FineractPluginGitParams processGitParams(FineractPluginExtension.FineractPluginGitParams params, Object data) {
if(params.messageTemplate) {
def result = templateService.render(params.messageTemplate, data)
params.message = result.output
}
return params
}
private Map<String, ?> context(Project project) {
return Map.of("project", project.getProperties().findAll { it.key != "password"})
}
private void printInstructions(Project project, String step) {
String version = project.properties?['fineract.release.version']?:"0.0.0"
this.context?.project?['fineract.release.version'] = version
FineractPluginExtension.FineractPluginTemplateParams result = templateService.render(new FineractPluginExtension.FineractPluginTemplateParams(templateFile: new File("buildSrc/src/main/resources/instructions/${step}.txt.ftl")), this.context)
log.warn(result.output)
}
}
@@ -0,0 +1,164 @@
/**
* 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.
*/
package org.apache.fineract.gradle
import org.apache.fineract.gradle.service.JiraService
import org.gradle.api.Project
class FineractPluginExtension {
Project project
Map<String, FineractPluginStep> steps = [:]
FineractPluginExtension(Project project) {
this.project = project
}
static class FineractPluginConfig {
String username
String password
}
static class FineractPluginConfigDoc {
String url
String directory
String branch
}
static class FineractPluginConfigJira {
String url
String username
String password
String project
}
static class FineractPluginConfigConfluence {
String url
String username
String password
String project
}
static class FineractPluginConfigGpg {
String keyName
String publicKeyring
String secretKeyring
String password
}
static class FineractPluginConfigTemplate {
String templateDir
}
static class FineractPluginConfigSmtp {
String host
int port
String username
String password
boolean tls
boolean ssl
}
static class FineractPluginConfigGit {
String username
String password
String dir
boolean dryRun
List<FineractPluginConfigGitSection> sections
}
static class FineractPluginConfigGitSection {
String section
String subsection
String name
String value
}
static class FineractPluginConfigSubversion {
String username
String password
String revision
}
static class FineractPluginStep {
int order
String description
FineractPluginEmailParams email
FineractPluginJiraParams jira
FineractPluginTemplateParams template
FineractPluginConfluenceParams confluence
FineractPluginGitParams git
FineractPluginSubversionParams subversion
FineractPluginGpgParams gpg
}
static class FineractPluginEmailParams {
String from
String name
String to
String cc
String bcc
String mime
String subject
FineractPluginTemplateParams subjectTemplate
String message
FineractPluginTemplateParams messageTemplate
}
static class FineractPluginJiraParams {
String command
String projectId
String fields = "*all"
String query
List<JiraService.JiraIssue> result = new ArrayList<>()
int pageOffset = 0
int pageSize= 50
int total = 1000
List<String> includes = ["summary", "status", "assignee", "fixVersions"]
}
static class FineractPluginConfluenceParams {
String title
String content
Integer ancestor
}
static class FineractPluginGitParams {
String tag
String message
FineractPluginTemplateParams messageTemplate
}
static class FineractPluginSubversionParams {
String url
String command
String revision = "HEAD"
String directory
}
static class FineractPluginTemplateParams {
String template
String templateFile
String output
String outputFile
}
static class FineractPluginGpgParams {
String file
}
}
@@ -0,0 +1,209 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import okhttp3.ConnectionPool
import okhttp3.Credentials
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.apache.fineract.gradle.FineractPluginExtension.FineractPluginConfigConfluence
import org.jetbrains.annotations.NotNull
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.*
import java.util.concurrent.TimeUnit
class ConfluenceService {
private static final Logger log = LoggerFactory.getLogger(ConfluenceService.class)
private ConfluenceApi api
ConfluenceService(FineractPluginConfigConfluence config) {
def credentials = Credentials.basic(config.username, config.password)
final ConnectionPool connectionPool = new ConnectionPool(5, 60, TimeUnit.SECONDS);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(log::debug)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder okClient = new OkHttpClient.Builder()
.connectionPool(connectionPool)
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
@Override
Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
def request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
})
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(config.url)
.client(okClient.build())
.addConverterFactory(JacksonConverterFactory.create(mapper))
Retrofit retrofit = builder.build()
this.api = retrofit.create(ConfluenceApi.class)
}
ConfluenceContent getContent(String id) {
this.api.getContentById(id).execute().body()
}
ConfluenceContent createContent(ConfluenceContent content) {
this.api.postContent(content).execute().body()
}
ConfluenceContent updateContent(String id, ConfluenceContent content) {
this.api.putContent(id, content).execute().body()
}
ConfluenceResponse deleteContent(String id) {
this.api.deleteContentById(id).execute().body()
}
static class ConfluenceContent {
String id
String type
String title
String status
String self
List<ConfluenceParent> ancestors
ConfluenceSpace space
ConfluenceBody body
ConfluenceVersion version
ConfluenceHistory history
}
static class ConfluenceContentResult {
List<ConfluenceContent> results
private int start
private int limit
private int size
}
static class ConfluenceParent {
String id
String type
}
static class ConfluenceVersion {
ConfluenceCreatedBy by
Date when
String message
Integer number
}
static class ConfluenceCreatedBy {
String username
String displayName
}
static class ConfluenceHistory {
boolean latest
ConfluenceCreatedBy createdBy
ConfluenceVersion nextVersion
ConfluenceVersion previousVersion
ConfluenceVersion lastUpdated
}
static class ConfluenceSpace {
String key
String name
}
static class ConfluenceSpaceResult {
List<ConfluenceSpace> results
}
static class ConfluenceBody {
ConfluenceStorage storage
}
static class ConfluenceStorage {
String value
String representation
}
static class ConfluenceResponse {
int statusCode
String message
Map<String, ?> data
}
static interface ConfluenceApi {
@GET("content")
Call<ConfluenceContentResult> getContentResults();
@GET("content")
Call<ConfluenceContentResult> getContentBySpaceKeyAndTitle(final @Query("key") String key, final @Query("title") String title);
@GET("content/{id}/child/{type}")
Call<ConfluenceContentResult> getChildren(final @Path("id") String parentId, final @Path("type") String type, final @QueryMap Map<String, String> params);
@GET("content/{id}?expand=body.storage")
Call<ConfluenceContent> getContentById(final @Path("id") String id);
@POST("contentbody/convert/{to}")
Call<ConfluenceStorage> postContentConversion(final @Body ConfluenceStorage storage, final @Path("to") String convertToFormat);
@POST("content")
Call<ConfluenceContent> postContent(final @Body ConfluenceContent content);
@PUT("content/{id}")
Call<ConfluenceContent> putContent(final @Path("id") String id, final @Body ConfluenceContent content);
@POST("content")
void postContentWithCallback(final @Body ConfluenceContent content, final Callback<ConfluenceContent> callback);
@DELETE("content/{id}")
Call<ConfluenceResponse> deleteContentById(final @Path("id") String id);
@GET("space")
Call<ConfluenceSpaceResult> getSpaces();
@POST("space")
Call<ConfluenceSpace> createSpace(final @Body ConfluenceSpace space);
@POST("space/_private")
Call<ConfluenceSpace> createPrivateSpace(final @Body ConfluenceSpace space);
@GET("space/{spaceKey}/content/page")
Call<ConfluenceContentResult> getAllSpaceContent(final @Path("spaceKey") String spaceKey, final @QueryMap Map<String, String> params);
@GET("space/{spaceKey}/content/{type}")
Call<ConfluenceContentResult> getRootContentBySpaceKey(final @Path("spaceKey") String spaceKey, final @Path("type") String contentType);
}
}
// curl -u aleks:xxx -X POST -H 'Content-Type: application/json' -d '{"type":"page","title":"1.7.0 - Apache Fineract","space":{"key":"FINERACT"},"ancestors":[{"id":75974324}],"body":{"storage":{"value":"<p>This is <br/> a new page</p>","representation": "storage"}}}' https://cwiki.apache.org/confluence/content/ | python -mjson.tool
@@ -0,0 +1,87 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.apache.fineract.gradle.FineractPluginExtension.FineractPluginEmailParams
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.mail.*
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage
class EmailService {
private static final Logger log = LoggerFactory.getLogger(EmailService.class)
String username
String password
Properties properties
EmailService(FineractPluginExtension.FineractPluginConfigSmtp config) {
this.username = config.username
this.password = config.password
this.properties = new Properties()
this.properties.put("mail.smtp.host", config.host)
this.properties.put("mail.smtp.auth", "true")
this.properties.put("mail.smtp.starttls.enable", config.tls.toString())
if(config.ssl) {
this.properties.put("mail.smtp.port", "465")
this.properties.put("mail.smtp.socketFactory.port", "465");
this.properties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
} else {
this.properties.put("mail.smtp.port", "587")
}
}
void send(FineractPluginEmailParams params) {
log.warn("Sending email: ${params.from} -> ${params.to} - ${params.subject}")
Session session = Session.getInstance(this.properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(getUsername(), getPassword())
}
})
try {
Message msg = new MimeMessage(session)
msg.setFrom(new InternetAddress(params.from, params.name))
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(params.to))
if(params.cc) {
msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(params.cc))
}
if(params.bcc) {
msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(params.bcc))
}
msg.setSubject(params.subject)
msg.setText(params.message);
Transport.send(msg);
log.info("Done")
} catch (MessagingException e) {
log.error(e.toString(), e)
}
}
}
@@ -0,0 +1,136 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.eclipse.jgit.annotations.NonNull
import org.eclipse.jgit.annotations.Nullable
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.errors.CanceledException
import org.eclipse.jgit.lib.CommitBuilder
import org.eclipse.jgit.lib.GpgSigner
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.lib.StoredConfig
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.RefSpec
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class GitService {
private static final Logger log = LoggerFactory.getLogger(GitService.class)
private Git git
private boolean dryRun
GitService(FineractPluginExtension.FineractPluginConfigGit config, FineractPluginExtension.FineractPluginConfigGpg gpgConfig) {
GpgSigner.setDefault(new GpgSigner() {
@Override
void sign(@NonNull CommitBuilder commit, @Nullable String gpgSigningKey, @NonNull PersonIdent committer, CredentialsProvider credentialsProvider) throws CanceledException {
log.warn("------------------------ KEY: ${gpgSigningKey} IDENT: ${committer}")
}
@Override
boolean canLocateSigningKey(@Nullable String gpgSigningKey, @NonNull PersonIdent committer, CredentialsProvider credentialsProvider) throws CanceledException {
log.warn("------------------------ KEY: ${gpgSigningKey} IDENT: ${committer}")
return false
}
});
Repository repository = new FileRepositoryBuilder()
.readEnvironment()
.setGitDir(new File(config.dir))
.findGitDir()
.build();
this.git = new Git(repository)
this.dryRun = config.dryRun
}
void clone(String url, String branch, String directory) {
def ref = "refs/heads/" + branch
Git.cloneRepository()
.setURI(url)
.setDirectory(new File(directory))
.setBranchesToClone(Arrays.asList(ref))
.setBranch(ref)
.call();
}
void pull(String directory) {
Git git = Git.open(new File(directory));
git.pull().call()
}
void commit(String directory) {
Git git = Git.open(new File(directory));
def status = git.status().call()
if(!status.isClean()) {
git.add().addFilepattern(".").call()
git.commit().setSign(true).setMessage("chore: Publish current docs").call()
}
def result = git.push().call()
}
void config(String directory, List<FineractPluginExtension.FineractPluginConfigGitSection> sections) {
Git git = Git.open(new File(directory));
StoredConfig gitConfig = git.getRepository().getConfig();
sections.forEach(section -> {
gitConfig.setString(section.section, section.subsection, section.name, section.value);
})
// gitConfig.setString("branch", "master", "merge", "refs/heads/master");
gitConfig.save();
}
void createTag(String name, String message) {
def ref = git.tag()
.setName(name)
.setMessage(message)
.setAnnotated(true)
.setForceUpdate(true)
.call()
try {
git.push().setPushTags().setForce(true).setDryRun(dryRun).call()
log.warn("Tag created: ${name} (${ref.name})")
} catch(Exception e) {
log.error(e.toString(), e)
}
}
// TODO: explicitly provide the directory
void removeTag(String name) {
git.tagDelete().setTags(name).call()
RefSpec refSpec = new RefSpec(":refs/tags/${name}");
git.push().setRefSpecs(refSpec).setRemote("origin").call()
log.warn("Tag deleted: ${name}")
}
}
@@ -0,0 +1,148 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.apache.fineract.gradle.FineractPluginExtension.FineractPluginConfigGpg
import org.bouncycastle.bcpg.ArmoredOutputStream
import org.bouncycastle.bcpg.BCPGOutputStream
import org.bouncycastle.gpg.keybox.BlobType
import org.bouncycastle.gpg.keybox.PublicKeyRingBlob
import org.bouncycastle.gpg.keybox.bc.BcKeyBox
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.security.Security
class GpgService {
private static final Logger log = LoggerFactory.getLogger(GpgService.class)
private PGPPrivateKey privateKey;
private PGPPublicKey publicKey;
static {
Security.addProvider(new BouncyCastleProvider());
}
GpgService(FineractPluginConfigGpg config) {
// TODO: provide implementation for *.gpg key rings
def keyBox = config.publicKeyring.endsWith(".kbx") ? new BcKeyBox(new FileInputStream(config.publicKeyring)) : null
if(keyBox) {
for(def keyBlob : keyBox.getKeyBlobs()) {
switch (keyBlob.type) {
case BlobType.X509_BLOB:
break
case BlobType.OPEN_PGP_BLOB:
def publicKeyRing = (keyBlob as PublicKeyRingBlob).getPGPPublicKeyRing();
def iterator = publicKeyRing.getPublicKeys();
while (publicKey == null && iterator.hasNext()) {
def k = iterator.next();
if (k.isEncryptionKey()) {
def keyName = Long.toHexString(k.keyID).toUpperCase()
if(config.keyName.substring(config.keyName.length()-keyName.length()) == keyName) {
publicKey = k;
log.warn("Found: ${publicKey.getUserIDs().next()}")
break
} else {
log.warn("Key mismatch: ${config.keyName} <-> ${keyName}")
}
}
}
break
}
}
PGPSecretKeyRingCollection secretRingCollection = new PGPSecretKeyRingCollection(new FileInputStream(config.secretKeyring), new BcKeyFingerprintCalculator())
def secretKey = secretRingCollection.getSecretKey(publicKey.keyID)
if(secretKey!=null) {
this.privateKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(config.password.toCharArray()))
}
} else {
log.warn("Could not open the public key ring.")
}
}
void sign(FineractPluginExtension.FineractPluginGpgParams params) {
InputStream is = new FileInputStream(params.file)
BCPGOutputStream os = new BCPGOutputStream(new ArmoredOutputStream(new FileOutputStream(params.file + ".asc")))
sign(is).encode(os)
os.close();
}
PGPSignature sign(InputStream is) throws IOException, PGPException, GeneralSecurityException {
PGPSignatureGenerator generator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(publicKey.getAlgorithm(), PGPUtil.SHA1));
generator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) >= 0) {
generator.update(buf, 0, len);
}
return generator.generate();
}
String md5(FineractPluginExtension.FineractPluginGpgParams params) {
return calc(new FileInputStream(params.file), MessageDigest.getInstance("MD5", BouncyCastleProvider.PROVIDER_NAME))
}
String sha512(FineractPluginExtension.FineractPluginGpgParams params) {
return calc(new FileInputStream(params.file), MessageDigest.getInstance("SHA-512", BouncyCastleProvider.PROVIDER_NAME))
}
private static String calc(InputStream is, MessageDigest digest) {
String output
int read
byte[] buffer = new byte[8192]
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read)
}
byte[] hash = digest.digest()
BigInteger bigInt = new BigInteger(1, hash)
output = bigInt.toString(16)
while ( output.length() < 32 ) {
output = "0"+output
}
} catch (Exception e) {
log.error(e.toString(), e)
return null
}
return output
}
}
@@ -0,0 +1,324 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import org.apache.fineract.gradle.FineractPluginExtension
import org.jetbrains.annotations.NotNull
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.*
import java.util.concurrent.TimeUnit
class JiraService {
private static final Logger log = LoggerFactory.getLogger(JiraService.class)
private JiraApi api
JiraService(FineractPluginExtension.FineractPluginConfigJira config) {
def credentials = Credentials.basic(config.username, config.password)
final ConnectionPool connectionPool = new ConnectionPool(5, 60, TimeUnit.SECONDS);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(log::debug)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder okClient = new OkHttpClient.Builder()
.connectionPool(connectionPool)
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
@Override
Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
def request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
})
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(config.url)
.client(okClient.build())
.addConverterFactory(JacksonConverterFactory.create(mapper))
Retrofit retrofit = builder.build()
this.api = retrofit.create(JiraApi.class)
}
Map<String, Object> serverInfo() {
return this.api.serverInfo.execute().body()
}
FineractPluginExtension.FineractPluginJiraParams search(FineractPluginExtension.FineractPluginJiraParams params) {
def query = [:]
query['jql'] = params.query
query['startAt'] = params.pageOffset?:0
query['maxResults'] = params.pageSize?:50
// query['expand'] = [
// "names",
// "schema",
// "operations"
// ]
// query['fields'] = params.fields?:"*all"
query['fields'] = params.includes?:[
"summary",
"status",
"assignee",
"fixVersions"
]
// params.total = params.total?:1000
params.total = 1000
def result = this.api.searchIssues(query).execute().body()
params.result.addAll(result?.issues as List)
if(result?.issues.size()>=params.pageSize && params.result.size() < params.total) {
params.pageOffset += params.pageSize
search(params);
}
return params
}
List getProjects() {
return this.api.getProjects()?.execute()?.body()
}
List getProjectVersions(String projectId) {
return this.api.getProjectVersions(projectId)?.execute()?.body()
}
JiraVersion getVersion(String id) {
return this.api.getVersion(id)?.execute()?.body()
}
void updateVersion(JiraVersion version) {
this.api.updateVersion(version.id, version).execute()
}
static class JiraVersion {
String self
String id
String description
String name
boolean archived
boolean released
String releaseDate
String userReleaseDate
String project
Integer projectId
}
static class JiraSearchResult {
String expand
Integer startAt
Integer maxResults
Integer total
List<JiraIssue> issues
List<String> warningMessages
Map<String, Object> names
Map<String, Object> schema
}
static class JiraIssue {
String expand
String self
String id
String key
Map<String, Object> renderedFields
Map<String, Object> properties
Map<String, Object> names
Map<String, Object> schema
Map<String, Object> fields
}
static class JiraIssueUpdateRequest {
Map<String, Map<String, Object>> historyMetadata
Map<String, List<Map<String, Object>>> update
Map<String, Object> fields
Map<String, List<Map<String, Object>>> properties
}
static interface JiraApi {
// Server Info
@GET("serverInfo")
Call<Map<String, Object>> getServerInfo()
// Components
@GET("component/{id}")
Call<Object> getComponent(@Path("id") String id)
@POST("component")
Call<Object> createComponent(@Body Object component)
@PUT("component/{id}")
Call<Void> updateComponent(@Path("id") String id, @Body Object component)
@GET("component/{id}/relatedIssueCounts")
Call<Object> getComponentIssueCount(@Path("id") String id)
// Issue
@GET("issue/{issueIdOrKey}")
Call<Object> getIssue(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue")
Call<Object> createIssue(@Body Object issue)
@PUT("issue/{issueIdOrKey}")
Call<Object> updateIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object issue, @QueryMap Map<String, String> queryMap)
@POST("issue/bulk")
Call<Object> createIssues(@Body Object issues)
@PUT("issue/{issueIdOrKey}/assignee")
Call<Void> assignIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object user)
@GET("issue/{issueIdOrKey}/comment")
Call<Object> getComments(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue/{issueIdOrKey}/comment")
Call<Object> addComment(@Path("issueIdOrKey") String issueIdOrKey, @Body Object comment)
@PUT("issue/{issueIdOrKey}/comment/{id}")
Call<Object> updateComment(@Path("issueIdOrKey") String issueIdOrKey, @Path("id") String id, @Body Object comment)
@GET("issue/{issueIdOrKey}/comment/{id}")
Call<Object> getComment(@Path("issueIdOrKey") String issueIdOrKey, @Path("id") String id)
@POST("issue/{issueIdOrKey}/notify")
Call<Void> notifyIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object notify)
@GET("issue/{issueIdOrKey}/transitions?expand=transitions.fields")
Call<Object> getTransitions(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue/{issueIdOrKey}/transitions")
Call<Void> transitionIssue(@Path("issueIdOrKey") String issueIdOrKey, @Body Object issue)
@GET("issue/{issueIdOrKey}/watchers")
Call<Object> getIssueWatches(@Path("issueIdOrKey") String issueIdOrKey)
@POST("issue/{issueIdOrKey}/watchers")
Call<Void> addIssueWatcher(@Path("issueIdOrKey") String issueIdOrKey, @Body String user)
// Remote Issue Links
@GET("issue/{issueIdOrKey}/remotelink")
Call<Object> getIssueRemoteLinks(@Path("issueIdOrKey") String issueIdOrKey, @Query("globalId") String globalId)
@GET("issue/{issueIdOrKey}/remotelink/{linkId}")
Call<Object> getIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Path("linkId") String linkId)
@POST("issue/{issueIdOrKey}/remotelink")
Call<Object> createIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Body Object issueLink)
@POST("issue/{issueIdOrKey}/remotelink/{linkId}")
Call<Object> updateIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Path("linkId") String linkId, @Body Object issueLink)
@DELETE("issue/{issueIdOrKey}/remotelink")
Call<Object> deleteIssueRemoteLinks(@Path("issueIdOrKey") String issueIdOrKey, @Query("globalId") String globalId)
@DELETE("issue/{issueIdOrKey}/remotelink/{linkId}")
Call<Object> deleteIssueRemoteLink(@Path("issueIdOrKey") String issueIdOrKey, @Path("linkId") String linkId)
// Issue Links
@POST("issueLink")
Call<Void> createIssueLink(@Body Object issueLink)
@GET("issueLink/{linkId}")
Call<Object> getIssueLink(@Path("linkId") String linkId)
@DELETE("issueLink/{linkId}")
Call<Object> deleteIssueLink(@Path("linkId") String linkId)
// Issue Link Types
@GET("issueLinkType")
Call<Object> getIssueLinkTypes()
// Project
@GET("project?expand=lead,description")
Call<List> getProjects()
@GET("project/{projectIdOrKey}")
Call<Object> getProject(@Path("projectIdOrKey") String projectId)
@GET("project/{projectIdOrKey}/statuses")
Call<Object> getProjectStatuses(@Path("projectIdOrKey") String projectId)
@GET("project/{projectIdOrKey}/components")
Call<Object> getProjectComponents(@Path("projectIdOrKey") String projectId)
@GET("project/{projectIdOrKey}/versions")
Call<List<JiraVersion>> getProjectVersions(@Path("projectIdOrKey") String projectId)
// Search
@POST("search")
Call<JiraSearchResult> searchIssues(@Body Map<String, Object> search)
// Version
@POST("version")
Call<JiraVersion> createVersion(@Body JiraVersion version)
@GET("version/{id}")
Call<JiraVersion> getVersion(@Path("id") String id)
@PUT("version/{id}")
Call<Void> updateVersion(@Path("id") String id, @Body JiraVersion version)
// Fields
@GET("field")
Call<Object> getFields()
@POST("field")
Call<Object> createField(@Body Object field)
// Users
@GET("user/search")
Call<Object> userSearch(@Query("username") String userName, @Query("startAt") int startAt, @Query("maxResults") int maxResults)
@GET("user/assignable/search")
Call<Object> assignableUserSearch(@Query("username") String userName, @Query("project") String project, @Query("issueKey") String issueKey, @Query("startAt") int startAt, @Query("maxResults") int maxResults)
@Multipart
@retrofit2.http.Headers("X-Atlassian-Token: no-check")
@POST("issue/{issueIdOrKey}/attachments")
Call<Object> uploadAttachment(@Path("issueIdOrKey") String issueIdOrKey, @Part MultipartBody.Part file)
@GET("attachment/{attachmentId}")
Call<Object> getAttachment(@Path("attachmentId") String attachmentId)
@DELETE("attachment/{attachmentId}")
Call<Object> deleteAttachment(@Path("attachmentId") String attachmentId)
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrl(@Url String fileUrl)
}
}
@@ -0,0 +1,74 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import org.apache.fineract.gradle.FineractPluginExtension
import org.gradle.api.GradleException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.tmatesoft.svn.core.SVNCancelException
import org.tmatesoft.svn.core.SVNDepth
import org.tmatesoft.svn.core.SVNException
import org.tmatesoft.svn.core.SVNURL
import org.tmatesoft.svn.core.wc.*
class SubversionService {
private static final Logger log = LoggerFactory.getLogger(SubversionService.class)
private SVNUpdateClient client;
private String revision;
SubversionService(FineractPluginExtension.FineractPluginConfigSubversion config) {
ISVNOptions options = SVNWCUtil.createDefaultOptions(true)
SVNClientManager clientManager = SVNClientManager.newInstance(options, SVNWCUtil.createDefaultAuthenticationManager(config.username, config.password))
this.client = clientManager.updateClient;
this.client.ignoreExternals = false
this.client.eventHandler = new ISVNEventHandler() {
@Override
void handleEvent(SVNEvent event, double progress) throws SVNException {
println ">>> PROGRESS: ${progress>0?:'-'}"
}
@Override
void checkCancelled() throws SVNCancelException {
}
}
this.revision = config.revision
}
void checkout(FineractPluginExtension.FineractPluginSubversionParams params) {
SVNURL svnUrl = SVNURL.parseURIEncoded(params.url)
SVNRevision svnRevision = this.revision == null ? SVNRevision.HEAD : SVNRevision.parse(this.revision)
if (svnRevision == SVNRevision.UNDEFINED) {
throw new GradleException("Invalid SVN revision: " + this.revision)
}
this.client.doCheckout(svnUrl, new File(params.directory), SVNRevision.HEAD, svnRevision, SVNDepth.INFINITY, false)
}
void commit(FineractPluginExtension.FineractPluginSubversionParams params) {
// TODO: implement this
SVNURL svnUrl = SVNURL.parseURIEncoded(params.url)
}
}
@@ -0,0 +1,84 @@
/**
* 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.
*/
package org.apache.fineract.gradle.service
import freemarker.core.Environment
import freemarker.template.Configuration
import freemarker.template.Template
import freemarker.template.TemplateException
import freemarker.template.TemplateExceptionHandler
import org.apache.fineract.gradle.FineractPluginExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class TemplateService {
private static final Logger log = LoggerFactory.getLogger(TemplateService.class)
private Configuration config
TemplateService(FineractPluginExtension.FineractPluginConfigTemplate config) {
def dir = new File(config.templateDir);
this.config = new Configuration(Configuration.VERSION_2_3_31);
this.config.setDirectoryForTemplateLoading(dir)
this.config.setDefaultEncoding("UTF-8");
this.config.setLogTemplateExceptions(false);
this.config.setWrapUncheckedExceptions(true);
this.config.setFallbackOnNullLoopVariable(false);
this.config.setTemplateExceptionHandler(new TemplateExceptionHandler() {
@Override
void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException {
if (!env.isInAttemptBlock()) {
PrintWriter pw = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out);
pw.print("FreeMarker template error\n");
te.printStackTrace(pw, false, true, false);
pw.flush(); // To commit the HTTP response
}
}}
)
}
FineractPluginExtension.FineractPluginTemplateParams render(FineractPluginExtension.FineractPluginTemplateParams params, Object data) {
Template template = null;
if(params.templateFile) {
template = new Template("template", new FileReader(new File(params.templateFile)), this.config)
}
if(params.template) {
template = new Template("template", new StringReader(params.template), this.config)
}
if(template) {
if(params.outputFile) {
def output = new File(params.outputFile)
output.createNewFile()
template.process(data, new FileWriter(output, false))
} else {
def output = new StringWriter()
template.process(data, output)
params.output = output.toString()
}
}
return params
}
}