tern
tern copied to clipboard
Packages installed by scripts are not reported
First off, I want to say this project is awesome -- I spent the last day playing around with it and am very excited to (hopefully!) contribute.
Describe the bug
Some projects utilize a shell script to perform package installation / configuration rather than inline statements in the Dockerfile
. Similarly, some projects provide an 'easy install' method to install / configure the project's package / dependencies. Packages installed in this manner are not reported by tern
.
While testing this, I noticed what appears to be an edge case related to #743 / #756 -- install commands that are not 'directly' executed in a RUN
directive are not identified.
To Reproduce
Below are three examples where the package hello
is installed on top of the debian:buster-20200803-slim
image.
Case 1: A local script
hello_script_file
:
FROM debian:buster-20200803-slim
COPY ./hello_install.sh /tmp/hello_install.sh
RUN /tmp/hello_install.sh
hello_install.sh
:
#!/usr/bin/env sh
apt-get update && apt-get -y install hello
tern
output for layer:
------------------------------------------------
Layer 3:
info: Instruction Line: RUN /tmp/hello_install.sh # buildkit
warning:
Unrecognized Commands:/tmp/hello_install.sh # buildkit
File licenses found in Layer: None
Packages found in Layer: None
Licenses found in Layer: None
------------------------------------------------
Case 2: A script fetched with curl
hello_script_curl
:
FROM debian:buster-20200803-slim
RUN apt-get update && apt-get -y install curl
RUN curl -o- https://gist.githubusercontent.com/dotcarls/42bcce3e83e7aa00f35feaf945d16f4d/raw/a9e5243a3a6666b6e72c079293011023e897b485/hello_install | sh
#!/usr/bin/env sh
apt-get update && apt-get -y install hello
tern
output for layer:
------------------------------------------------
Layer 3:
info: Instruction Line: RUN curl -o- https://gist.githubusercontent.com/dotcarls/42bcce3e83e7aa00f35feaf945d16f4d/raw/a9e5243a3a6666b6e72c079293011023e897b485/hello_install | sh # buildkit
warning:
Unrecognized Commands:curl -o- https://gist.githubusercontent.com/dotcarls/42bcce3e83e7aa00f35feaf945d16f4d/raw/a9e5243a3a6666b6e72c079293011023e897b485/hello_install | sh # buildkit
File licenses found in Layer: None
Packages found in Layer: None
Licenses found in Layer: None
------------------------------------------------
Case 3: An echo
'd script:
I'm not familiar enough with the code to say this is really a bug, but I it seems to me like #743 / #756 is trying to parse RUN
directives for package install commands. Please forgive me if I've misunderstood!
hello_script_echo
:
FROM debian:buster-20200803-slim
RUN echo "#!/usr/bin/env sh\n apt-get update && apt-get -y install hello" > /tmp/hello_install.sh && \
chmod +x /tmp/hello_install.sh && \
/tmp/hello_install.sh
tern
output for layer:
------------------------------------------------
Layer 2:
info: Instruction Line: RUN echo "#!/usr/bin/env sh\n apt-get update && apt-get -y install hello" > /tmp/hello_install.sh && chmod +x /tmp/hello_install.sh && /tmp/hello_install.sh # buildkit
warning:
Unrecognized Commands:echo #!/usr/bin/env sh\n apt-get update && apt-get -y install hello > /tmp/hello_install.sh
chmod +x /tmp/hello_install.sh
/tmp/hello_install.sh # buildkit
File licenses found in Layer: None
Packages found in Layer: None
Licenses found in Layer: None
------------------------------------------------
Expected behavior
All installed packages that are managed by the package managers in tern/command_lib/snippets.yml
/ tern/command_lib/base.yml
should be reported by tern
as per Guiding Principle #1. Ideally, the above cases would produce reports similar to the following:
hello_inline
:
FROM debian:buster-20200803-slim
RUN apt-get update && apt-get -y install hello
tern
output for layer:
------------------------------------------------
Layer 2:
info: Instruction Line: RUN apt-get update && apt-get -y install hello # buildkit
warning:
Ignored Commands:apt-get update
info: Layer created by commands: RUN /bin/sh -c apt-get update && apt-get -y install hello # buildkit
info: Retrieved by invoking listing in command_lib/base.yml
versions:
in container:
pkgs=`dpkg --get-selections | cut -f1 -d':' | awk '{print $1}'`
for p in $pkgs; do dpkg -l $p | awk 'NR>5 {print $3}'; done
names:
in container:
dpkg --get-selections | cut -f1 -d':' | awk '{print $1}'
copyrights:
in container:
pkgs=`dpkg --get-selections | cut -f1 -d':' | awk '{print $1}'`
for p in $pkgs; do /bin/cat /usr/share/doc/$p/copyright; echo LICF; done
Invoking commands from command_lib/base.yml:
warning: No listing method for 'srcs'. Additional analysis may be required.
No listing method for 'licenses'. Additional analysis may be required.
File licenses found in Layer: None
Packages found in Layer: hello-2.10-2
Licenses found in Layer: None
------------------------------------------------
Environment you are running Tern on Enter all that apply
- Output of 'tern --version'
root@b01c5367a39b:/src# tern --version
Tern at commit f752cde11f8f5243d0965b6e53dc906b58773dcf
python version = 3.7.3 (default, Jul 25 2020, 13:03:44)
-
Operating System (Linux Distro and version or Mac or Windows) Host is macOS Catalina 10.15.5.
-
Container OS Container is
debian:buster
and is based on theDockerfile
in this repo. I made the following change:
ADD . /src
WORKDIR /src
RUN pip3 install wheel && \
pip3 install -e.[dev]
- Python version (3.6 or higher)
root@b01c5367a39b:/src# python3 --version
Python 3.7.3
Regarding a solution, I believe that relying on heuristics to detect package installation is insufficient for all use-cases. It'd be great to have a flag like --full-audit
that would force tern
to check for packages after each and every RUN
layer rather than only those where tern
can parse a package installation command.
Hi @dotcarls, thanks for opening this issue and for your interest in Tern! We are always welcoming new contributors and would love to see you get involved :)
As you have correctly stated, Tern does not currently detect packages installed by scripts. The reason for this right now is mainly the fact that there are infinite ways that people install packages in containers. You have given examples for 3 ways above but the list goes on and parsing for all the ways is not feasible. We have tried to focus on the most common/popular way that packages are installed, i.e. by parsing the RUN commands for common install directives and package manager utilities. This behavior in Tern is also in line with the best practices that we advocate for surrounding container compliance -- using a package manager to install packages is the best way to produce secure, recent and reproducible container images.
That being said, you bring up a valid point that people will find all sorts of interesting ways to install packages and as the project grows, it makes sense to consider these. While I don't think it's worthwhile for Tern to parse RUN
commands for install scripts (there's too many variables when it comes to scripts), I like your idea about some sort of feature that could check for packages after each and every RUN layer regardless of install directives found. A couple issues that would need to be addressed prior are 1) Performance - There are currently 9 (soon to be 10) different supported package managers in tern/command_lib/base.yml
and running all of these for each RUN layer would definitely be a performance hit, especially for larger images. 2) How to deal with the lack of availability for each package manager by layer. For example, if we mount a layer where a script installs a set of packages using apt
, we as humans know by looking at the script that the apt package manager is available in the layer. But if we try to run the other 8-9 package managers to collect package information in that layer, we are going to have to deal with the failures of running all the commands that try to collect the names
, versions
, licenses
, proj_urls
and copyrights
for each of the 8-9 other package manager utilities not available in the layer. This is another reason why Tern tries to parse the RUN
commands looking for a package manager -- so that it only runs commands from base.yml
where it knows the utility will be available in the layer.
Thoughts?
FYI -->
Case 3: An
echo
'd script: I'm not familiar enough with the code to say this is really a bug, but I it seems to me like #743 / #756 is trying to parseRUN
directives for package install commands. Please forgive me if I've misunderstood!
You are correct about what #743 / #756 is trying to do here. Although, I don't know if I would qualify this as a "bug" since we don't actually try to parse RUN commands for echo/install script behavior at the moment.
Thanks for the detailed response @rnjudge ! Totally agree with regards to the points about best practices / the infeasibility of writing parsers to cover all installation possibilities.
- Performance - There are currently 9 (soon to be 10) different supported package managers in tern/command_lib/base.yml and running all of these for each RUN layer would definitely be a performance hit, especially for larger images.
My expertise is mostly around dpkg
(and related apt
, apt-get
, aptitude
, etc.) and npm
. My first thought is that tern
wouldn't need to execute each package manager on each layer as the list should be filtered to only those where the required binary exists. This would also apply to your 2nd point regarding handling errors -- if which dpkg
(or an equivalent check like stat
ing the well known binary path) doesn't yield a usable binary, simply don't run that analysis. This check would have to be repeated for each layer but I think it would be pretty inexpensive.
I'm not sure if this is applicable for other package managers, but another optimization for dpkg
based package installations would to have tern
check for modifications to the dpgk
status / state files. There's a really cool project called dive that presents you with a nice TUI file tree for each layer in an image. This is what a report looks like for the hello_curl
example I provided:

This shows what I believe would be a fairly reliable heuristic for detecting package changes via dpkg
-- modifications to files under /var/lib/dpkg
indicate a change in package state. I'm not sure what the equivalent would be (or if there is one) for the others like [t]dnf
, yum
, etc., but I'd be happy to do some investigation.
Implementation wise, my understanding is that tern
already collects a list of all files
for a layer, but I'm not sure what would be required to add metadata to these lists to track things like modifications. I do think deepening the analysis of the contents of each layer would be very powerful for tern. Consider a somewhat contrived example like:
FROM my-npm-build-image:latest
COPY ./package*.json /src
RUN npm install --production
FROM my-minimal-node-runtime-image:latest
COPY --from=my-npm-build-image:latest /src/node_modules /app/node_modules
In this case, I don't think tern
would have any knowledge of my-npm-build-image:latest
. I think the only way it could then report npm
packages would be to look into the layer contents to try to extract package metadata from the node_modules
directory. I will certainly concede that maintaining this level of introspection across all the supported package managers might be unrealistic (and probably out of scope for this issue report), but I'd love to hear your thoughts.
My expertise is mostly around
dpkg
(and relatedapt
,apt-get
,aptitude
, etc.) andnpm
. My first thought is thattern
wouldn't need to execute each package manager on each layer as the list should be filtered to only those where the required binary exists. This would also apply to your 2nd point regarding handling errors -- ifwhich dpkg
(or an equivalent check likestat
ing the well known binary path) doesn't yield a usable binary, simply don't run that analysis. This check would have to be repeated for each layer but I think it would be pretty inexpensive.
@dotcarls Agree that this would be a better way to do it without taking huge performance hit. We actually already do this for the base layer using the get_base_bin()
function located in tern/analyze/common.py.
While this function only checks for the existence of a single package manager binary, we could do something similar to check for multiple binaries in subsequent layers.
I'm not sure if this is applicable for other package managers, but another optimization for
dpkg
based package installations would to havetern
check for modifications to thedpgk
status / state files. There's a really cool project called dive that presents you with a nice TUI file tree for each layer in an image.
I will take a look at this project, thanks for pointing me to it!
This shows what I believe would be a fairly reliable heuristic for detecting package changes via
dpkg
-- modifications to files under/var/lib/dpkg
indicate a change in package state. I'm not sure what the equivalent would be (or if there is one) for the others like[t]dnf
,yum
, etc., but I'd be happy to do some investigation.
More investigation on whether other package managers support this would be great! If it's only dpkg that supports this type of detection, I would be slightly more hesitant to implement such big functionality around it as I would prefer to find a detection method that works more universally with the other package managers.
Implementation wise, my understanding is that
tern
already collects a list of allfiles
for a layer, but I'm not sure what would be required to add metadata to these lists to track things like modifications.
Tern does collect a list of all files for a layer. Adding metadata to these file objects to track things like modifications would likely happen in the file_data
class.
I do think deepening the analysis of the contents of each layer would be very powerful for tern. Consider a somewhat contrived example like:
I labeled your two Dockerfile examples so they are easier to reference in my response below :)
Dockerfile A:
FROM my-npm-build-image:latest COPY ./package*.json /src RUN npm install --production
Dockerfile B:
FROM my-minimal-node-runtime-image:latest COPY --from=my-npm-build-image:latest /src/node_modules /app/node_modules
In this case, I don't think
tern
would have any knowledge ofmy-npm-build-image:latest
. I think the only way it could then reportnpm
packages would be to look into the layer contents to try to extract package metadata from thenode_modules
directory. I will certainly concede that maintaining this level of introspection across all the supported package managers might be unrealistic (and probably out of scope for this issue report), but I'd love to hear your thoughts.
When Tern runs, it will look for a package manager binary in the first image layer by checking for the existence of the various package manager paths
in base.yml (see get_base_bin
in tern/analyze/common.py). So for Dockerfile A and B, as long as my-npm-build-image:latest
or my-minimal-node-runtime-image:latest
had one of the supported package manager paths
existing in the first layer filesystem, Tern would find package information in that base layer by running the commands in base.yml
under the found package manager binary. This functionality seems similar to what you are proposing above -- checking for a package manger utility in the layer and then running the necessary commands accordingly. If no package manager binary existed, then yes, Tern would not find anything.
For Dockerfile B, you are also right that Tern would not find any packages in the second layer since they are not being installed by a RUN
command. In Dockerfile A, however, Tern would recognize the npm install
snippet in the RUN command and run the appropriate package collection utilities for that layer.
More investigation on whether other package managers support this would be great! If it's only dpkg that supports this type of detection, I would be slightly more hesitant to implement such big functionality around it as I would prefer to find a detection method that works more universally with the other package managers.
I looked into this and it seems that there are equivalents for all OS variants in base.yml
. It boils down to checking the local package database for rpm
, dpkg
, apk
, and pacman
. I wrote a script to verify this, it does the following:
- From a base image for the OS,
stat
files in the package database directory - Install
nano
-
stat
s the package database directory files again -
diff
the before / after stat output
Diff for fedora
Before <-> After
2020-07-09 06:48:30.000000000 +0000 790528 /var/lib/rpm/Basenames | 2020-08-16 22:10:17.060160000 +0000 790528 /var/lib/rpm/Basenames
2020-07-09 06:48:30.000000000 +0000 8192 /var/lib/rpm/Conflictname | 2020-08-16 22:10:17.064160000 +0000 8192 /var/lib/rpm/Conflictname
2020-07-09 06:48:30.000000000 +0000 204800 /var/lib/rpm/Dirnames | 2020-08-16 22:10:17.066160000 +0000 204800 /var/lib/rpm/Dirnames
2020-07-09 06:48:36.000000000 +0000 12288 /var/lib/rpm/Group | 2020-08-16 22:10:17.062160000 +0000 12288 /var/lib/rpm/Group
2020-07-09 06:48:36.000000000 +0000 12288 /var/lib/rpm/Installtid | 2020-08-16 22:10:17.066160000 +0000 12288 /var/lib/rpm/Installtid
2020-07-09 06:48:36.000000000 +0000 16384 /var/lib/rpm/Name | 2020-08-16 22:10:17.057160000 +0000 16384 /var/lib/rpm/Name
2020-07-09 06:48:36.000000000 +0000 4751360 /var/lib/rpm/Packages | 2020-08-16 22:10:17.056160000 +0000 4775936 /var/lib/rpm/Packages
2020-07-09 06:48:36.000000000 +0000 81920 /var/lib/rpm/Providename | 2020-08-16 22:10:17.064160000 +0000 81920 /var/lib/rpm/Providename
2020-07-09 06:48:30.000000000 +0000 57344 /var/lib/rpm/Requirename | 2020-08-16 22:10:17.063160000 +0000 57344 /var/lib/rpm/Requirename
2020-07-09 06:48:36.000000000 +0000 20480 /var/lib/rpm/Sha1header | 2020-08-16 22:10:17.067160000 +0000 20480 /var/lib/rpm/Sha1header
2020-07-09 06:48:30.000000000 +0000 20480 /var/lib/rpm/Sigmd5 | 2020-08-16 22:10:17.067160000 +0000 20480 /var/lib/rpm/Sigmd5
2020-07-09 06:48:36.000000000 +0000 311296 /var/lib/rpm/__db.001 | 2020-08-16 22:10:18.487160000 +0000 311296 /var/lib/rpm/__db.001
2020-07-09 06:48:36.000000000 +0000 90112 /var/lib/rpm/__db.002 | 2020-08-16 22:10:18.486160000 +0000 90112 /var/lib/rpm/__db.002
2020-07-09 06:48:36.000000000 +0000 263800 /var/lib/rpm/__db.003 | 2020-08-16 22:10:18.488160000 +0000 1318912 /var/lib/rpm/__db.003
Diff for photon
Before <-> After
2020-08-13 00:54:04.000000000 +0000 53248 /var/lib/rpm/Basenames | 2020-08-16 21:09:14.361722000 +0000 57344 /var/lib/rpm/Basenames
2020-08-13 00:54:04.000000000 +0000 20480 /var/lib/rpm/Dirnames | 2020-08-16 21:09:14.363722000 +0000 20480 /var/lib/rpm/Dirnames
2020-08-13 00:54:10.000000000 +0000 8192 /var/lib/rpm/Group | 2020-08-16 21:09:14.361722000 +0000 8192 /var/lib/rpm/Group
2020-08-13 00:54:10.000000000 +0000 8192 /var/lib/rpm/Installtid | 2020-08-16 21:09:14.363722000 +0000 8192 /var/lib/rpm/Installtid
2020-08-13 00:54:10.000000000 +0000 8192 /var/lib/rpm/Name | 2020-08-16 21:09:14.359722000 +0000 8192 /var/lib/rpm/Name
2020-08-13 00:54:10.000000000 +0000 425984 /var/lib/rpm/Packages | 2020-08-16 21:09:14.358722000 +0000 450560 /var/lib/rpm/Packages
2020-08-13 00:54:10.000000000 +0000 32768 /var/lib/rpm/Providename | 2020-08-16 21:09:14.362722000 +0000 32768 /var/lib/rpm/Providename
2020-08-13 00:54:04.000000000 +0000 20480 /var/lib/rpm/Requirename | 2020-08-16 21:09:14.362722000 +0000 20480 /var/lib/rpm/Requirename
2020-08-13 00:54:10.000000000 +0000 8192 /var/lib/rpm/Sha1header | 2020-08-16 21:09:14.364722000 +0000 8192 /var/lib/rpm/Sha1header
2020-08-13 00:54:04.000000000 +0000 8192 /var/lib/rpm/Sigmd5 | 2020-08-16 21:09:14.363722000 +0000 8192 /var/lib/rpm/Sigmd5
> 2020-08-16 21:09:14.320722000 +0000 376832 /var/lib/rpm/__db.001
> 2020-08-16 21:09:14.338722000 +0000 98304 /var/lib/rpm/__db.002
> 2020-08-16 21:09:14.358722000 +0000 684352 /var/lib/rpm/__db.003
Diff for debian
Before <-> After
2020-08-03 07:00:00.000000000 +0000 4096 /var/lib/dpkg/alternatives | 2020-08-16 22:10:26.216160000 +0000 4096 /var/lib/dpkg/alternatives
2020-08-03 07:00:00.000000000 +0000 20480 /var/lib/dpkg/info | 2020-08-16 22:10:26.188160000 +0000 4096 /var/lib/dpkg/info
2020-08-03 07:00:00.000000000 +0000 0 /var/lib/dpkg/lock | 2020-08-16 22:10:26.202160000 +0000 0 /var/lib/dpkg/lock
2020-08-03 07:00:00.000000000 +0000 76300 /var/lib/dpkg/status | 2020-08-16 22:10:26.219160000 +0000 77532 /var/lib/dpkg/status
2020-08-03 07:00:00.000000000 +0000 76304 /var/lib/dpkg/status-old | 2020-08-16 22:10:26.193160000 +0000 77510 /var/lib/dpkg/status-old
2020-08-03 07:00:00.000000000 +0000 4096 /var/lib/dpkg/updates | 2020-08-16 22:10:26.221160000 +0000 4096 /var/lib/dpkg/updates
Diff for alpine
Before <-> After
2020-05-29 14:20:33.000000000 11895 /lib/apk/db/installed | 2020-08-16 22:10:28.000000000 16224 /lib/apk/db/installed
2020-05-29 14:20:33.000000000 10752 /lib/apk/db/scripts.tar | 2020-08-16 22:10:28.000000000 10752 /lib/apk/db/scripts.tar
2020-05-29 14:20:33.000000000 76 /lib/apk/db/triggers | 2020-08-16 22:10:28.000000000 76 /lib/apk/db/triggers
Diff for centos
Before <-> After
2020-08-09 21:39:32.000000000 +0000 950272 /var/lib/rpm/Basenames | 2020-08-16 22:10:47.848531000 +0000 950272 /var/lib/rpm/Basenames
2020-08-09 21:39:31.000000000 +0000 8192 /var/lib/rpm/Conflictname | 2020-08-16 22:10:47.851531000 +0000 8192 /var/lib/rpm/Conflictname
2020-08-09 21:39:32.000000000 +0000 286720 /var/lib/rpm/Dirnames | 2020-08-16 22:10:47.853531000 +0000 286720 /var/lib/rpm/Dirnames
2020-08-09 21:39:32.000000000 +0000 8192 /var/lib/rpm/Group | 2020-08-16 22:10:47.850531000 +0000 8192 /var/lib/rpm/Group
2020-08-09 21:39:32.000000000 +0000 12288 /var/lib/rpm/Installtid | 2020-08-16 22:10:47.853531000 +0000 12288 /var/lib/rpm/Installtid
2020-08-09 21:39:32.000000000 +0000 20480 /var/lib/rpm/Name | 2020-08-16 22:10:47.844531000 +0000 20480 /var/lib/rpm/Name
2020-08-09 21:39:32.000000000 +0000 18944000 /var/lib/rpm/Packages | 2020-08-16 22:10:47.844531000 +0000 18944000 /var/lib/rpm/Packages
2020-08-09 21:39:32.000000000 +0000 1798144 /var/lib/rpm/Providename | 2020-08-16 22:10:47.851531000 +0000 1798144 /var/lib/rpm/Providename
2020-08-09 21:39:32.000000000 +0000 86016 /var/lib/rpm/Requirename | 2020-08-16 22:10:47.851531000 +0000 86016 /var/lib/rpm/Requirename
2020-08-09 21:39:32.000000000 +0000 24576 /var/lib/rpm/Sha1header | 2020-08-16 22:10:47.854531000 +0000 24576 /var/lib/rpm/Sha1header
2020-08-09 21:39:32.000000000 +0000 16384 /var/lib/rpm/Sigmd5 | 2020-08-16 22:10:47.854531000 +0000 16384 /var/lib/rpm/Sigmd5
2020-08-09 21:39:32.000000000 +0000 352256 /var/lib/rpm/__db.001 | 2020-08-16 22:10:48.313531000 +0000 352256 /var/lib/rpm/__db.001
2020-08-09 21:39:32.000000000 +0000 98304 /var/lib/rpm/__db.002 | 2020-08-16 22:10:48.290531000 +0000 98304 /var/lib/rpm/__db.002
2020-08-09 21:39:32.000000000 +0000 1318912 /var/lib/rpm/__db.003 | 2020-08-16 22:10:48.294531000 +0000 1318912 /var/lib/rpm/__db.003
Diff for opensuse
Before <-> After
2020-08-16 07:54:21.000000000 +0000 143360 /var/lib/rpm/Basenames | 2020-08-16 22:59:58.774335000 +0000 143360 /var/lib/rpm/Basenames
2020-08-16 07:54:21.000000000 +0000 57344 /var/lib/rpm/Dirnames | 2020-08-16 22:59:58.775335000 +0000 57344 /var/lib/rpm/Dirnames
2020-08-16 07:54:21.000000000 +0000 8192 /var/lib/rpm/Group | 2020-08-16 22:59:58.774335000 +0000 8192 /var/lib/rpm/Group
2020-08-16 07:54:21.000000000 +0000 8192 /var/lib/rpm/Installtid | 2020-08-16 22:59:58.775335000 +0000 8192 /var/lib/rpm/Installtid
2020-08-16 07:54:21.000000000 +0000 8192 /var/lib/rpm/Name | 2020-08-16 22:59:58.772335000 +0000 8192 /var/lib/rpm/Name
2020-08-16 07:54:21.000000000 +0000 4042752 /var/lib/rpm/Packages | 2020-08-16 22:59:58.771335000 +0000 4083712 /var/lib/rpm/Packages
2020-08-16 07:54:21.000000000 +0000 1757184 /var/lib/rpm/Providename | 2020-08-16 22:59:58.775335000 +0000 1757184 /var/lib/rpm/Providename
2020-08-16 07:54:21.000000000 +0000 57344 /var/lib/rpm/Requirename | 2020-08-16 22:59:58.774335000 +0000 57344 /var/lib/rpm/Requirename
2020-08-16 07:54:21.000000000 +0000 16384 /var/lib/rpm/Sha1header | 2020-08-16 22:59:58.775335000 +0000 16384 /var/lib/rpm/Sha1header
2020-08-16 07:54:21.000000000 +0000 16384 /var/lib/rpm/Sigmd5 | 2020-08-16 22:59:58.775335000 +0000 16384 /var/lib/rpm/Sigmd5
Diff for archlinux
Before <-> After
> 2020-08-16 22:58:41.182530000 +0000 4096 /var/lib/pacman/local/nano-5.0-1
So it seems like there should be a pretty reliable and consistent way to detect package changes based on changes to the filesystem. Now the ~hard~ fun part -- making tern
aware of this information so it can intelligently decide whether or not to trigger the packages
commands in base.yml
. If you don't mind, I'll start looking into that -- if you have any pointers, suggestions, or comments I'd appreciate them!