The Tugboat DevX tool

NOTE:If you only looking for the code, jump to Source code

Context

Welcome

This project was written at General Atomics (GA), and is the property of GA. Thanks to the generosity of GA, a subset of the code is available to review. I attempted to provide perhaps too much background information because so many parts of the code are not available to review. However, I hope what I did provide can give you an understanding of how I structure code.

This project (and previous iterations) were almost entirely my own work. Some LLMs were used, and I estimate about 5% of the code is written by LLM (after review, of course). A few other edits here and there were provided by team members, but largely this is my project and should show how I thought about architecture and design. There is a weakness in that the lack of contributions means that there is almost certainly much room for improvement, but I also feel that this project is a very fair perspective on my software development maturity and where I am currently (2026) at in my career.

If you want to jump straight to the code, go to The Project.

Why this exists

tugboat is the latest iteration of a development and compliance tool used when packaging up releases to be shipped to the customer site. Most deployments that use this deploy to classified (air gapped) environments where automated deploys are forbidden and which have an increased amount of cybersecurity compliance such as:

  • all non-public code must have static code analysis (SAST) scans

  • all containers must have dependency analysis performed

  • all vulnerabilities over a certain score (depends on the customer) must be mitigated, eliminated, or deliberately accepted

There are two critical needs that this project provides for:

  1. Enumerating the transitive dependency tree for projects that ship 10-20 different containers, some with 10+ proprietary Java jars embedded including:

    1. deduplication, specific to each dependency version and profile (when the same version of a jar can be compiled in different ways),

    2. and the source code (git repo and commit sha) for each component.

  2. Collecting all vulnerabilities and cross-referencing each found vulnerability with previously justified or accepted vulnerabilities.

Obviously, the faster a team can get through all these requirements the more quickly we can iterate and the more cheaply (less staff and/or less days to do compliance) they can develop.

For example, teams are free to tweak container images (remove code, update base images) much more often and get feedback of how that adjusted the cybersecurity posture now that they can generate these reports in just a matter of seconds.

Compliance vs security

This project aims to solve the compliance problem over the security problem. Security is hard to quantify, it can be dynamic (adversaries are not static), and it requires accepting many unknowns. Compliance and security frameworks help manage that, but speaking as a previous cybersecurity professional, there is a difference. Ultimately the customer requires compliance so we provide compliance.

History

This is the third (or thereabouts) major revision, because as different parts of this problem were solved, more teams wanted to take advantage of it and the more often it would be needed to be run.

It started as a series of bash scripts and a lot of manual labor that took ~10 ish hours to complete. Only a few individuals were even qualified to do that work (like me). Every run would reveal additional or different dependency chains, so it was re-architected into a python project.

The python project worked better now that dependency trees got a couple levels deep, but it still suffered from name/version collisions, non-deterministic results, and a large degree of non-portability (only ever worked on 3 different developer laptops). I hosted an architectural review with other senior devs across the company before re-writing it in Rust.

I chose Rust because I was somewhat familiar in it and I knew it could produce a statically compiled binary (solving the portability problem). This project essentially is about merging and referencing many different kinds of metadata, so using Rust enabled a higher quality code base due to stricter typing, algebraic data types, and more compile time guarantees. Aggressive caching, deterministic results, integration to other systems are major features that were designed for. Over the last 6 months or so, requests for additional major features have been completely supported by this architecture so it appears that the re-write was a success. Third time is the charm!

The Project

A redacted version of this project is on codeberg. However, the following sections provide additional information that can help fill in some of the gaps.

README

The documentation is a weak point in this project, but it may answer questions you have: README.adoc.

Original structure

Only some code can be provided, so the rest of the project is described here.

Tree

Here is the tree view of the original codebase.

63M fededge-cli
   60 ├──.gitignore
 5778 ├──.gitlab-ci.yml
 1068 ├──bacon.toml
  95K ├──Cargo.lock
 2158 ├──Cargo.toml
  66K ├──config.toml
  41K ├──containers
  12K │  ├──fededge-cli
 1931 │  │  ├──Dockerfile
 1891 │  │  └──justfile
  12K │  ├──maven_yq
  587 │  │  ├──Dockerfile
 1317 │  │  └──justfile
  12K │  └──trivy_lz4_oras
  456 │     ├──Dockerfile
 1332 │     └──justfile
  66K ├──examples
  477 │  ├──debug.yaml
 1421 │  ├──example_kind_config.yaml
  49K │  ├──jobs
 2131 │  │  ├──git_clone.yaml
 6193 │  │  ├──hostpath_test.yaml
 3420 │  │  ├──list_images.yaml
 2008 │  │  ├──maven_list_dependencies.yaml
 5476 │  │  ├──read_write_many_test.yaml
 3396 │  │  ├──README.adoc
 1692 │  │  └──trivy_scan.yaml
  900 │  └──README.adoc
 4431 ├──justfile
  23K ├──README.adoc
  160 ├──rustfmt.toml
  16K ├──source_map.toml
 1.1M ├──src
 164K │  ├──bin
  31K │  │  ├──edgecli.rs
 127K │  │  └──tugboat
  14K │  │     ├──cli.rs
  70K │  │     ├──command
  25K │  │     │  ├──clone.rs
 2666 │  │     │  ├──dependency.rs
 2183 │  │     │  ├──list.rs
   79 │  │     │  ├──mod.rs
  17K │  │     │  ├──scan.rs
 4370 │  │     │  └──spider.rs
 5546 │  │     ├──context.rs
  28K │  │     └──main.rs
 922K │  └──lib
  27K │     ├──cache.rs
 9785 │     ├──common_cli.rs
 115K │     ├──config
   84 │     │  ├──mod.rs
 9369 │     │  ├──pattern.rs
  22K │     │  ├──project.rs
  44K │     │  ├──source_map.rs
 1606 │     │  ├──target.rs
  18K │     │  └──user.rs
  30K │     ├──errors.rs
 1631 │     ├──generic.rs
  230 │     ├──lib.rs
  66K │     ├──local
 6766 │     │  ├──exec.rs
  464 │     │  ├──mod.rs
 4450 │     │  ├──outcome.rs
 1627 │     │  ├──parallel.rs
 4564 │     │  ├──resource_monitor.rs
  25K │     │  └──solve.rs
  21K │     ├──oci.rs
 176K │     ├──processing
 139K │     │  ├──dependencies
 5875 │     │  │  ├──bom.rs
  12K │     │  │  ├──container.rs
  21K │     │  │  ├──cyclonedx.rs
  16K │     │  │  ├──git.rs
  44K │     │  │  ├──graph.rs
  22K │     │  │  ├──maven.rs
  280 │     │  │  └──mod.rs
 6352 │     │  ├──kubernetes_parsing.rs
 1807 │     │  ├──list_images.rs
 3529 │     │  ├──mod.rs
  16K │     │  └──output.rs
 238K │     ├──remote
 115K │     │  ├──cluster
  53K │     │  │  ├──components.rs
  26K │     │  │  ├──connection.rs
  19K │     │  │  ├──helpers.rs
 4709 │     │  │  └──mod.rs
  675 │     │  ├──definitions.rs
  317 │     │  ├──mod.rs
  10K │     │  ├──oci.rs
  15K │     │  ├──solve.rs
  73K │     │  ├──solve_slop.rs
 4635 │     │  └──task.rs
 119K │     ├──spec
  25K │     │  ├──docker
 1765 │     │  │  ├──mod.rs
  14K │     │  │  └──schema.rs
  946 │     │  ├──filters.rs
 9891 │     │  ├──git.rs
 4236 │     │  ├──helmfile_template.rs
  20K │     │  ├──maven
 2131 │     │  │  ├──cyclonedx.rs
  227 │     │  │  ├──mod.rs
 5165 │     │  │  └──pom.rs
 1485 │     │  ├──mod.rs
 1704 │     │  ├──syft.rs
  37K │     │  └──trivy
  14K │     │     ├──mod.rs
  956 │     │     ├──parse.rs
  10K │     │     └──schema.rs
  49K │     ├──task
 5538 │     │  ├──context.rs
 8870 │     │  ├──definition.rs
  334 │     │  ├──mod.rs
  19K │     │  └──tracker.rs
  758 │     ├──uptime.rs
  37K │     ├──utils.rs
 6958 │     └──version_check.rs
  66K ├──templates
 8192 │  ├──graph
 1050 │  │  └──graph.mmd.jinja
  53K │  └──task
  49K │     └──spec
  212 │        ├──docker_inspect.sh.jinja
  352 │        ├──git_clone.sh.jinja
  376 │        ├──helmfile_template.sh.jinja
  379 │        ├──mvn_cyclonedx.sh.jinja
  352 │        ├──mvn_effective_pom.sh.jinja
  561 │        ├──README.adoc
  422 │        ├──syft_cyclonedx.sh.jinja
  376 │        ├──trivy_db_info.sh.jinja
  492 │        ├──trivy_fetch_specific_db.sh.jinja
  663 │        ├──trivy_image.sh.jinja
  591 │        └──trivy_latest_db.sh.jinja
 266K └──tests
 2361    ├──cache.rs
 8246    ├──config_project.rs
 2013    ├──config_user.rs
  70K    ├──edgecli
 3298    │  ├──clone_maven_project.rs
 2286    │  ├──completion.rs
 4096    │  ├──fixtures
  725    │  ├──help.rs
 1272    │  ├──helpers.rs
  21K    │  ├──k8s_integration.rs
 2476    │  ├──list.rs
  536    │  ├──main.rs
 7642    │  ├──trivy.rs
 1264    │  └──verify.rs
 4096    ├──fixtures
  29K    ├──local.rs
 4679    ├──oci.rs
 7066    ├──processing.rs
 2258    ├──remote.rs
 8962    ├──task.rs
 4038    ├──tracker.rs
 5070    ├──trivy.rs
  90K    └──tugboat
  23K       ├──clone.rs
 7208       ├──dependencies.rs
 3219       ├──filter_images.rs
  12K       ├──fixtures
  175       │  ├──filter_images_input0.yaml
 4075       │  └──filter_images_input1.yaml
 1405       ├──help.rs
 1272       ├──helpers.rs
 2624       ├──init.rs
 1665       ├──list.rs
  195       ├──main.rs
 8068       ├──spider.rs
 5982       └──trivy.rs

Code Size

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Language              Files        Lines         Code     Comments       Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 AsciiDoc                  4          721          534            0          187
 Dockerfile                3           82           62            5           15
 Jinja2                   11          152          131           19            2
 Just                      4          300          185           48           67
 Markdown                  1           86            0           70           16
 TOML                      5         1705         1287          207          211
 YAML                     10          918          858           59            1
─────────────────────────────────────────────────────────────────────────────────
 Rust                     98        26508        21571         2375         2562
 |- Markdown              77         2299           68         1951          280
 (Total)                            28807        21639         4326         2842
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Total                   136        32771        24696         4734         3341
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

(as measured by tokei)

Help text

This is what you get when running tugboat --help, lightly redacted:

Locally executed helmfile-based project developent utility

Usage: tugboat [OPTIONS] <COMMAND>

Commands:
  init           Initialize the cache and config. Automatically called when needed.
  list           List information about a deployment
  blake3hash     Compute a blake3 hash of a file
  filter-images  Filter kubernetes yaml to extract images present
  trivy          Runs trivy vulnerability scanner
  set-trivy-db   Download a specific Trivy db to your local machine
  clone          Clone the source repo(s) for one or more artifacts
  verify         Verify that the installed tugboat binary is correctly configured
  dependencies   Follow and list all the transitive dependencies of an artifact [aliases: deps, dependency]
  spider         Follow the transitive dependencies of every artifact in a project
  completion     Generate completion for your shell
  help           Print this message or the help of the given subcommand(s)

Options:
  -v, --verbose...
          Turn debugging information on

  -c, --cache <CACHE>
          Set the location of the cache, defaults to `$XDG_DIR/.cache/edge`. Can also be set with the environment variable `EDGE_CACHE`. Command line flag takes precidence over environment variable

      --clean
          Clean the cache, cluster, etc so this command can start from a "fresh slate"

      --timeout <TIMEOUT>
          Set the individual task timeout (rather than the default of 10 min). Because timeouts are handled on a task-by-task basis, the overall run of the binary may result in an effective timeout of some multiple of this value. Accepts a string that gets parsed into a duration.

          Usage:

          --timeout 30s

          --timeout 5m10s

  -m, --local-source-map
          Use `$PWD/source_map.toml` as artifact source mapping configuration file. This file maps Maven, OCI/Docker artifacts to their git repository sources when pom.xml has incorrect or missing SCM information

      --no-color
          Prevent the tool from writing colorful ANSI output

      --skip-update-check
          Skip checking the server for a newer release of this tool for this run. To disable the check without CLI flags, set `check_for_updates = false` in your user config. This CLI flag will override whatever is set in your config file

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Examples:
  cat templated.yaml | tugboat filter-images | xargs docker pull
  tugboat trivy [proj] table image example.com/.../debian:bookworm-slim --db-date 2026-01-01
  tugboat dependencies --filtered -o graph.mmd mvn com.example:artifact:24.0.0:jar mermaid
  tugboat dependencies --filtered -o graph.text image example.com/.../example:5.10.1 text
  tugboat spider [proj]
  tugboat spider -f -m [proj] plain | tugboat clone -m --clone-in ./repos
  tugboat spider -f -m [proj] plain | tugboat clone --dry-run
  tugboat clone -m --clone-to ./my-artifact mvn com.example:artifact:22.0.1
  tugboat set-trivy-db 2026-06-29

Source code

There are 4 modules available to read:

  1. processing - this is where metadata is merged, cross-referenced, etc. This is the main "business logic" module.

  2. local - this is how tasks (which are templated bash scripts) get executed (solved), cached, etc.

    • errors - common error definitions.

    • generic - traits.

The README provides information on other aspects of the codebase, especially about how caching is handled.