Categories
Coding

Scripting Maven Deployments with Ruby

Recently I had to import a number of legacy projects into a Maven-type structure. I knocked up the following script to make the task easier for repeated applications. Basically what it does is the following:

  • Tried to parse the filenames of jars in the current directory;
  • Does a Nexus search to see if it can locate the artifact;
  • If it finds the artifact in Nexus, it can use the GAV parameters for that artifact, otherwise, it generates a GAV stanza for inclusion in the POM.
  • For each artifact that could not be located in Nexus, it generates a mvn deploy:deploy-file command to upload the dependency

Error handling is non-existent in this file, so caveat emptor!

[sourcecode language=”ruby” wraplines=”true”]
#!/usr/bin/ruby

require ‘net/http’
require ‘rexml/document’
include REXML

# Nexus server
host="nexus-server"
port=8080

# Could also use GAV parameters in query string
url="http://#{host}:#{port}/nexus/service/local/data_index?q="

print "<dependencies>","\n"

# Open file for dependencies entries
depsFile=File.open("dependencies.xml",’w’)
depsFile.write("</dependencies><dependencies>\n")

# Track unprocessed jars
errors=[]

# Jars to be uploaded to Nexus
uploads=[]

for jarFile in Dir.glob("*.jar")
if jarFile =~ /([A-Za-z\-\.0-9]+?)(?:\-)?(\d+(?:\.\d+)*)?.jar/
artifact=$1
version=$2

if artifact =~ /(\S+)\.(.*)/
group=$1
artifact=$2
else
group=artifact.gsub("\-", "\.")
end

if version.nil?
version="1.0"
end

# Create a default stanza template
stanza = "<dependency>\n"
stanza < < ‘ <groupId>#{group}\n’
stanza < < ‘ <artifactId>#{artifact}<artifactid>\n’
stanza < < ‘ <version>#{version}\n’
stanza < < " <packaging>jar\n"
stanza < < "</dependency>\n\n"

puts "Searching Nexus for #{artifact}"
query="#{url}#{artifact}"

resp = Net::HTTP.get_response(URI.parse(query))
respDoc = REXML::Document.new(resp.body)
XPath.each(respDoc, "//search-results/totalCount") do |count|
matches = Integer(count.text)
if matches > 0

idx=1; artifactMap = {}
XPath.each( count, "//data/artifact") do |ar|
artifactId=ar.elements["artifactId"].text
artifactGroupId=ar.elements["groupId"].text
artifactVersion=ar.elements["version"].text
artifactRepo=ar.elements["repoId"].text
puts "#{idx}. #{artifactGroupId}:#{artifactId}:#{artifactVersion} (repo:#{artifactRepo})"
artifactMap[idx]={"group",artifactGroupId, "artifact",artifactId, "version",artifactVersion}
idx+=1
end

puts "Found #{matches} possible match(es) for #{group}:#{artifact}:#{version}"
puts "Enter artifact number, or enter to use generated artifact:"
num=gets.chomp

if !num.empty?
num=Integer(num)
entry=artifactMap[num]
group=entry["group"];artifact=entry["artifact"];version=entry["version"]
else
uploads.push( {"artifact",artifact,"group",group,"version",version,"file",jarFile} )
end

else
uploads.push( {"artifact",artifact,"group",group,"version",version,"file",jarFile} )
end
stanza = eval(‘"’ + stanza + ‘"’)
depsFile.write(stanza)
end
else
errors < < jarFile
end
end

depsFile.write("</dependencies>\n")
depsFile.close

puts "Finished processing.\nCheck the file dependencies.xml for generated XML"

# Generate the upload script
File.open("upload_deps.sh", "w") { |f|
f.puts "#!/bin/bash\n\n"
uploads.each do |dep|
# Send the file to the correct repo
repo="http://#{host}:#{port}/nexus/content/repositories/"
if dep["version"] =~ /(.*)SNAPSHOT/
repo < < "snapshots"
else
repo << "releases"
end

line="mvn deploy:deploy-file -Dfile=#{dep["file"]} -DartifactId=#{dep["artifact"]} -DgroupId=#{dep["group"]} -Dversion=#{dep["version"]} -Dpackaging=jar -DrepositoryId=nexus -DgeneratePom=true -Durl=#{repo}"

f.puts "echo \"Uploading #{dep["file"]}…\""
f.puts line
end
}

puts <<HERE
The file upload_deps.sh contains Maven commands to upload dependencies not currently in Nexus.

This assumes that you have a stanza in $MVN_HOME/conf/settings.xml as follows:

<server>
<id>nexus</id>
<username>$USERNAME</username>
<password>$PASSWORD</password>

Where the appropriate Nexus deployment credentials are in <username> and <password>.
HERE

if !errors.empty?
puts "The following jar files could not be processed and need to be manually defined:"
errors.each do |error|
puts error
end
end

[/sourcecode]