diff --git a/lib/puppet/functions/archive/artifactory_latest_url.rb b/lib/puppet/functions/archive/artifactory_latest_url.rb new file mode 100644 index 00000000..30b0c515 --- /dev/null +++ b/lib/puppet/functions/archive/artifactory_latest_url.rb @@ -0,0 +1,45 @@ +require 'json' +require 'puppet_x/bodeco/util' + +Puppet::Functions.create_function(:'archive::artifactory_latest_url') do + dispatch :artifactory_latest_url do + param 'Variant[Stdlib::HTTPUrl, Stdlib::HTTPSUrl]', :url + param 'Hash', :maven_data + end + + def artifactory_latest_url(url, maven_data) + # Turn provided artifactory URL into the fileinfo API endpoint of the parent directory + uri = URI(url.sub('/artifactory/', '/artifactory/api/storage/')[%r{^(.*)/.*$}, 1]) + + response = PuppetX::Bodeco::Util.content(uri) + content = JSON.parse(response) + + uris = if maven_data['classifier'] + content['children'].select do |child| + child['uri'] =~ %r{^/#{maven_data['module']}-#{maven_data['base_rev']}-(SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+)))-#{maven_data['classifier']}\.#{maven_data['ext']}$} && !child['folder'] + end + else + content['children'].select do |child| + child['uri'] =~ %r{^/#{maven_data['module']}-#{maven_data['base_rev']}-(SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+)))\.#{maven_data['ext']}$} && !child['folder'] + end + end + + raise("Couldn't find any Artifactory artifacts") if uris.empty? + + latest = uris.sort_by { |x| x['uri'] }.last['uri'] + Puppet.debug("Latest artifact found for #{url} was #{latest}") + + # Now GET the fileinfo endpoint of the resolved latest version file + uri = URI("#{content['uri']}#{latest}") + response = PuppetX::Bodeco::Util.content(uri) + content = JSON.parse(response) + + url = content['downloadUri'] + sha1 = content['checksums'] && content['checksums']['sha1'] + + { + 'url' => url, + 'sha1' => sha1 + } + end +end diff --git a/lib/puppet/functions/archive/parse_artifactory_url.rb b/lib/puppet/functions/archive/parse_artifactory_url.rb new file mode 100644 index 00000000..ab21ad9f --- /dev/null +++ b/lib/puppet/functions/archive/parse_artifactory_url.rb @@ -0,0 +1,30 @@ +# A function to parse an Artifactory maven 2 repository URL +Puppet::Functions.create_function(:'archive::parse_artifactory_url') do + dispatch :parse_artifactory_url do + param 'Variant[Stdlib::HTTPUrl, Stdlib::HTTPSUrl]', :url + end + + def parse_artifactory_url(url) + # Regex is for the 'maven-2-default Repository Layout' + matchdata = url.match(%r{ + (?.*/artifactory) + / + (?[^/]+) + / + (?.+?) + / + (?[^/]+) + / + (?[^/]+?) + (?:-(?SNAPSHOT))? + / + \k-\k + (?:-(?SNAPSHOT|(?:(?:[0-9]{8}.[0-9]{6})-(?:[0-9]+))))? + (?:-(?[^/]+?))? + \. + (?(?:(?!\d))[^\-/]+|7z) + }x) + return nil unless matchdata + Hash[matchdata.names.zip(matchdata.captures)] + end +end diff --git a/manifests/artifactory.pp b/manifests/artifactory.pp index 8cbff718..3a1171c7 100644 --- a/manifests/artifactory.pp +++ b/manifests/artifactory.pp @@ -72,24 +72,43 @@ } if $url { - $file_url = $url - $sha1_url = regsubst($url, '/artifactory/', '/artifactory/api/storage/') + $maven2_data = archive::parse_artifactory_url($url) + if $maven2_data and $maven2_data['folder_iteg_rev'] == 'SNAPSHOT'{ + # URL represents a SNAPSHOT version. eg 'http://artifactory.example.com/artifactory/repo/com/example/artifact/0.0.1-SNAPSHOT/artifact-0.0.1-SNAPSHOT.zip' + # Only Artifactory Pro lets you download this directly but the corresponding fileinfo endpoint (where the sha1 checksum is published) doesn't exist. + # This means we can't use the artifactory_sha1 function + + $latest_url_data = archive::artifactory_latest_url($url, $maven2_data) + + $file_url = $latest_url_data['url'] + $sha1 = $latest_url_data['sha1'] + } else { + $file_url = $url + $sha1_url = regsubst($url, '/artifactory/', '/artifactory/api/storage/') + $sha1 = undef + } } elsif $server and $port and $url_path { warning('archive::artifactory attribute: server, port, url_path are deprecated') $art_url = "http://${server}:${port}/artifactory" $file_url = "${art_url}/${url_path}" $sha1_url = "${art_url}/api/storage/${url_path}" + $sha1 = undef } else { fail('Please provide fully qualified url path for artifactory file.') } + if $sha1 { + $_sha1 = $sha1 + } else { + $_sha1 = artifactory_sha1($sha1_url) + } archive { $file_path: ensure => $ensure, path => $file_path, extract => $extract, extract_path => $extract_path, source => $file_url, - checksum => artifactory_sha1($sha1_url), + checksum => $_sha1, checksum_type => 'sha1', creates => $creates, cleanup => $cleanup, diff --git a/spec/functions/parse_artifactory_url_spec.rb b/spec/functions/parse_artifactory_url_spec.rb new file mode 100644 index 00000000..614a8ee1 --- /dev/null +++ b/spec/functions/parse_artifactory_url_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe 'archive::parse_artifactory_url' do + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('not_a_url').and_raise_error(ArgumentError) } + + context 'releases' do + it do + is_expected.to run.with_params('https://repo.jfrog.org/artifactory/repo1-cache/maven-proxy/maven-proxy-webapp/0.2/maven-proxy-webapp-0.2.war').and_return( + 'base_url' => 'https://repo.jfrog.org/artifactory', + 'repository' => 'repo1-cache', + 'org_path' => 'maven-proxy', + 'module' => 'maven-proxy-webapp', + 'base_rev' => '0.2', + 'folder_iteg_rev' => nil, + 'file_iteg_rev' => nil, + 'classifier' => nil, + 'ext' => 'war' + ) + end + context 'with classifier' do + it do + is_expected.to run.with_params('https://repo.jfrog.org/artifactory/repo1-cache/maven-proxy/maven-proxy-standalone/0.2/maven-proxy-standalone-0.2-app.jar').and_return( + 'base_url' => 'https://repo.jfrog.org/artifactory', + 'repository' => 'repo1-cache', + 'org_path' => 'maven-proxy', + 'module' => 'maven-proxy-standalone', + 'base_rev' => '0.2', + 'folder_iteg_rev' => nil, + 'file_iteg_rev' => nil, + 'classifier' => 'app', + 'ext' => 'jar' + ) + end + end + end + context 'SNAPSHOTs' do + it do + is_expected.to run.with_params('https://repo.jfrog.org/artifactory/java.net-cache/com/sun/grizzly/grizzly-framework/2.0.0-SNAPSHOT/grizzly-framework-2.0.0-SNAPSHOT.jar').and_return( + 'base_url' => 'https://repo.jfrog.org/artifactory', + 'repository' => 'java.net-cache', + 'org_path' => 'com/sun/grizzly', + 'module' => 'grizzly-framework', + 'base_rev' => '2.0.0', + 'folder_iteg_rev' => 'SNAPSHOT', + 'file_iteg_rev' => 'SNAPSHOT', + 'classifier' => nil, + 'ext' => 'jar' + ) + end + context 'with classifiers' do + it do + is_expected.to run.with_params('https://repo.jfrog.org/artifactory/java.net-cache/com/sun/grizzly/grizzly-framework/2.0.0-SNAPSHOT/grizzly-framework-2.0.0-SNAPSHOT-javadoc.jar').and_return( + 'base_url' => 'https://repo.jfrog.org/artifactory', + 'repository' => 'java.net-cache', + 'org_path' => 'com/sun/grizzly', + 'module' => 'grizzly-framework', + 'base_rev' => '2.0.0', + 'folder_iteg_rev' => 'SNAPSHOT', + 'file_iteg_rev' => 'SNAPSHOT', + 'classifier' => 'javadoc', + 'ext' => 'jar' + ) + end + it do + is_expected.to run.with_params('https://repo.jfrog.org/artifactory/java.net-cache/com/sun/grizzly/grizzly-framework/2.0.0-SNAPSHOT/grizzly-framework-2.0.0-SNAPSHOT-tests.jar').and_return( + 'base_url' => 'https://repo.jfrog.org/artifactory', + 'repository' => 'java.net-cache', + 'org_path' => 'com/sun/grizzly', + 'module' => 'grizzly-framework', + 'base_rev' => '2.0.0', + 'folder_iteg_rev' => 'SNAPSHOT', + 'file_iteg_rev' => 'SNAPSHOT', + 'classifier' => 'tests', + 'ext' => 'jar' + ) + end + end + end +end