diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 01f5da4..deeb4d0 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -33,7 +33,7 @@ jobs: - name: Gradle publish uses: gradle/gradle-build-action@v2 with: - arguments: build publish githubRelease buildDashboard -PgithubToken=${{secrets.GITHUB_TOKEN}} + arguments: build publish groovydoc githubRelease buildDashboard -PgithubToken=${{secrets.GITHUB_TOKEN}} - name: Upload build reports uses: actions/upload-artifact@v3 diff --git a/plugin/src/main/groovy/io/noumenal/ApplicationContainer.groovy b/plugin/src/main/groovy/io/noumenal/ApplicationContainer.groovy index fcb4464..23baa71 100644 --- a/plugin/src/main/groovy/io/noumenal/ApplicationContainer.groovy +++ b/plugin/src/main/groovy/io/noumenal/ApplicationContainer.groovy @@ -2,26 +2,62 @@ package io.noumenal import groovy.util.logging.Slf4j +/** + * A domain container that allows for defining "Snowflake Applications." The plugin automatically creates the UDFs configured in this container. + */ @Slf4j class ApplicationContainer { ApplicationContainer(final String name) { this.name = name } + /** + * The name of the domain container. + */ String name + /** + * The 'inputs' property for the Snowflake UDF. + */ List inputs + /** + * The 'returns' property for the Snowflake UDF. + */ String returns + /** + * The 'type' property of the UDF, either 'function' or 'procedure'. DEFAULT: 'function'. + */ String type = 'function' + /** + * The 'language' property of the UDF, currently only 'JAVA' is supported. DEFAULT: 'JAVA'. + */ String language = 'JAVA' + /** + * The 'handler' property of the UDF. + */ String handler + /** + * A getter for the objectType of the UDF. + * + * @return The objectType + */ String getObjectType() { returns ? 'function' : 'procedure' } + /** + * A getter for whether the UDF is a function. + * + * @return Is the UDF a function? + */ Boolean isFunction() { objectType == 'function' } + /** + * A getter for the create statement for the UDF. The imports are passed in as the only property. + * + * @return The complete UDF create statement. + */ String getCreate(String imports) { """|CREATE OR REPLACE $type $name (${inputs.join(', ')}) | returns $returns diff --git a/plugin/src/main/groovy/io/noumenal/SnowflakeExtension.groovy b/plugin/src/main/groovy/io/noumenal/SnowflakeExtension.groovy index 2adb60a..1cac77c 100644 --- a/plugin/src/main/groovy/io/noumenal/SnowflakeExtension.groovy +++ b/plugin/src/main/groovy/io/noumenal/SnowflakeExtension.groovy @@ -1,34 +1,81 @@ package io.noumenal import org.gradle.api.Project +/** + * The plugin configuration extension that is applied to the Gradle project as 'snowflake'. + */ class SnowflakeExtension { SnowflakeExtension(Project project) { this.project = project } private Project project + /** + * The Snowflake account URL, for instance: https://gradle-snowflake.us-east-1.snowflakecomputing.com:443. + */ String account + /** + * The Snowflake user to connect as. + */ String user + /** + * The Snowflake password to connect with. + */ String password + /** + * The Snowflake database to connect to. + */ String database + /** + * The Snowflake schema to connect with. Default: 'public'. + */ String schema = 'public' + /** + * The Snowflake role to connect with. + */ String role + /** + * The Snowflake warehouse to connect with. Default: 'compute_wh'. + */ String warehouse = "compute_wh" + /** + * The Snowflake stage to upload to. Default: 'maven'. + */ String stage = 'maven' + /** + * Optional: specify the URL of {@link #stage} if it is external. The plugin will apply the 'maven-publish' plugin and handle publishing artifacts there. + */ String publishUrl + /** + * Optional: specify an artifact groupId when using the 'maven-publish' plugin. + */ String groupId = project.getGroup() + /** + * Optional: specify an artifactId when using the 'maven-publish' plugin. + */ String artifactId = project.getName() + /** + * Optional: do not automatically apply 'maven-publish' and allow the user to apply that plugin in the 'build.gradle' file. + */ Boolean useCustomMaven = false + /** + * Convert names to be Snake Case. + */ private static String toSnakeCase( String text ) { text.replaceAll( /([A-Z])/, /_$1/ ).toLowerCase().replaceAll( /^_/, '' ) } - + /** + * Convert names to be Camel Case. + */ private static String toCamelCase( String text, boolean capitalized = false ) { text = text.replaceAll( "(_)([A-Za-z0-9])", { Object[] it -> it[2].toUpperCase() } ) return capitalized ? capitalize(text) : text } + /** + * Return the name of the Maven publication task associated with the external stage. + */ String getPublishTask() { toCamelCase("publish_snowflake_publication_to_${stage}Repository") } diff --git a/plugin/src/main/groovy/io/noumenal/SnowflakePlugin.groovy b/plugin/src/main/groovy/io/noumenal/SnowflakePlugin.groovy index 15394e9..1132d02 100644 --- a/plugin/src/main/groovy/io/noumenal/SnowflakePlugin.groovy +++ b/plugin/src/main/groovy/io/noumenal/SnowflakePlugin.groovy @@ -5,11 +5,16 @@ import org.gradle.api.Project import org.gradle.api.Plugin import org.gradle.api.publish.maven.MavenPublication import org.gradle.authentication.aws.AwsImAuthentication - +/** + * The Gradle plugin. + */ @Slf4j class SnowflakePlugin implements Plugin { private static String PLUGIN = 'snowflake' + /** + * Apply the gradle-snowflake plugin to a Gradle project. Also applies the 'com.github.johnrengelman.shadow' and 'java-library' plugins. Supporting the 'scala' plugin instead is on the roadmap. + */ void apply(Project project) { project.extensions.create(PLUGIN, SnowflakeExtension) diff --git a/plugin/src/main/groovy/io/noumenal/SnowflakePublish.groovy b/plugin/src/main/groovy/io/noumenal/SnowflakePublish.groovy index 1febbe8..dcffa0d 100644 --- a/plugin/src/main/groovy/io/noumenal/SnowflakePublish.groovy +++ b/plugin/src/main/groovy/io/noumenal/SnowflakePublish.groovy @@ -5,10 +5,8 @@ import com.snowflake.snowpark_java.Session import groovy.util.logging.Slf4j import net.snowflake.client.jdbc.SnowflakeStatement import org.gradle.api.DefaultTask -import org.gradle.api.PathValidation import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile @@ -18,67 +16,104 @@ import org.gradle.api.tasks.options.Option import java.sql.ResultSet import java.sql.Statement +/** + * A Cacheable Gradle task for publishing Java-based applications as UDFs to Snowflake. + */ @Slf4j @CacheableTask class SnowflakePublish extends DefaultTask { private static String PLUGIN = 'snowflake' + /** + * A helper for getting the plugin extension. + * + * @return A reference to the plugin extension. + */ @Internal def getExtension() { project.extensions."$PLUGIN" } + /** + * The task Constructor with 'description' and 'group'. + * + * @return A custom task class. + */ SnowflakePublish() { description = "Publish a Java artifact to an external stage and create Snowflake Functions and Procedures." group = "publishing" } + /** + * The Snowflake account URL, for instance: https://gradle-snowflake.us-east-1.snowflakecomputing.com:443. Overrides {@link SnowflakeExtension#account}. + */ @Optional @Input @Option(option = "account", - description = "The URL of the Snowflake account." + description = "Override the URL of the Snowflake account." ) String account = extension.account + /** + * The Snowflake user to connect as. Overrides {@link SnowflakeExtension#user}. + */ @Optional @Input @Option(option = "user", - description = "The user to connect to Snowflake." + description = "Override the Snowflake user to connect as." ) String user = extension.user + /** + * The Snowflake password to connect with. Overrides {@link SnowflakeExtension#password}. + */ @Optional @Input @Option(option = "password", - description = "The password to connect to Snowflake." + description = "Override the Snowflake password to connect with." ) String password = extension.password + /** + * The Snowflake database to connect to. Overrides {@link SnowflakeExtension#database}. + */ @Optional @Input @Option(option = "database", - description = "The Snowflake database to use." + description = "Override the Snowflake database to connect to." ) String database = extension.database + /** + * The Snowflake schema to connect with. Overrides {@link SnowflakeExtension#schema}. + */ @Input @Option(option = "schema", - description = "The Snowflake schema to use." + description = "Override the Snowflake schema to connect with." ) String schema = extension.schema + /** + * The Snowflake role to connect with. Overrides {@link SnowflakeExtension#warehouse}. + */ @Input @Option(option = "warehouse", - description = "The Snowflake warehouse to use." + description = "Override the Snowflake role to connect with." ) String warehouse = extension.warehouse + /** + * The Snowflake warehouse to connect with. Overrides {@link SnowflakeExtension#role}. + */ @Input @Option(option = "role", description = "The Snowflake role to use." ) String role = extension.role + /** + * The Snowflake stage to upload to. Overrides {@link SnowflakeExtension#stage}. + */ @Optional @Input @Option(option = "stage", @@ -86,16 +121,19 @@ class SnowflakePublish extends DefaultTask { ) String stage = extension.stage + /** + * Optional: manually pass a JAR file path to upload instead of relying on Gradle metadata. + */ @Optional @Input - @Option(option = "jar", description = "Manually pass a JAR file path to upload instead of relying on Gradle metadata.") + @Option(option = "jar", description = "Optional: manually pass a JAR file path to upload instead of relying on Gradle metadata.") String jar = project.tasks.shadowJar.archiveFile.get() -// @InputFile -// def getJarFile() { -// project.file(jar, PathValidation.NONE) -// } - + /** + * Create a Snowflake session. + * + * @return a Snowflake session. + */ @Internal Session getSession() { Map props = [ @@ -121,9 +159,17 @@ class SnowflakePublish extends DefaultTask { return session } + /** + * A simple text output file for the Snowflake applications create statements. Mainly for making the class Cacheable. + */ @OutputFile File output = project.file("${project.buildDir}/${PLUGIN}/output.txt") + /** + * Get the 'import' property for the UDF. + * + * @return the 'import' property. + */ String getImports(Session session) { String basePath = "@${stage}/${extension.groupId.replace('.', '/')}/${extension.artifactId}/${project.version}" @@ -150,6 +196,9 @@ class SnowflakePublish extends DefaultTask { "'$basePath/$fileName'" } + /** + * The Gradle TaskAction method. Publish the Snowflake Application. + */ @TaskAction def publish() { // keep the session