Helpful utilities for working with images
To format:
$ make format
To run tests:
$ make test
Helpful utilities for working with images
License: Apache License 2.0
Today if you do the following:
local.NewImage("some-image", dockerClient, local.FromBaseImage("some-base-image"))
and some-base-image
doesn't exist on the daemon, the image will fail to save on Windows with a cryptic error:
Failed to save image:failed to write image to the following tags: [lifecycle:101f194-dirty: image load 'lifecycle:101f194-dirty'. first error: embedded daemon response: re-exec error: exit status 1: output: ProcessBaseLayer \\?\C:\ProgramData\Docker\windowsfilter\49e547ec94502d3e8d1e8de5be79a631f6247fb6fadb57bf288c4c22981a84af: Cannot create a file when that file already exists.] exit status 1
Through experimentation, it seems like the image will actually save successfully on Linux, though I am not sure how this works and why it is different.
pack
and lifecycle/tools/image/main.go currently circumvent this issue by pulling the base image prior to initializing the new image.
imgutil should produce a helpful error in this scenario.
Prerequisite for buildpacks/pack#469
At present, any link added to a Windows layer entry gets Files
prepended to the header.Name
but not to the header.Linkname
meaning the expanded file would be invalid.
This WindowsWriter should be changed to prepend Files
to header.Linkname
as well as header.Name
.
Related implementation:
https://github.com/moby/buildkit/blob/56e2ea083a4a392da9fb3a25a9e7cae34430948e/util/winlayers/differ.go#L245-L247
Please see related PR and discussion: #73
As part of the implementation of the export to OCI Layout feature we required to translate an image reference and map it to a path in the local filesystem. This operation is useful for Lifecycle implementors but also for Platform implementors, that's why the best place to offer this utility method is on imgUtil repository.
Considering an image reference refers to either a tag reference or digest reference. It has the following formats
<registry>/<repo>:<tag>
<registry>/<repo>@<algorithm>:<digest>
Expose a method like:
classDiagram
class layout {
ParseRefToPath(imageRef string) (string, error)
}
This method parses the given image reference to local path directory following the rules:
<registry>/<repo>/<tag>
<registry>/<repo>/<algorithm>/<digest>
See issue 152 for more context
As part of the discussion of the export to OCI layout RFC we require to add the org.opencontainers.image.ref.name
annotation to the final OCI layout image when it is saved.
The reason to that is because other tools in the ecosystem uses or add that annotation. For example:
org.opencontainers.image.ref.name
to be present.copy
in OCI layout format.When an image is created using the layout.NewImage(...)
method the resulting image on disk after Save()
, SaveAs()
is called should contain the org.opencontainers.image.ref.name
annotation as described in the spec
Saves an OCI layout image on disk as a tarball
Given a LocalImage
created from a tag or digest reference, the returned Identifier
should be a DigestIdentifier
if the docker inspect contains a RepoDigest
with repository matching the repository from the original requested reference.
Note While implementing this we might want to improve the workflow for fetching identifiers. Identifiers should only be available for saved images, not those that have been mutated but not yet commited.
The recent update of the docker/docker lib #68 has created negative cascading effects in regards to how pack can import lifecycle
+ imgutil
+ docker
libraries (plus other dependencies that update golang's sys
package).
Prior to said PR, we were using v1.4.2-0.20190924003213-a8608b5b67c7 (the same version as GGCR).
At a high-level, most of the issues stem from that fact that docker/docker (aka moby/moby aka docker/engine ๐) is not go module
friendly...
Relevant conversations:
...making some lower level dependencies trail off and cause conflicts such as:
Various attempts are resolving this issue have yield erratically unexpected failures (primarily affecting Windows).
Solutions attempted:
sys
package of upgraded docker version: ory/dockertest#208 (comment)I don't propose we indefinitely remain at the prior version but we should be more strategic in the upgrade based on our current dependency structure. I suspect that pack ran into this issue sooner rather than later but other platforms may find similar issues.
I would propose that we test and coordinate the upgrade in the following order GGCR
-> imgutil
-> lifecycle
-> pack
.
We would like to be able to call img.SaveAsFile(filepath)
(or something similar) and have our images saved as some sort of archive.
This ask has popped up in a couple of places:
buildpacks/pack#1125
buildpacks/lifecycle#423
buildpacks/pack#768 (needed for some of the exporter changes discussed here)
Based on discoveries mentioned here (buildpacks/pack#351 (comment)), we need to use gzip.DefaultCompression
when creating a tarball.Layer
to simulate docker push
compression and layer digest generation.
This could easily be addressed when google/go-containerregistry#574 is merged or if needed we could compress the tar file ourselves and use tarball.LayerFromOpener
.
This question is spurred from conversation around this PR: #113 which attempts to use io.CopyN
instead of io.Copy
when untarring an image. Our use of io.Copy
was flagged by muse-dev as being potentially vulnerable to DOS attacks.
In order for the change to io.CopyN
to be meaningful, we should check the total number of bytes read as we are reading and throw an error if the number exceeds some threshold.
Some questions:
As part of the implementation of the export to OCI Layout feature the RFC defines the
analyzed.toml
file will be updated to include the reference
format in case of layout is being used.
[image]
reference = "<image reference>"
[run-image]
reference = "<image reference>"
[previous-image]
reference = "<image reference>"
Where
[image|run-image|previos-image].reference
MUST be either:
We required to translate an image reference with the format [path]@[digest]
and map it to an Identifier. Because imgUtil defines the layout.Identifier()
implementation the best place to offer this utility method is on imgUtil repository.
Considering an image identifier with the format [path]@[digest]
Expose a method like:
classDiagram
class layout {
ParseIdentifier(identifier string) (Identifier, error)
}
Images created using the local package seem to experience errors when the remote package is used to save the previously created image against a remote artifactory. (When performing a remote.Write
)
The artifactory returns the error MANIFEST_INVALID: manifest invalid;
Preliminary investigations make it seem like the Config files created are different for either image (remote vs local). What was a little weird is that we were able to use the docker CLI to push the image to artifactory but were unable to use GGCR to perform the same operation.
Additionally, performing an operation like mutate.CreatedAt
on the image updated the config to become a "valid" one.
Let me know if I can provide any further information.
As preparation for buildpacks/pack#814, I was hoping to get the test coverage of code that I port over to this repo, to ensure that it's sufficiently tested to be a library component, but I noticed that codecov (or a similar code coverage tool) isn't used.
If you could add, so I could have more reassurance that my code is sufficiently tested, and so users could have more confidence in the repo, that would be very helpful.
Right now, several image parameters get set by default on a new, empty image, which can only be overridden with Set*()
. It would be convenient to be able to set these in the constructor.
The most useful constructor parameters for common image config properties would be:
os
- remote only, to override linux
defaultos.version
- local/remote, for setting compatible Windows kernel version.architecture
- local/remote, to override amd64
defaultThis should also be considered along with #54 which will need a way to disambiguate manifest lists during FromBaseImage
(also os
, os.version
, architecture
, though these fields map to the manifest platform not the image config)
ggcr's Daemon implementation appears to now support daemon.Image
and daemon.Write
and appears that we could use it for the local
implementation. If the daemon
package has feature-parity with remote
, I believe we could get many benefits from using daemon
:
remote
and local
- perhaps eventually a single implementation with flags for remote/local.daemon
package: https://github.com/google/go-containerregistry/tree/master/pkg/v1/daemon
Currently, a layout.Image
implements the v1.Image
interface (because the underlying "base" image is embedded). With some small changes, remote.Image
could follow this pattern. local.Image
is trickier because of the daemon, but with some work we could assemble enough information to return a v1.ConfigFile
, erroring if a manifest is requested. We'll need to be careful to distinguish what is the "saved" image vs the "working image" (e.g., after AddLayer
is called the layer is added to the working image but not the image that's saved in the disk/registry/daemon) but if we do it right we can remove a bunch of duplication across the layout/remote/local packages and make the interface more intuitive to work with.
The OCI spec defines an Image Index concept to handle multiple manifests in an OCI image. Currently, we expose an Image interface that is consumed no matter the implementation (daemon, registry or layout), this is very convenient to decouple the implementation.
This issue suggests the definition and implementation of a new interface to expose the operations to deal with an Image Index .
Note: I am using underscore to refer to a package, for example, foo_Bar referes to a Bar class in the foo package
classDiagram
ManifestList <|-- remote_ManifestList
ManifestList <|-- local_ManifestList
remote_ManifestList o-- ManifestListOption
class ManifestListOption {
+WithMediaTypes(requested imgutil.MediaTypes)
+WithPath(path string)
}
class remote_ManifestList {
+NewManifestList(repoName string, keychain authn.Keychain, ops []ManifestListOption) ManifestList
}
class local_ManifestList {
+NewManifestList(repoName string, path string, ops []ManifestListOption) ManifestList
}
class ManifestList {
<<interface>>
+Add(repoName string) error
+Remove(repoName string) error
+Save() error
}
imgutil.remote
package in a manifest_list.go
fileWe currently omit base image layers when writing to the docker daemon in order to improve performance. However some container runtimes like Podman that implement the daemon API do not allow an image load with missing layers. Therefore if our first attempt at writing fails, we should fall back on the slow/complete behavior and provide all layer tars.
context: buildpacks/pack#925
The default is great, but it would be nice to be able to override the v1Config() function (or potentially wrap it) to inject platform-specific values into the image config. I'm happy to make the relevant PR(s) if others think this is a good idea :)
In our tests, we have some cleanup steps that are wrapped in an h.AssertNil
. If the cleanup fails, we'll halt execution and not do other cleanup steps.
If any cleanup steps fail, we should set the test outcome to failed, but we should continue with other cleanup. See conversation here: #114 (comment) for how pack accomplishes this.
This might be an opportunity to start migrating the local image to use ggcr. ggcr has a tarball type image that would work well for this https://godoc.org/github.com/google/go-containerregistry/pkg/v1/tarball. We don't need to block merging on this but I wanted to point it out.
Originally posted by @ekcasey in https://github.com/buildpack/imgutil/diffs/5
For consistency with other buildpacks repos
Periodically when executing the lifecycle against the daemon errors like this occur:
ERROR: rebase working image: manifest.json had unexpected number of entries: 2
This happens when we are saving an image from the docker daemon to disk and we unexpectedly get more than one manifest.
Lines 542 to 544 in afd98bd
We should figure out how we get into this state and determine a mitigation.
E.g.,
remote.NewImage(tags[0], authn.DefaultKeychain, remote.FromBaseImage("mcr.microsoft.com/windows/nanoserver:1809")
(a manifest list)
fails with
connect to repo store 'mcr.microsoft.com/windows/nanoserver:1809': no child with platform amd64/linux in index mcr.microsoft.com/windows/nanoserver:1809
Whereas
remote.NewImage(tags[0], authn.DefaultKeychain, remote.FromBaseImage("mcr.microsoft.com/windows/nanoserver:1809-amd64")
(an image index)
works just fine.
Existing, unrelated PAXRecords should not prevent MSWINDOWS.rawsd
from being initialized. However, we should still only initialize MSWINDOWS.rawsd
when it itself is not yet set.
imgutil/layer/windows_writer.go
Lines 42 to 45 in 7b9490c
TBD
For a standard pack
developer flow, I'd like to develop locally (on a MacOS workstation) and run tests quickly against a local daemon (e.g. Docker Desktop with Linux containers) and a remote daemon via DOCKER_HOST
(e.g. local Windows VM running Docker Desktop with Windows containers). Other useful scenarios could be running tests against older daemons or Docker-in-Docker rootless daemons and be able to fix issues before committing/pushing.
This would also give some coverage for DOCKER_HOST
functionality, which technically works though tests fail.
I'm interested in opening a PR to change the tests as follows:
localhost:<registry.Port>/random-image-name
to <DOCKER_HOST ip>:<registry.Port>/random-image-name
only when DOCKER_HOST
is setpack
's tests.remote
to query using test registry Basic Auth credentials. Two possible approaches:
auth.DefaultKeychain
by setting DOCKER_CONFIG
to point to a test config with auths
containing creds which default keychain loads.remote.NewImage
to use a test keychain instead that loads the credentials.remote
to not push/pull to daemon, instead use go-containerregistry
to directly work with manifests and images directly on the registry.See here - if we can, we should send an OCI formatted tar to the daemon. This will be true when we have all the layers (from first doing a docker image save
).
The changes can be made here. We should just stream an OCI layout-formatted tar to the daemon instead of building up the image in the old format.
Replace use of the now unsupported set-env
command with environment files in all GHA tasks.
context:
It appears some internally generated/downloaded layers (note: temp files, not daemon layers) get left behind after Rebase
and ReuseLayer
. This does not impact the output images, just takes up disk space in the environment where these functions are called.
Repro (with pack rebase
... pack src -> lifecycle src):
git clone github.com/buildpacks/samples
cd samples
make build-alpine
mkdir tmpstuff
TMPDIR=$PWD/tmpstuff pack rebase sample-kotlin-gradle-app:alpine
du -h -a tmpstuff
### OUTPUT ###
4.0K tmpstuff//imgutil.local.image.032493284/a99d3f0802478557b0a44efae5e5a4928414594a9dd9ea69a8d1da181b89a9df.json
4.0K tmpstuff//imgutil.local.image.032493284/6994ead759479b7875be89121e6e9f48910f8cdac4f57778d85302f465d0adaa/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/6994ead759479b7875be89121e6e9f48910f8cdac4f57778d85302f465d0adaa/json
4.0K tmpstuff//imgutil.local.image.032493284/6994ead759479b7875be89121e6e9f48910f8cdac4f57778d85302f465d0adaa/VERSION
12K tmpstuff//imgutil.local.image.032493284/6994ead759479b7875be89121e6e9f48910f8cdac4f57778d85302f465d0adaa
2.7M tmpstuff//imgutil.local.image.032493284/525f14e123a954c5ac257a0ab234f02e8e4204c067004ebc77aceaaef823eb96/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/525f14e123a954c5ac257a0ab234f02e8e4204c067004ebc77aceaaef823eb96/json
4.0K tmpstuff//imgutil.local.image.032493284/525f14e123a954c5ac257a0ab234f02e8e4204c067004ebc77aceaaef823eb96/VERSION
2.7M tmpstuff//imgutil.local.image.032493284/525f14e123a954c5ac257a0ab234f02e8e4204c067004ebc77aceaaef823eb96
12K tmpstuff//imgutil.local.image.032493284/86dee262a9b01683307b452211da1ca3754798bcaf8d6181187c45cc6be22a59/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/86dee262a9b01683307b452211da1ca3754798bcaf8d6181187c45cc6be22a59/json
4.0K tmpstuff//imgutil.local.image.032493284/86dee262a9b01683307b452211da1ca3754798bcaf8d6181187c45cc6be22a59/VERSION
20K tmpstuff//imgutil.local.image.032493284/86dee262a9b01683307b452211da1ca3754798bcaf8d6181187c45cc6be22a59
4.0K tmpstuff//imgutil.local.image.032493284/repositories
5.6M tmpstuff//imgutil.local.image.032493284/bcf486159b1fbeb1b8d1a50fd7b9d46f97f2d59e6380fd9d788c889dade56eab/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/bcf486159b1fbeb1b8d1a50fd7b9d46f97f2d59e6380fd9d788c889dade56eab/json
4.0K tmpstuff//imgutil.local.image.032493284/bcf486159b1fbeb1b8d1a50fd7b9d46f97f2d59e6380fd9d788c889dade56eab/VERSION
5.6M tmpstuff//imgutil.local.image.032493284/bcf486159b1fbeb1b8d1a50fd7b9d46f97f2d59e6380fd9d788c889dade56eab
22M tmpstuff//imgutil.local.image.032493284/8e2746c2ab899aa80cb43ef1578fbe8f49a10005745c3a4a9d835ccc1d6d09de/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/8e2746c2ab899aa80cb43ef1578fbe8f49a10005745c3a4a9d835ccc1d6d09de/json
4.0K tmpstuff//imgutil.local.image.032493284/8e2746c2ab899aa80cb43ef1578fbe8f49a10005745c3a4a9d835ccc1d6d09de/VERSION
22M tmpstuff//imgutil.local.image.032493284/8e2746c2ab899aa80cb43ef1578fbe8f49a10005745c3a4a9d835ccc1d6d09de
2.2M tmpstuff//imgutil.local.image.032493284/94f0a64e124b02b14b5b2fa39992851f2bed27f5fed69841166ac960271eb8c4/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/94f0a64e124b02b14b5b2fa39992851f2bed27f5fed69841166ac960271eb8c4/json
4.0K tmpstuff//imgutil.local.image.032493284/94f0a64e124b02b14b5b2fa39992851f2bed27f5fed69841166ac960271eb8c4/VERSION
2.2M tmpstuff//imgutil.local.image.032493284/94f0a64e124b02b14b5b2fa39992851f2bed27f5fed69841166ac960271eb8c4
4.0K tmpstuff//imgutil.local.image.032493284/manifest.json
201M tmpstuff//imgutil.local.image.032493284/6d4e3d565d9c0c3e5225325cfec30fed5c06ae3603d4b57f4786d2002745e988/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/6d4e3d565d9c0c3e5225325cfec30fed5c06ae3603d4b57f4786d2002745e988/json
4.0K tmpstuff//imgutil.local.image.032493284/6d4e3d565d9c0c3e5225325cfec30fed5c06ae3603d4b57f4786d2002745e988/VERSION
201M tmpstuff//imgutil.local.image.032493284/6d4e3d565d9c0c3e5225325cfec30fed5c06ae3603d4b57f4786d2002745e988
4.0K tmpstuff//imgutil.local.image.032493284/5c0a1c5355861df4fd73d0d0f31cf1765c4966b8fde64fa417ecc464d255d1c9/layer.tar
4.0K tmpstuff//imgutil.local.image.032493284/5c0a1c5355861df4fd73d0d0f31cf1765c4966b8fde64fa417ecc464d255d1c9/json
4.0K tmpstuff//imgutil.local.image.032493284/5c0a1c5355861df4fd73d0d0f31cf1765c4966b8fde64fa417ecc464d255d1c9/VERSION
12K tmpstuff//imgutil.local.image.032493284/5c0a1c5355861df4fd73d0d0f31cf1765c4966b8fde64fa417ecc464d255d1c9
233M tmpstuff//imgutil.local.image.032493284
233M tmpstuff/
New package layout
will be exposed with the implementation of the Image
interface based on the OCI Image Layout
Note: There is a v1.Image wrapper implementation in the lifecycle that mimics the behavior of a Image without actually having the layers on disk.
The minimal implementation requires to expose the following classes:
classDiagram
class ImageOption {
<<interface>>
FromBaseImage(base v1.Image) ImageOption
FromBaseImagePath(path string) ImageOption
WithPreviousImage(path string)
}
class LayoutImage {
NewImage(path string, ops ...ImageOption) (*Image, error)
}
class SparseImage {
NewImage(path string, from v1.Image) (*Image, error)
}
Where
Image
interface and handle the complexity of saving on Disk in OCI layout format.Image
interface that mimics the behavior onf a image without actually saving any layer on disk.A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.