Big bang
This commit is contained in:
commit
3496e77824
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "lib/g.bash"]
|
||||||
|
path = lib/g.bash
|
||||||
|
url = https://code.garrettmills.dev/garrettmills/g.bash
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
9
.idea/docker-isolate.iml
Normal file
9
.idea/docker-isolate.iml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/docker-isolate.iml" filepath="$PROJECT_DIR$/.idea/docker-isolate.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
COPY README.md /README.md
|
||||||
|
|
||||||
|
RUN echo "Fubar!" > /fubar
|
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Goal:
|
||||||
|
|
||||||
|
- Specify a base image name
|
||||||
|
- Specify a final image name
|
||||||
|
- Identify the last layer of the base image
|
||||||
|
- Export the intermediate layers that the final image adds to the base image
|
||||||
|
- Reimport those as a single layer
|
18
docker-isolate.bash
Executable file
18
docker-isolate.bash
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
. "${SCRIPT_DIR}/lib/g.bash/src/g.bash"
|
||||||
|
|
||||||
|
g::source src/setup
|
||||||
|
g::source src/isolate
|
||||||
|
|
||||||
|
g::app dlt
|
||||||
|
|
||||||
|
g::app::command isolate "Build a Docker image with only the changed layers between two images"
|
||||||
|
g::app::command::arg from_image "The base (starting) image, whose layers will be excluded"
|
||||||
|
g::app::command::arg to_image "The final (ending) image, whose additional layers will be isolated"
|
||||||
|
g::app::command::flag output= "Path for the output archive"
|
||||||
|
g::app::command::flag as= "Reimport the isolated filesystem as the specified image name"
|
||||||
|
g::app::command::flag no-base "When specified with --as, the isolated filesystem will NOT be applied to the base image"
|
||||||
|
|
||||||
|
g::app::invoke "$@"
|
1
lib/g.bash
Submodule
1
lib/g.bash
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit c5141fd19e96066f154937f2d401d76560660d36
|
5
lib/jq/README.md
Normal file
5
lib/jq/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
https://github.com/jqlang/jq
|
||||||
|
|
||||||
|
`jq`: Command-line JSON processor
|
||||||
|
Binary version 1.7.1
|
||||||
|
Licensed MIT
|
26
src/dkr_functions.bash
Normal file
26
src/dkr_functions.bash
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
function dkr::save_and_extract() {
|
||||||
|
# image name = $1
|
||||||
|
# file name = $2
|
||||||
|
g::verbose "Extracting image $1 as $2"
|
||||||
|
docker save "$1" -o "$2.tar"
|
||||||
|
mkdir "$2"
|
||||||
|
cd "$2"
|
||||||
|
tar xf "../$2.tar"
|
||||||
|
cd - > /dev/null
|
||||||
|
rm -f "$2.tar"
|
||||||
|
}
|
||||||
|
|
||||||
|
function dkr::import_fs_as_layer() {
|
||||||
|
# tar path = $1
|
||||||
|
# base image = $2
|
||||||
|
# final image = $3
|
||||||
|
docker image import "$1" docker-isolate-flat-temp:latest > /dev/null
|
||||||
|
echo "FROM docker-isolate-flat-temp:latest as isolated" > Dockerfile
|
||||||
|
echo "FROM $2 as from_image" >> Dockerfile
|
||||||
|
echo "COPY --from=isolated / /" >> Dockerfile
|
||||||
|
docker image build -f Dockerfile -t "$3" . > /dev/null
|
||||||
|
docker image rm -f docker-isolate-flat-temp:latest > /dev/null
|
||||||
|
rm -f Dockerfile
|
||||||
|
}
|
82
src/isolate.bash
Normal file
82
src/isolate.bash
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
g::source dkr_functions
|
||||||
|
|
||||||
|
function app::dlt::isolate() {
|
||||||
|
local args="$1"
|
||||||
|
|
||||||
|
from_image="$(g::arg "$args" from_image)"
|
||||||
|
to_image="$(g::arg "$args" to_image)"
|
||||||
|
g::info "Isolating changes from $from_image to $to_image"
|
||||||
|
|
||||||
|
# Resolve the output path for the tarball since we're going to be changing directories
|
||||||
|
output_path="$(g::flag "$args" output)"
|
||||||
|
if [ -z "$output_path" ]; then
|
||||||
|
output_path="$(pwd)/isolated.tar"
|
||||||
|
fi
|
||||||
|
output_path="$(g::path::resolve "$output_path")"
|
||||||
|
g::verbose "Output path: $output_path"
|
||||||
|
|
||||||
|
g::path::cd "$(g::path::tmpdir)"
|
||||||
|
g::verbose "Working dir: $(pwd)"
|
||||||
|
|
||||||
|
# Extract both the base and final image in container layer format
|
||||||
|
dkr::save_and_extract "$to_image" to_image
|
||||||
|
dkr::save_and_extract "$from_image" from_image
|
||||||
|
|
||||||
|
# Load arrays of BOTH the raw layer tarballs and the SHA sums of the layers in the final image
|
||||||
|
to_json_path="$(jq -r '.[0].Config' < to_image/manifest.json)"
|
||||||
|
g::verbose "Found config for to_image: ${to_json_path}"
|
||||||
|
|
||||||
|
readarray -t to_raw_layers <<< "$(jq -r '.[0].Layers[]' < to_image/manifest.json)"
|
||||||
|
readarray -t to_sha_layers <<< "$(jq -r '.rootfs.diff_ids[]' < "to_image/${to_json_path}")"
|
||||||
|
|
||||||
|
# Load an array of the SHA sums of the layers in the base image
|
||||||
|
from_json_path="$(jq -r '.[0].Config' < from_image/manifest.json)"
|
||||||
|
g::verbose "Found config for from_image: ${from_json_path}"
|
||||||
|
|
||||||
|
readarray -t from_sha_layers <<< "$(jq -r '.rootfs.diff_ids[]' < "from_image/${from_json_path}")"
|
||||||
|
|
||||||
|
mkdir isolated
|
||||||
|
cd isolated
|
||||||
|
|
||||||
|
# Extract the isolated layers
|
||||||
|
for ((i = 0 ; i < "${#to_raw_layers[@]}" ; i++)); do
|
||||||
|
raw_layer="${to_raw_layers[$i]}"
|
||||||
|
sha_layer="${to_sha_layers[$i]}"
|
||||||
|
|
||||||
|
# If the SHA sum of this layer appears in the base image, skip it
|
||||||
|
if g::arr::includes "$sha_layer" "${from_sha_layers[@]}"; then
|
||||||
|
g::debug "Skipping layer from base image: $sha_layer"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract the layer into the isolated directory
|
||||||
|
g::info "Unpacking layer: $sha_layer -> $raw_layer"
|
||||||
|
tar xf "../to_image/${raw_layer}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create a tarball w/ the isolated files (special find magic to avoid the leading ./ on paths)
|
||||||
|
g::info 'Creating archive'
|
||||||
|
cd ..
|
||||||
|
find isolated/ \( -type f -o -type d \) -printf "%P\n" | tar cf isolated.tar -C isolated/ -T -
|
||||||
|
|
||||||
|
# Do a bit of cleanup
|
||||||
|
rm -rf to_image from_image isolated
|
||||||
|
|
||||||
|
# If the user asked for the isolated filesystem to be re-imported as a docker image, do that
|
||||||
|
as_image="$(g::flag "$args" as)"
|
||||||
|
if [ -n "$as_image" ]; then
|
||||||
|
if g::flag::has "$args" no-base; then
|
||||||
|
g::info "Creating image $as_image without base layers"
|
||||||
|
docker image import isolated.tar "$as_image" > /dev/null
|
||||||
|
else
|
||||||
|
g::info "Creating image $as_image on top of $from_image"
|
||||||
|
dkr::import_fs_as_layer isolated.tar "$from_image" "$as_image"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create the output archive and exit
|
||||||
|
g::verbose "Moving archive to final destination"
|
||||||
|
mv isolated.tar "$output_path"
|
||||||
|
}
|
7
src/setup.bash
Normal file
7
src/setup.bash
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
SRC_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
export PATH="${SRC_DIR}/lib/jq:${PATH}"
|
||||||
|
|
||||||
|
g::log::setLevel info
|
||||||
|
|
Loading…
Reference in New Issue
Block a user