This document describes the runtime flow through the supplied Perl code for the two main cases identified in description.md:
1. Updating status.xml from an Artifactory release. 2. Downloading/staging/extracting binaries on destination hosts.
It also comments on the completeness of the implementation.
The analysis covers all supplied files:
- artifactory-release - lib/Dap/Artifactory.pm - lib/Dap/Artifactory/Tx.pm - lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Product.pm - lib/Dap/Artifactory/Manifest/Entry/Jdk.pm - lib/Dap/Artifactory/Manifest/Entry/Jdklist.pm - lib/Dap/Artifactory/Const.pm - lib/Dap/Artifactory/PrettyPrint.pm - lib/Dap/Artifactory/Dapfile.pm - lib/Dap/Artifactory/Dapfile/Generic.pm - lib/Dap/Artifactory/Dapfile/Jdk.pm - list.json - description.md
According to description.md, this project appears intended to support the controlled release of Oracle/WebLogic and other DAP platform binaries from Artifactory to destination hosts.
The supplied code supports, or partially supports, these operations:
1. Check that a named Artifactory release exists. 2. Read release metadata from Artifactory. 3. Update a local status.xml file with products, JDKs, and JDK-list mappings. 4. Download product and JDK archives from Artifactory. 5. Stage WebLogic tarballs. 6. Extract Tomcat, JBoss, and JDK archives into staging directories. 7. Process local .dap metadata files, although that path is incomplete.
There is no web-page or web-controller implementation in the supplied files. The code currently consists of command-line and library components.
This is the clearest implemented flow. It starts in artifactory-release and continues through lib/Dap/Artifactory/Manifest.pm.
File: artifactory-release
The script defines two packages:
package Dap::Artifactory::Release { ... }
package main { ... }
The main package uses Dap::Script to parse command-line arguments:
my $script = Dap::Script->new(
"release|r=s" => \my $release,
"statusxml=s" => \my $status_xml,
);
Expected command-line inputs are:
artifactory-release \
--release <release-name-or-path> \
--statusxml <path-to-status.xml>
The script then creates a release object:
Dap::Artifactory::Release->new(
release => $release,
status_xml => $status_xml,
)->run;
The Dap::Artifactory::Release object requires:
has status_xml => ( is => 'rw', required => 1 );
has release => ( is => 'rw', required => 1 );
It also creates:
has log => ( is => 'rw', default => sub { Mojo::Log->new });
has tx => ( is => 'rw', default => sub { Dap::Artifactory::Tx->new });
The transaction object comes from lib/Dap/Artifactory/Tx.pm.
File: artifactory-release
The main method is:
sub run {
my $self = shift;
if (not $self->tx->file_exist($self->release)) {
$self->log->error("RELEASE: " . $self->release . ' NOT FOUND on Artifactory');
exit 1;
}
$self->log->info('RELEASE ' . $self->release . ' OK');
Dap::Artifactory::Manifest->new(
release => $self->release,
status_xml => $self->status_xml,
)->update;
}
Purpose:
1. Check whether the release exists in Artifactory. 2. If not found, log an error and exit. 3. If found, update the local status.xml.
The existence check is delegated to Dap::Artifactory::Tx.
File: lib/Dap/Artifactory/Tx.pm
The transaction class reads Artifactory configuration from environment variables:
has user => ( is => 'rw', lazy => 1, default => $ENV{ARTIFACTORY_USER} );
has token => ( is => 'rw', lazy => 1, default => $ENV{ARTIFACTORY_TOKEN} );
has url => ( is => 'rw', lazy => 1, default => $ENV{ARTIFACTORY_URL} );
Expected environment inputs are:
ARTIFACTORY_USER
ARTIFACTORY_TOKEN
ARTIFACTORY_URL
The default repository path is:
has default_artifatory_path => (
is => 'rw',
default => sub { '/dist-private-local/com/db/dap' }
);
There is a spelling error in the attribute name: default_artifatory_path should probably be default_artifactory_path.
The user agent is constructed lazily:
has ua => (
is => 'ro',
default => sub {
my $self = shift;
my $ua = Mojo::UserAgent->new;
$ua->on(start => sub {
my ($ua, $tx) = @_;
$tx->req->url->userinfo(
sprintf( "%s:%s", $self->user, $self->token )
);
$tx->req->url->base(Mojo::URL->new($self->url));
});
$ua->connect_timeout(5);
$ua->inactivity_timeout(5);
return $ua;
},
lazy => 1
);
Purpose:
- Attach Artifactory credentials to each request. - Set the Artifactory base URL. - Configure short connection and inactivity timeouts.
File: lib/Dap/Artifactory/Tx.pm
The method used by artifactory-release is:
sub file_exist {
my $self = shift;
my $file = shift;
my $url = $self->url . "/api/storage/dist-private-local/com/db/dap/$file";
$self->log->debug("GET $url");
$self->ua->get($url)->res->is_success;
}
For example, if:
ARTIFACTORY_URL=https://artifactory.example.com/artifactory
release=release-2024-08
then the check is approximately:
GET https://artifactory.example.com/artifactory/api/storage/dist-private-local/com/db/dap/release-2024-08
Purpose:
- Use Artifactory’s storage API to check whether a path exists.
File: lib/Dap/Artifactory/Manifest.pm
If the release exists, artifactory-release creates:
Dap::Artifactory::Manifest->new(
release => $self->release,
status_xml => $self->status_xml,
)->update;
The manifest class requires a release:
has release => ( is => 'rw', required => 1 );
It also has:
has status_xml => (
is => 'rw',
default => sub { $const->dap_etc . '/status.xml' }
);
The local XML document is loaded lazily:
has dom => (
is => 'rw',
lazy => 1,
default => sub {
my $self = shift;
XML::LibXML->load_xml(location=>$self->status_xml)
}
);
Purpose:
- Represent the remote Artifactory release. - Load and update the local status.xml.
File: lib/Dap/Artifactory/Manifest.pm
The private method:
sub _release_url {
my $self = shift;
return $self->tx->url
. $self->tx->default_artifatory_path
. '/'
. $self->release;
}
For a release named release-2024-08, this builds:
$ARTIFACTORY_URL/dist-private-local/com/db/dap/release-2024-08
This is the base URL from which release metadata is fetched.
File: lib/Dap/Artifactory/Manifest.pm
The top-level method is:
sub update {
my $self = shift;
$self->update_products;
$self->update_jdks;
$self->update_jdklist;
$self->save;
return $self;
}
Call flow:
Dap::Artifactory::Manifest->update
-> update_products
-> update_jdks
-> update_jdklist
-> save
-> pretty
-> Dap::Artifactory::PrettyPrint->pretty_print
-> XML::LibXML::Document->toFile
Purpose:
- Rebuild the products section. - Rebuild the JDK entries. - Rebuild the JDK-list mappings. - Pretty-print and save status.xml.
Files:
- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Product.pm - lib/Dap/Artifactory/Const.pm
In lib/Dap/Artifactory/Manifest.pm:
sub products {
my $self = shift;
if ($self->tx->file_exist('products')) {
my $aref = $self->tx->ua
->get($self->_release_url . '/products')
->res
->json;
return c(@$aref)->map(sub {
Dap::Artifactory::Manifest::Entry::Product->new($_)
});
}
c();
}
Intended remote path:
<release-url>/products
Expected content:
[
{
"technology": "tomcat",
"name": "Tomcat 9.0.71",
"status": "Unsupported",
"version": "tomcat-9.0.71",
"jdklist": "java1187",
"securitystatus": "Insecure High",
"upgradedue": "2023-09-12"
}
]
The Product entry class in lib/Dap/Artifactory/Manifest/Entry/Product.pm requires:
upgradedue
name
technology
securitystatus
status
jdklist
version
Optional:
releasedate
Technology constants are defined in lib/Dap/Artifactory/Const.pm:
sub WEBLOGIC { 'dap' };
sub TOMCAT { 'tomcat' };
sub JBOSS { 'jboss' };
sub JAVA { 'java' };
Important point: WebLogic is represented internally as technology value dap.
In lib/Dap/Artifactory/Manifest.pm:
sub update_products {
my $self = shift;
$self->log->debug("Updating products...");
my $dom = $self->dom;
my $node = c($dom->findnodes("//cluster"))->first;
c($node->getChildrenByTagName('products'))->map(sub {
$_->unbindNode
});
my $products = $dom->createElement('products');
$self->products->map(sub {
my $p = $_;
my $el = $dom->createElement($p->technology);
c(qw/upgradedue name technology securitystatus status jdklist version/)
->sort(sub { $a cmp $b})
->map(sub { $el->setAttribute($_ => $p->$_) });
$products->appendChild($el);
});
$node->appendChild($products);
return $self;
}
Purpose:
1. Find the <cluster> node. 2. Remove the existing <products> node. 3. Create a new <products> node. 4. Add one child element per remote product. 5. Use the product’s technology as the XML element name.
Example output:
<products>
<tomcat
jdklist="java1187"
name="Tomcat 9.0.71"
securitystatus="Insecure High"
status="Unsupported"
technology="tomcat"
upgradedue="2023-09-12"
version="tomcat-9.0.71" />
</products>
Files:
- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Jdk.pm
In lib/Dap/Artifactory/Manifest.pm:
sub jdks {
my $self = shift;
if ($self->tx->file_exist('java/jdks')) {
my $res = $self->tx->ua
->get($self->_release_url . '/java/jdks')
->res;
return c(@{$res->json})
->sort(sub { versioncmp($a->{location}, $b->{location}) })
->map(sub {
Dap::Artifactory::Manifest::Entry::Jdk->new($_);
});
}
c();
}
Intended remote path:
<release-url>/java/jdks
Expected JSON content:
[
{
"name": "java8-openjdk-401",
"version": "17.0.10",
"location": "openjdk1.17.0_10",
"status": "Invest",
"securitystatus": "Secure",
"upgradedue": "2025-12-31"
}
]
The JDK entry class in lib/Dap/Artifactory/Manifest/Entry/Jdk.pm requires:
location
upgradedue
securitystatus
status
name
version
It also defines archive and path helpers:
sub archive { shift->location . '.tar.gz' }
sub af_file { 'java/' . shift->archive }
sub absolute_install_name { $const->dap_apps .'/'. shift->location }
sub absolute_staging_name { $const->staging_apps .'/'. shift->location }
In lib/Dap/Artifactory/Manifest.pm:
sub update_jdks {
my $self = shift;
my $dom = $self->dom;
my $jdklist_node = c($dom->findnodes("//cluster/jdks"))->first;
c($jdklist_node->getChildrenByTagName('jdk'))->map(sub {
$_->unbindNode
});
$self->jdks->map(sub {
my $install_dir = $const->dap_apps . "/" . $_->location;
my $jdk = $dom->createElement('jdk');
for my $attr (sort qw/name version location status securitystatus upgradedue/) {
$jdk->setAttribute($attr => $_->$attr);
$jdklist_node->appendChild($jdk);
}
});
return $self;
}
Purpose:
1. Locate <cluster>/<jdks>. 2. Remove existing direct <jdk> children. 3. Create new <jdk> elements from remote JSON. 4. Append them under <jdks>.
Example intended output:
<jdks>
<jdk
location="openjdk1.17.0_10"
name="java8-openjdk-401"
securitystatus="Secure"
status="Invest"
upgradedue="2025-12-31"
version="17.0.10" />
</jdks>
Implementation note:
The current code appends the same $jdk node inside the attribute loop. This should be moved outside the loop. It probably does not create duplicate nodes with XML::LibXML, but it is conceptually wrong and should be cleaned up.
Files:
- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Jdklist.pm
In lib/Dap/Artifactory/Manifest.pm:
sub jdklist {
my $self = shift;
if ($self->tx->file_exist('java/jdklist')) {
my $content = $self->tx->ua
->get($self->_release_url . '/java/jdklist')
->res
->body;
my %priority;
my $h = {};
my $c = c(split($/, $content))
->grep(sub { not m/^\s*$/ and not m/^\s*\#/ })
->map(sub {
my ($list, $jdk) = split(/[\s\t]+/);
my $entry = Dap::Artifactory::Manifest::Entry::Jdklist->new(
list => $list,
name => $jdk,
priority => ++$priority{$list}
);
$h->{$list} = c() unless $h->{$list};
push @{$h->{$list}}, $entry;
$entry;
});
return wantarray ? %$h : $c;
}
}
Intended remote path:
<release-url>/java/jdklist
Expected format:
# list-name jdk-name
java87 java8-openjdk-401
java87 java8-openjdk-402
java1187 java11-openjdk-21
The entry class in lib/Dap/Artifactory/Manifest/Entry/Jdklist.pm is simple:
has [qw/list name/] => ( is => 'rw', required => 1 );
has priority => ( is => 'rw' );
Purpose:
- Parse a plain-text mapping of JDK-list names to JDK names. - Assign priority based on order within each list. - Return either a flat collection or a hash grouped by list name.
In lib/Dap/Artifactory/Manifest.pm:
sub update_jdklist {
my $self = shift;
my $dom = $self->dom;
my $remote_jdklist = $self->jdklist;
my %remote_jdklist = $self->jdklist;
my $local_jdklist = c($dom->findnodes("//cluster/jdks/jdklist"));
my $jdklist_node = c($dom->findnodes("//cluster/jdks"))->first;
c($jdklist_node->getChildrenByTagName('jdklist'))->map(sub {
$_->unbindNode
});
c(keys %remote_jdklist)->sort(sub { versioncmp($a, $b) })->map(sub {
my $list = $dom->createElement('jdklist');
$list->setAttribute(name => $_);
$remote_jdklist{$_}->map(sub {
my $jdk = $dom->createElement('jdk');
$jdk->setAttribute(name => $_->name);
$jdk->setAttribute(priority => $_->priority);
$list->appendChild($jdk);
});
$jdklist_node->appendChild($list);
});
return $self;
}
Purpose:
1. Remove existing <jdklist> nodes under <cluster>/<jdks>. 2. Recreate each list from the remote java/jdklist file. 3. Add child <jdk> nodes with name and priority.
Example output:
<jdks>
<jdklist name="java87">
<jdk name="java8-openjdk-401" priority="1" />
<jdk name="java8-openjdk-402" priority="2" />
</jdklist>
</jdks>
Files:
- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/PrettyPrint.pm
In lib/Dap/Artifactory/Manifest.pm:
sub save {
my $self = shift;
$self->log->debug("Writing " . $self->status_xml);
$self->pretty;
$self->dom->toFile($self->status_xml);
return $self;
}
The pretty method calls:
sub pretty {
my $self = shift;
Dap::Artifactory::PrettyPrint->new->pretty_print($self->dom);
return $self;
}
lib/Dap/Artifactory/PrettyPrint.pm is a vendored XML pretty-printer based on XML::LibXML::PrettyPrint.
Purpose:
- Strip and normalize whitespace. - Indent XML elements. - Save a readable status.xml.
artifactory-release
-> Dap::Script->new(...)
Purpose: parse --release and --statusxml
-> Dap::Artifactory::Release->new(...)->run
Purpose: orchestrate release validation and manifest update
-> Dap::Artifactory::Tx->file_exist($release)
Purpose: check Artifactory storage API for release path
-> Dap::Artifactory::Manifest->new(...)->update
Purpose: update status.xml from remote release metadata
-> update_products
-> products
-> Tx->file_exist('products')
-> UA GET <release-url>/products
-> Product->new(...)
-> remove old <products>
-> create new <products>
-> update_jdks
-> jdks
-> Tx->file_exist('java/jdks')
-> UA GET <release-url>/java/jdks
-> Jdk->new(...)
-> remove old <jdk>
-> create new <jdk>
-> update_jdklist
-> jdklist
-> Tx->file_exist('java/jdklist')
-> UA GET <release-url>/java/jdklist
-> Jdklist->new(...)
-> remove old <jdklist>
-> create new <jdklist>
-> save
-> pretty
-> PrettyPrint->pretty_print
-> XML::LibXML->toFile
This flow is represented mainly by lib/Dap/Artifactory.pm, with download and extraction operations in lib/Dap/Artifactory/Tx.pm.
However, this part is less complete than Case 1.
File: lib/Dap/Artifactory.pm
The file is intended to provide the main downloader/stager class, but it contains a major package typo:
package Dap::Artifatory {
The expected package name from the file path would be:
package Dap::Artifactory {
Because of this, code that does:
use Dap::Artifactory;
Dap::Artifactory->new(...)
will not get the expected package. This is a serious completeness issue.
File: lib/Dap/Artifactory.pm
The class defines:
has force_download => ( is => 'rw', default => 0 );
Purpose:
- If false, skip products/JDKs that are already installed or staged. - If true, ignore existing local copies and re-download.
It also creates:
has log => (
is => 'ro',
default => sub { Mojo::Log->new },
);
has tx => (
is => 'ro',
default => sub { Dap::Artifactory::Tx->new },
lazy => 1,
);
And:
has manifest => (
is => 'rw',
default => sub { Dap::Artifactory::Manifest->new }
);
This is currently broken because Dap::Artifactory::Manifest in lib/Dap/Artifactory/Manifest.pm requires a release attribute:
has release => ( is => 'rw', required => 1 );
So this default constructor will fail unless the manifest class is changed or the release is supplied.
File: lib/Dap/Artifactory.pm
The main method is:
sub run {
my $self = shift;
$self->process_jboss;
$self->process_tomcat;
$self->process_weblogic;
$self->process_jdks;
}
Purpose:
- Process all supported product families. - Then process all JDKs.
Call flow:
Dap::Artifactory->run
-> process_jboss
-> process_tomcat
-> process_weblogic
-> process_jdks
Technology constants come from lib/Dap/Artifactory/Const.pm:
sub WEBLOGIC { 'dap' };
sub TOMCAT { 'tomcat' };
sub JBOSS { 'jboss' };
sub JAVA { 'java' };
The wrappers are:
sub process_weblogic { shift->_process_product(WEBLOGIC) }
sub process_tomcat { shift->_process_product(TOMCAT) }
sub process_jboss { shift->_process_product(JBOSS) }
Files:
- lib/Dap/Artifactory.pm - lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Product.pm - lib/Dap/Artifactory/Tx.pm
The method _process_product drives product installation/staging:
sub _process_product {
my $self = shift;
my $technology = shift;
$self->manifest->products->map(sub {
my $p = $_;
return unless $p->technology eq $technology;
...
});
}
Purpose:
- Iterate over all products in the manifest. - Filter to the requested technology. - Download and stage/extract each missing product.
The manifest products come from lib/Dap/Artifactory/Manifest.pm, via:
$self->manifest->products
Each product is represented by Dap::Artifactory::Manifest::Entry::Product, defined in lib/Dap/Artifactory/Manifest/Entry/Product.pm.
File: lib/Dap/Artifactory.pm
Inside _process_product:
if (not $self->force_download) {
if (-e $p->absolute_install_name){
$self->log->info("Already installed under " . $p->absolute_install_name);
return;
}
if (-e $p->absolute_staging_name) {
$self->log->info("Already exists under " . $p->absolute_staging_name);
return;
}
}
Purpose:
- Avoid unnecessary downloads. - Skip if the product already exists in the final install location. - Skip if the product already exists in the staging location. - If force_download is true, bypass these checks.
The path helpers are implemented in lib/Dap/Artifactory/Manifest/Entry/Product.pm.
File: lib/Dap/Artifactory/Manifest/Entry/Product.pm
The product object defines:
sub is_weblogic {
my $self = shift;
return $self->technology eq WEBLOGIC;
}
Since WEBLOGIC is dap, a product is WebLogic if:
$p->technology eq 'dap'
Archive naming is intended to be:
sub archive {
my $self = shift;
return $self->is_weblogic
? 'weblogic-' . $self->version . '.tar.bz2'
: $_->version . '.tar.gz';
}
There is a bug here:
$_->version
should be:
$self->version
Intended archive names:
WebLogic: weblogic-<version>.tar.bz2
Tomcat: <version>.tar.gz
JBoss: <version>.tar.gz
The WebLogic archive naming is confirmed by list.json, which contains entries such as:
/weblogic-14.1.1.0.240628.tar.bz2
/weblogic-14.1.1.0.231220.tar.bz2
/weblogic-12.2.1.4.231010.tar.bz2
Artifactory file paths are produced by:
sub af_file {
my $self = shift;
return $self->is_weblogic
? 'weblogic/' . $self->archive
: $self->technology . '/' . $self->archive;
}
Examples:
weblogic/weblogic-14.1.1.0.240628.tar.bz2
tomcat/tomcat-9.0.71.tar.gz
jboss/jboss-eap-7.4.17.tar.gz
Local staging/install paths are:
sub absolute_staging_name {
my $self = shift;
my $path = $self->is_weblogic
? $const->staging_wltarballs . '/' . $self->archive
: $const->staging_apps . '/' . $self->version;
}
sub absolute_install_name {
my $self = shift;
return $self->is_weblogic
? $const->dap_apps . '/weblogic-'. $self->version
: $const->dap_apps . '/' . $self->version;
}
These rely on $const from Dap::Const, which is used in several files but not supplied in the provided documents.
File: lib/Dap/Artifactory.pm
After skip checks, _process_product does:
$self->log->info("Downloading " . $p->af_file);
if ($self->tx->file_exist($p->af_file)) {
my $downloaded = $self->tx->download(
file => $p->af_file,
archive => $p->archive
);
if ($p->is_weblogic) {
rename($downloaded, $p->absolute_staging_name)
? $self->log->info("$downloaded -> ".$p->absolute_staging_name)
: $self->log->error("Failed to move $downloaded - $!");
}
else {
$self->tx->extract(dir => $p->verison, file => $p->archive);
unlink($downloaded)
? $self->log->debug("Removing temporary file $downloaded")
: $self->log->error("Failed to cleanup $downloaded - $!");
}
}
else {
$self->log->error("NOT FOUND ON REMOTE! " . $p->af_file);
}
Purpose:
1. Check that the archive exists in Artifactory. 2. Download it. 3. For WebLogic: - Move the archive into the WebLogic tarball staging directory. 4. For Tomcat/JBoss: - Extract the archive into the staging applications directory. - Delete the temporary archive.
There is a typo:
$p->verison
should be:
$p->version
As written, the Tomcat/JBoss extraction path will fail unless there is an accidental verison method.
File: lib/Dap/Artifactory/Tx.pm
The download method shells out to curl:
sub download {
my $self = shift;
my $opt = {@_};
my $path = $opt->{path} // $self->default_artifatory_path;
my $file = $opt->{file};
my $tgz = $opt->{archive};
$self->log->info("Downloading $tgz, please wait...");
chdir($const->dap_tmp);
my @cmd = ($const->prog_curl, "-OJkLu",
sprintf( "%s:%s", $self->user, $self->token ),
$self->url . "${path}/${file}"
);
system(@cmd);
$self->log->debug("move $tgz -> ".$const->dap_tmp."/$tgz");
rename($tgz, $const->dap_tmp."/$tgz");
return $const->dap_tmp . "/$tgz";
}
Purpose:
- Download an archive from Artifactory into $const->dap_tmp.
The code comments explain why curl is used:
# For some funny reason Mojo::UserAgent does not download entire file from Artifactory therefore
# the need to shell out to external curl command to download the archive.
Completeness issues:
- It does not check the return code from system(@cmd). - It does not verify the file exists after download. - It does not verify the file size. - It blindly renames the expected file. - It depends on $const->prog_curl and $const->dap_tmp from Dap::Const, not supplied here.
File: lib/Dap/Artifactory/Tx.pm
The extract method is:
sub extract {
my $self = shift;
my $opts = {@_};
my $dir = $opts->{dir};
my $tgz = $opts->{file};
if (not $dir) {
confess("Missing dir parameter");
}
if (not $tgz) {
confess "Missing file parameter";
}
my $staging = $const->staging_apps . '/' . $dir;
if (-e $staging) {
my @cmd = ('rm', '-fr', $staging);
$self->log->debug("@cmd");
system("@cmd");
}
my @tar = (
"/bin/tar",
"--no-same-owner",
"--extract",
"--auto-compress",
"--directory=".$const->staging_apps,
"--file=".$const->dap_tmp."/$tgz"
);
$self->log->debug("@tar");
system(@tar) == 0
or $self->log->error("Failed to exctract $staging - $! / $?");
}
Purpose:
1. Remove any existing staging directory. 2. Extract the tar archive into $const->staging_apps. 3. Use --no-same-owner to avoid preserving archive ownership.
Completeness and safety issues:
- system("@cmd") invokes a shell. This should use list form system(@cmd) or a Perl filesystem API. - There is no validation that the extracted directory actually exists afterward. - Error message has a typo: exctract. - It assumes the archive extracts into a directory matching $dir.
Files:
- lib/Dap/Artifactory.pm - lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Jdk.pm - lib/Dap/Artifactory/Tx.pm
In lib/Dap/Artifactory.pm:
sub process_jdks {
my $self = shift;
$self->manifest->jdks
->grep(sub { $_->location ne 'unknown'})
->map(sub {
my $j = $_;
if (not $self->force_download) {
return if -e $j->absolute_install_name;
return if -e $j->absolute_staging_name;
}
if ($self->tx->file_exist($j->af_file)) {
my $downloaded = $self->tx->download(
file => $j->af_file,
archive => $j->archive
);
$self->tx->extract(
dir => $j->location,
file => $j->archive
);
unlink($downloaded)
? $self->log->debug("Removing temporary file $downloaded")
: $self->log->error("Failed to cleanup $downloaded - $!");
}
else {
$self->log->error($j->af_file . " not found on Artifactory");
}
});
}
Purpose:
1. Get JDK entries from the manifest. 2. Ignore entries with location unknown. 3. Skip if already installed or staged, unless forced. 4. Check Artifactory for the archive. 5. Download the archive. 6. Extract the archive. 7. Delete the temporary archive.
The JDK helper methods from lib/Dap/Artifactory/Manifest/Entry/Jdk.pm are:
sub archive { shift->location . '.tar.gz' }
sub af_file { 'java/' . shift->archive }
sub absolute_install_name { $const->dap_apps .'/'. shift->location }
sub absolute_staging_name { $const->staging_apps .'/'. shift->location }
Example:
location: openjdk1.17.0_10
archive: openjdk1.17.0_10.tar.gz
af_file: java/openjdk1.17.0_10.tar.gz
Assuming the package-name typo and manifest-construction issue are fixed, the intended flow is:
Dap::Artifactory->new(...)->run
-> process_jboss
-> _process_product('jboss')
-> manifest->products
-> Product->technology
-> Product->absolute_install_name
-> Product->absolute_staging_name
-> Product->af_file
-> Tx->file_exist(...)
-> Tx->download(...)
-> Tx->extract(...)
-> unlink temporary archive
-> process_tomcat
-> _process_product('tomcat')
-> same pattern as JBoss
-> process_weblogic
-> _process_product('dap')
-> manifest->products
-> Product->is_weblogic
-> Product->archive
-> Product->af_file
-> Tx->file_exist(...)
-> Tx->download(...)
-> rename archive to staging_wltarballs
-> process_jdks
-> manifest->jdks
-> Jdk->absolute_install_name
-> Jdk->absolute_staging_name
-> Jdk->af_file
-> Tx->file_exist(...)
-> Tx->download(...)
-> Tx->extract(...)
-> unlink temporary archive
Although not one of the two main cases in description.md, the .dap files are relevant to the overall project and are present in the supplied code.
Files:
- lib/Dap/Artifactory/Dapfile.pm - lib/Dap/Artifactory/Dapfile/Generic.pm - lib/Dap/Artifactory/Dapfile/Jdk.pm
In lib/Dap/Artifactory/Dapfile.pm:
sub process {
my $self = shift;
my $dir = shift;
$self->file(path("$dir/.dap"));
if (not $self->file->exists) {
$self->log->error($self->file . " error - $!");
return;
}
$self->log->debug("Processing " . $self->file);
$self->discovery;
return $self;
}
Purpose:
- Look for a .dap metadata file in a supplied directory. - Parse it if it exists.
The discovery method does:
my $meta = from_json($self->file->slurp);
if (exists $meta->{technology}) {
$meta->{technology} = lc(trim($meta->{technology}));
$meta->{selector} =
$meta->{technology} eq TOMCAT ? 'tomcat' :
$meta->{technology} eq WEBLOGIC ? 'dap' :
$meta->{technology} eq JBOSS ? 'jboss' :
undef;
Dap::Artifactory::Dapfile::Generic->new($meta)->process;
}
else {
$self->log->error("Could not find technology in the metadata file.");
return;
}
Purpose:
- Determine the product technology. - Select the XML element name. - Dispatch to Dap::Artifactory::Dapfile::Generic.
In lib/Dap/Artifactory/Dapfile/Generic.pm:
sub process {
my $self = shift;
my $filename = '/home/nedevala/status.xml';
my $dom = XML::LibXML->load_xml(location=>$filename);
my $tc = c($dom->findnodes("//cluster/products/".$self->selector))
->grep(sub{$_->{version} eq $self->version})
->first;
if ($tc) {
...
}
else {
...
}
Dap::Artifactory::PrettyPrint->new->pretty_print($dom);
$dom->toFile($filename, 2)
? $self->log->debug("Wrote $filename")
: $self->log->error("Failed to write $filename");
}
Purpose:
- Open a local status.xml. - Find an existing product entry with the same version. - Update selected attributes if found. - Otherwise create a new XML element. - Pretty-print and save.
Completeness issues:
- The XML path is hard-coded to /home/nedevala/status.xml. - JDK-specific .dap handling is not connected. - There is no schema validation. - The sample JDK JSON after __END__ in lib/Dap/Artifactory/Dapfile.pm contains invalid JSON syntax:
"location"="openjdk1.17.0_10"
It should be:
"location": "openjdk1.17.0_10"
In lib/Dap/Artifactory/Dapfile/Jdk.pm, the model exists:
has technology => ( is => 'rw', required => 1 );
has name => ( is => 'rw', required => 1 );
has priority => ( is => 'rw', required => 1 );
has jdklist => ( is => 'rw', required => 1 );
has version => ( is => 'rw', required => 1 );
has location => ( is => 'rw', required => 1 );
has status => ( is => 'rw', required => 1 );
has securitystatus => ( is => 'rw', required => 1 );
has upgradedue => ( is => 'rw', required => 1 );
has releasedate => ( is => 'rw', required => 1 );
But the processor is empty:
sub process {
my $self = shift;
}
So JDK .dap processing is not implemented.
The following pieces are largely present:
Implemented in:
- artifactory-release - lib/Dap/Artifactory/Tx.pm
The script can check whether a release path exists in Artifactory.
Implemented in:
- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Product.pm - lib/Dap/Artifactory/Manifest/Entry/Jdk.pm - lib/Dap/Artifactory/Manifest/Entry/Jdklist.pm - lib/Dap/Artifactory/PrettyPrint.pm
The code can conceptually:
- Fetch product metadata. - Fetch JDK metadata. - Fetch JDK-list mappings. - Rewrite status.xml. - Pretty-print the result.
Implemented in:
- lib/Dap/Artifactory/Tx.pm
It provides:
- Existence checks. - Downloads using curl. - Uploads using Mojo::UserAgent. - Archive extraction.
Implemented in:
- lib/Dap/Artifactory/Manifest/Entry/Product.pm - lib/Dap/Artifactory/Manifest/Entry/Jdk.pm - lib/Dap/Artifactory/Const.pm
The WebLogic naming convention is supported by the sample Artifactory listing in list.json.
Although description.md says the project was partially developed to give a web page to release Oracle binaries to destination hosts, none of the supplied files implement a web UI.
There are no Mojolicious controllers, routes, templates, forms, or web handlers in the provided documents.
In lib/Dap/Artifactory.pm:
package Dap::Artifatory {
This should almost certainly be:
package Dap::Artifactory {
Until fixed, this module will not behave as expected when loaded as Dap::Artifactory.
In lib/Dap/Artifactory.pm:
has manifest => ( is => 'rw', default => sub { Dap::Artifactory::Manifest->new } );
But in lib/Dap/Artifactory/Manifest.pm:
has release => ( is => 'rw', required => 1 );
Therefore, the default construction of the manifest will fail.
A release must be supplied, for example:
Dap::Artifactory::Manifest->new(release => $release)
or Manifest.pm must be redesigned to support local-only operation.
In lib/Dap/Artifactory/Manifest.pm, the code checks:
$self->tx->file_exist('products')
$self->tx->file_exist('java/jdks')
$self->tx->file_exist('java/jdklist')
But it fetches:
$self->_release_url . '/products'
$self->_release_url . '/java/jdks'
$self->_release_url . '/java/jdklist'
The checks do not include the release path.
They probably should be:
$self->tx->file_exist($self->release . '/products')
$self->tx->file_exist($self->release . '/java/jdks')
$self->tx->file_exist($self->release . '/java/jdklist')
As written, the code may check one Artifactory location and fetch another.
In lib/Dap/Artifactory/Manifest/Entry/Product.pm:
: $_->version . '.tar.gz';
should be:
: $self->version . '.tar.gz';
This affects Tomcat and JBoss archive naming.
In lib/Dap/Artifactory.pm:
$self->tx->extract(dir => $p->verison, file => $p->archive);
should be:
$self->tx->extract(dir => $p->version, file => $p->archive);
This breaks non-WebLogic product extraction.
In lib/Dap/Artifactory/Tx.pm, download calls:
system(@cmd);
but does not check the return code.
It should verify:
- system(@cmd) == 0 - the file exists - the file is non-empty - optionally checksum/hash matches Artifactory metadata
In lib/Dap/Artifactory/Tx.pm:
system("@cmd");
is used for rm -fr.
This invokes the shell and should be replaced with safer list-form execution or Perl-native removal.
In lib/Dap/Artifactory/Manifest.pm, the code assumes the XML already has:
<cluster>
<jdks>
</jdks>
</cluster>
If these nodes are missing, the code will fail.
Files:
- lib/Dap/Artifactory/Dapfile.pm - lib/Dap/Artifactory/Dapfile/Generic.pm - lib/Dap/Artifactory/Dapfile/Jdk.pm
Problems:
- JDK .dap processing is not implemented. - Generic.pm uses a hard-coded XML path. - Sample JSON contains syntax errors. - There is no schema validation.
lib/Dap/Artifactory/Tx.pm contains an upload method, but no supplied flow appears to call it.
The project is best described as a partially complete Perl/Moo toolkit for managing DAP binary releases in Artifactory.
The most complete flow is the artifactory-release command in artifactory-release, which validates a release and uses lib/Dap/Artifactory/Manifest.pm to update status.xml.
The second intended flow, implemented in lib/Dap/Artifactory.pm, is a downloader/stager for WebLogic, Tomcat, JBoss, and JDK archives. Conceptually the design is clear, but this flow is currently not production-ready because of several blocking issues:
- wrong package name, - missing required manifest release, - archive naming bug, - extraction typo, - weak download error handling, - unsafe shell call, - and reliance on external Dap::Const.
The .dap metadata path is even less complete. It contains useful ideas for processing per-package metadata, but JDK handling is empty and the generic processor still uses a developer-specific hard-coded path.
In short:
| Area | Completeness |
|---|---|
| Release validation | Mostly implemented |
| `status.xml` update from release metadata | Partially implemented, needs path-check fixes and validation |
| Product/JDK download and staging | Designed but currently broken by several bugs |
| WebLogic archive naming | Implemented and confirmed by `list.json` |
| `.dap` metadata processing | Prototype/incomplete |
| Web UI | Not present in supplied files |
| Production hardening | Not complete |