Artifactory Project — Code Flow Document

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

1. High-level purpose

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.

1.1 Entry point

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.

1.2 Release validation

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.

1.3 Artifactory transaction setup

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.

1.4 Release existence check

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.

1.5 Manifest object creation

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.

1.6 Release URL construction

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.

1.7 Main update sequence

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.

1.8 Product metadata flow

Files:

- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Product.pm - lib/Dap/Artifactory/Const.pm

1.8.1 Fetching products

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.

1.8.2 Updating the XML products section

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>

1.9 JDK metadata flow

Files:

- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Jdk.pm

1.9.1 Fetching JDK metadata

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 }

1.9.2 Updating JDK XML entries

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.

1.10 JDK-list metadata flow

Files:

- lib/Dap/Artifactory/Manifest.pm - lib/Dap/Artifactory/Manifest/Entry/Jdklist.pm

1.10.1 Fetching JDK-list mappings

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.

1.10.2 Updating JDK-list XML

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>

1.11 Pretty-printing and saving XML

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.

1.12 Case 1 call flow summary

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.

2.1 Intended entry point

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.

2.2 Object attributes

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.

2.3 Main deployment flow

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)    }

2.4 Product processing flow

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.

2.5 Product skip logic

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.

2.6 Product archive and path naming

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.

2.7 Product download and staging/extraction

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.

2.8 Download implementation

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.

2.9 Extraction implementation

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.

2.10 JDK processing flow

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

2.11 Case 2 call flow summary

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

3.1 `.dap` dispatcher

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.

3.2 Generic `.dap` processor

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"

3.3 JDK `.dap` processor placeholder

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.

4.1 What is substantially implemented

The following pieces are largely present:

Release validation

Implemented in:

- artifactory-release - lib/Dap/Artifactory/Tx.pm

The script can check whether a release path exists in Artifactory.

XML manifest update

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.

Artifactory transaction layer

Implemented in:

- lib/Dap/Artifactory/Tx.pm

It provides:

- Existence checks. - Downloads using curl. - Uploads using Mojo::UserAgent. - Archive extraction.

Artifact naming

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.

4.2 What is incomplete or broken

No web page code is supplied

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.

Package-name typo in the main deployment module

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.

Manifest construction is broken in the deployment flow

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.

Remote metadata existence checks are probably wrong

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.

Product archive method contains a bug

In lib/Dap/Artifactory/Manifest/Entry/Product.pm:

: $_->version . '.tar.gz';

should be:

: $self->version . '.tar.gz';

This affects Tomcat and JBoss archive naming.

Product extraction contains a typo

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.

Download does not verify success

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

Unsafe shell usage

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.

XML structure is assumed

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.

`.dap` processing is incomplete

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.

Upload is present but not integrated

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 |
version 1