Simple deployment from Gradle to Artifactory

The maven ecosystem works perfectly together with JFrog’s Artifactory, a wide spreaded repository manager for GAV artifacts, debian artifacts, bower, npm, ….. . Gradle, the rising competitor of Maven and Ant, fits also to the maven ecosystem but tries to stay independent by providing no native support for Maven repistories but plugins with (in my oppinion!) low documentation. Let’s have a look at the differences between a full fledged Artifactory integrated Maven setup and a similar Gradle setup.

Versions

component version download
Ubuntu 14.04 http://www.ubuntu.com/download/desktop
Java Hotspot 1.8.0_72 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Maven 3.3.9 https://maven.apache.org/download.cgi
Gradle 2.14 http://gradle.org/gradle-download/
Artifactory 4.8.22 OSS https://www.jfrog.com/open-source/

Artifactory

  • runs on localhost port 10000
  • default local repos „libs-release-local“ and „libs-snapshots-local“ are used
  • A user „arti“ with deploy permissions is configured in Artifactory with password „123456“

Project setup

overview

package eu.christophburmeister.playground;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

class Main{

    static final Logger LOGGER = LogManager.getLogger(Main.class);

    public static void main(String args[]){
        LOGGER.info("blubb");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30" status="debug">
    <Appenders>
        <Console name="appender-Console-all" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="appender-Console-all" />
        </Root>
    </Loggers>
</Configuration>

Maven

In Maven’s settings.xml you define the credentials for the distribution targets configured in pom.xml. This way you can separate the credentials of the repo from the projects themselve. Beside these credentials, you overwrite the „central“ and „snapshot“ repository here. That’s where Maven looks for required artifacts that it doesn’t already have in its cache. This file never gets pushed/committed to any source repository, it stays on your machine only!

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

    <localRepository>/home/christoph/jtools/apache-maven-repo</localRepository>

    <servers>
        <server>
            <id>central</id>
            <username>arti</username>
            <password>123456</password>
        </server>
        <server>
            <id>snapshots</id>
            <username>arti</username>
            <password>123456</password>
        </server>
    </servers>


    <profiles>
        <profile>
            <id>my-artifactory-profile</id>
            <repositories>
                <repository>
                    <id>central</id>
                    <url>http://localhost:10000/artifactory/repo</url>
                    <snapshots>
                        <enabled>false</enabled>
                    </snapshots>
                </repository>
                <repository>
                    <id>snapshots</id>
                    <url>http://localhost:10000/artifactory/repo</url>
                    <releases>
                        <enabled>false</enabled>
                    </releases>
                </repository>
            </repositories>
            <pluginRepositories>
                <pluginRepository>
                    <id>central</id>
                    <url>http://localhost:10000/artifactory/repo</url>
                    <snapshots>
                        <enabled>false</enabled>
                    </snapshots>
                </pluginRepository>
                <pluginRepository>
                    <id>snapshots</id>
                    <url>http://localhost:10000/artifactory/repo</url>
                    <releases>
                        <enabled>false</enabled>
                    </releases>
                </pluginRepository>
            </pluginRepositories>
        </profile>

    </profiles>

    <activeProfiles>
        <activeProfile>my-artifactory-profile</activeProfile>
    </activeProfiles>
</settings>

The pom contains in this example no special build section. It contains only the distribution and the log4j2 dependencies:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>eu.christophburmeister.playground</groupId>
    <artifactId>build-playground</artifactId>
    <version>0.1-SNAPSHOT</version>

    <distributionManagement>
        <repository>
            <id>central</id>
            <url>http://localhost:10000/artifactory/libs-release-local</url>
        </repository>
        <snapshotRepository>
            <id>snapshots</id>
            <url>http://localhost:10000/artifactory/libs-snapshots-local</url>
            <uniqueVersion>true</uniqueVersion>
        </snapshotRepository>
    </distributionManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.6.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.6.1</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

That’s all you need for a complete Maven deploy process (Have I already written, I like Maven? 🙂 ):

$ rm -rf ~/jtools/apache-maven-repo/ # deleting the cached artifacts to make sure, we are really fetching from our Artifactory
$ mvn clean deploy
  1. deletes the target directory goal „clean“)
  2. compiles the source files (goal „compile“)
  3. packages class files and resources to jar (goal „package“)
  4. installs the jar in the local Maven repository (goal „install“)
  5. deploys the jar and the pom to Artifactory (goal „deploy“)

Gradle

The properties file for Artifactory authentication that gets never(!) committed/pushed to source repositories. On local machines, you can pass it from ide, on buildsystems (e.g. Jenkins, Hudson, …) you can pass it via several plugins.

artifactoryUsername=arti
artifactoryPassword=123456

This properties file corresponds to the server section in Maven’s settings.xml

For some reason (I absolutely don’t know why) the artifact name has to be configured in a special file called „settings.gradle“ if you don’t want to use the top level directory’s name:

rootProject.name='build-playground'

This is corresponds to Maven’s artifactId.

apply plugin: 'java'
apply plugin: 'maven' // needed for pom-generation

project.ext.groupId = 'eu.christophburmeister.playground'
project.ext.artifactId = project.name
project.ext.version = '0.1-SNAPSHOT'
project.ext.generatedPomPath = "$buildDir/generated-pom.xml"

// distribution properties
project.ext.distributionRepository = 'http://localhost:10000/artifactory'
project.ext.releaseDistributionRepo = "$distributionRepository/libs-release-local"
project.ext.snapshotDistributionRepo  = "$distributionRepository/libs-snapshot-local"

// reading an external properties file for artifactory authentication
Properties artifactoryAuthProps = new Properties()
artifactoryAuthProps.load(new FileInputStream('artifactory-auth.properties'))
project.ext.artifactoryUsername = artifactoryAuthProps.getProperty('artifactoryUsername')
project.ext.artifactoryPassword = artifactoryAuthProps.getProperty('artifactoryPassword')

repositories{
    maven {
        url = "$project.distributionRepository/repo"
        credentials {
            username = project.artifactoryUsername
            password = project.artifactoryPassword
        }
    }
}

dependencies {
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.1'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.1'
}

task generatePom (dependsOn: [build]) {
    doLast {
        pom {
            project {
                groupId project.ext.groupId
                artifactId project.ext.artifactId
                version project.ext.version
            }
        }.writeTo(project.generatedPomPath)
    }
}

task deploy (dependsOn: [generatePom]){
    doLast{
        simpleArtifactoryDeploy()
    }
}

/**
 * https://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API
 * curl -u myUser:myP455w0rd! -X PUT "http://localhost:8081/artifactory/my-repository/my/new/artifact/directory/file.txt" -T Desktop/myNewFile.txt
 */
def simpleArtifactoryDeploy(){
    File deployablePom = new File(project.ext.generatedPomPath)
    File deployableArtifact = jar.archivePath // getArchivePath returns already a file object!

    String distributionRepo = (project.ext.version.contains('SNAPSHOT')) ? project.ext.snapshotDistributionRepo : project.ext.releaseDistributionRepo

    String groupIdPath = project.ext.groupId.replace('.', '/')
    String baseUrl = "$distributionRepo/$groupIdPath/$project.ext.artifactId/$project.ext.version/"
    String deployArtifactUrl = "$baseUrl/$project.ext.artifactId-${project.ext.version}.jar"
    String deployPomUrl = "$baseUrl/$project.ext.artifactId-${project.ext.version}.pom"

    deploy(deployableArtifact, deployArtifactUrl)
    deploy(deployablePom, deployPomUrl)
}

/**
 * Method to deploy a file to a specified url via HTTP PUT with global injected credentials.
 */
def deploy(File deployItem, String targetUrl){
    HttpURLConnection connection = new URL(targetUrl).openConnection()
    String encodedCredentials = new sun.misc.BASE64Encoder().encode("$project.ext.artifactoryUsername:$project.ext.artifactoryPassword".getBytes())
    connection.setRequestProperty("Authorization", "Basic " + encodedCredentials)
    connection.setRequestMethod('PUT')
    connection.setDoOutput(true)
    DataOutputStream dataOut = new DataOutputStream(connection.getOutputStream())
    println "deploying $deployItem.path to $targetUrl"
    dataOut.write(deployItem.getBytes()) // writes out bytes of file
    dataOut.flush()
    dataOut.close()
    if (201 == connection.getResponseCode() ){ // HTTP 201 = CREATED
        println "creation of $deployItem.path was successful"
    } else {
        throw new Exception("creation of $deployItem.path failed: ${connection.responseCode}: ${connection.responseMessage}")
    }
}

That’s all you need for a complete Gradle deploy process:

$ rm -rf ~/.gradle # delete the cached artifacts to make sure, we are really fetching from our Artifactory
$ gradle clean deploy
  1. deletes the build directory
  2. compiles the source files
  3. packages class files and resources to jar (task „build“)
  4. Generates a valid pom.xml that contains the dependencies (task „generatePom“ from maven-plugin)
  5. deploys the jar and the pom to Artifactory (task „deploy“ with custom HTTP-PUT)

Conclusion

Even without using the (in my eyes) bad documented Artfactory-Gradle-Plugin and a bit of Groovy magic to satisfy the Artifactory REST API, even with Gradle you are able to deploy systematically to a Maven-ecosystem-optimized Artifact repo manager.