-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds WithPlatform constructor option for initialization/manifest list querying values #91
Adds WithPlatform constructor option for initialization/manifest list querying values #91
Conversation
f4113f0
to
0388727
Compare
local/local_test.go
Outdated
prevLayer1SHA = inspect.RootFS.Layers[len(inspect.RootFS.Layers)-2] | ||
prevLayer2SHA = inspect.RootFS.Layers[len(inspect.RootFS.Layers)-1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this to avoid having the assertion switch on the daemon OS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and I realize it's repeated in a couple other places - I'll toss in a helper to clarify it.
The reason it needed to be changed here is that NewImage()
can no longer create Windows images with no layers at all (which technically could be saved to a daemon, but have no value that I could see).
If layerless Windows images are important, I can certainly take a shot at getting them back
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a new helper that works as follows:
from:
prevLayer1SHA = inspect.RootFS.Layers[len(inspect.RootFS.Layers)-2]
to:
// negative index (offset from len(inspect.RootFS.Layers))
prevLayer1SHA = h.StringElementAt(inspect.RootFS.Layers), -2)
// also normal index
prevLayer1SHA = h.StringElementAt(inspect.RootFS.Layers), 1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason it needed to be changed here is that NewImage() can no longer create Windows images with no layers at all (which technically could be saved to a daemon, but have no value that I could see).
This statement confuses me. I thought we were always adding a base layer on windows images because it is impossible to pull a windows image without one. Can you successfully docker load an empty image, even though you can't pull one?
@micahyoung I think the flow you proposed makes sense. I know @ekcasey will want to review this one, but this looks good to me! |
} | ||
when("#WithPlatform", func() { | ||
when("base image and platform architecture/OS do not match", func() { | ||
it("uses the base image architecture/OS, ignoring platform", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just fail in this case? Given that we can't sensibly reconcile the two options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this gets tricky because we have WithPlatform
doing double-duty for remote
(1. as a new image value setter, 2. as a tie-breaker for manifest lists). If an imgutil
consumer wants to call NewImage
in a way that ignores platform for image manifest refs, but has some tie-breaking for manifest list refs, then I think the only way to do this with a single call is to ignore the platform for image manifest results entirely (much like ggcr does). And because we're stuck with that behavior for remote
, I think we need to stay consistent in local
.
Maybe a different name for WithPlatform
would clarify this behavior though, maybe WithPreferredPlatform
or WithFallbackPlatform
or something. Or we could split out the double-duty of remote.WithPlatform
and add a platform param to remote.FromBaseImage
and remote.WithPreviousImage
and those could deal with manifest list platform disambiguation only.
I'd love to hear other thoughts though.
Thanks for the reviews! Here's my TODO list so far:
|
After the above changes, plus some manual and newly enabled acceptance lifecycle testing on Windows, I'm feeling like this is in a good state and ready for another look, if you all wouldn't mind. |
12b2541
to
ef64247
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Just a couple suggestions for cleanup.
- For Windows images only, adds minimal Windows baselayer on new empty images with no base layers (breaking change) Signed-off-by: Micah Young <[email protected]>
- Prioritize based on type instead of using struct values Signed-off-by: Micah Young <[email protected]>
Signed-off-by: Micah Young <[email protected]>
Signed-off-by: Micah Young <[email protected]>
- Don't fill in fields for remote.WithPlatform, should limit ambiguous consumer calls and allow intentionally blank fields Signed-off-by: Micah Young <[email protected]>
- Don't autofill OS field for local.WithPlatform, should limit ambiguous consumer calls and allow intentionally blank fields Signed-off-by: Micah Young <[email protected]>
Signed-off-by: Micah Young <[email protected]>
Signed-off-by: Micah Young <[email protected]>
Signed-off-by: Micah Young <[email protected]>
Signed-off-by: Micah Young <[email protected]>
- Guard options at constructor level - Modify image by reference - Return nil image for err cases Signed-off-by: Micah Young <[email protected]>
ef64247
to
c314c6c
Compare
I made changes based on the feedback, which did clarify the logic. I feel like I'll want to revisit this at some point after living with it a bit and maybe looking into some different option pattern examples - I feel there must be some tidier ways to move the logic back into the ImageOptions, but still enforce the order of execution that we need. |
The changes in this PR are to introduce a
WithPlatform()
option forNewImage()
to make it easier to override the defaultArchitecture
/OS
/OSVersion
with alternate values in new images and querying manifest lists withFromBaseImage
orWithPreviousImage
. ImgutilNewImage()
currently creates new images with a default of linux/amd64 forremote
and daemon OS/amd64 forlocal
, and when usingremote.FromBaseImage()
orremote.WithPreviousImage()
, it queries manifest lists preferring linux/amd64 as inherited fromgo-containerregistry
.The new implementation attempts to be additive and optional, preserving existing behavior (with an exception on Windows¹) but enabling some new scenarios:
Initialize a new empty image with Architecture/OS/OSVersion:
remote
local
with a windows/amd64 daemonInitialize a new remote image from a base image in multi-arch/os manifest list:
Initialize a new empty image when manifest list doesn't contain image manifest for requested platform:
There is additional behavior that is intended to stay as close as possible to existing behavior for
NewImage()
andFromBaseImage()
, but may nevertheless be surprising:A single image manifest result for
FromBaseImage()
is always used, even when it doesn't match theWithPlatform()
valuesThe reasoning for this behavior is that
WithPlatform()
should primarily be used to assign values to a new image when nothing is inherited - when either noFromBaseImage()
is given or when theFromBaseImage()
image is not found. It's secondary purpose is forremote
's special edge-case whenFromBaseImage()
needs a manifest list tie-breaker. It shouldn't be treated as a constraint or validator. This approach also followsggcr/remote.Image()
, which currently only uses it's ownggcr/remote.WithPlatform()
option when theref
is a manifest list, and disregards it for single image manifests even when the platform and image conflict.In addition to deviating from
ggcr
, the potential alternative approaches I looked into all had downsides:NewImage
is called. For consumers currently usingNewImage()
and accepting all image platforms, new errors could make backward compatibility challenging, if they only wantWithPlatform
for manifest list tie-breaking and new image defaults.With the PR approach, consumers can hopefully compare the returned image with their desired platform, and throw an appropriate error if needed. I'm sure there are other considerations though so I'm open for feedback on this.
¹ New empty Windows images are always initialized with minimal Windows base layers (Adds support for creating Windows scratch-equivalent baselayers #64)
A Windows image cannot be pulled or stored on a daemon without a valid base layer - either from a Microsoft-authored base image or one from
layer.WindowsBaseLayer()
. To simplify new image creation,NewImage()
now adds one based onlayer.WindowsBaseLayer()
, whenFromBaseImage()
is not used or returns an empty image. This will allow Linux and Windows to behave much more consistently and hopefully hides away this implementation detail for most consumers. It could also allow us to deprecate the shim base layer if it becomes unnecessary.It is a breaking change for all Windows consumers that already externally add their own base layer with
layer.WindowsBaseLayer()
andAddLayer()
since that will effectively add it twice. This should be acceptable since it's only known to be used inpack
which has it behind an experimental flag. Additionally, this new functionality should likely have been included in the original implementation but was blocked on the missingWithPlatform
support. It is included in the PR since it's the driver for thisWithPlatform
implementation (Fixes cache image with incorrect OS on windows lifecycle#532), though can be separated out if needed.