Skip to content
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

index.json: improve implementors note to better represent algorithms necessary #588

Closed
cyphar opened this issue Mar 1, 2017 · 42 comments
Closed
Assignees
Milestone

Comments

@cyphar
Copy link
Member

cyphar commented Mar 1, 2017

As discussed in the OCI call, currently the implementors note for index.json doesn't fully encapsulate the reason for the more complex Index data structure and the ways in which implementations should handle the n-tall tree that it can represent.

The main points that need to be clarified are:

  • That the UX for selecting what manifest is being operated on is not defined by the spec, and that implementations are free to either fail when they can't uniquely resolve a manifest, or provide some sort of indication to the user that

  • The platform matching / annotation matching algorithm necessary to match and flatten the tree structure of descriptors and then reduce the set of descriptors to those that match a particular query. The SQL analogy would probably be something we should mention.

@stevvooe Stepped up to work on this one.

@stevvooe
Copy link
Contributor

stevvooe commented Mar 8, 2017

Based on my research over the last week, I think we can address this by pulling up the platform descriptions into the descriptor to get rid of the type hierarchy of descriptors which is awkward. This is a Go type change and a slight reorganization of the types definition which is completely compatible, but it would be nice to have that correct for 1.0.

Let me know what you think about this or if more details are needed.

@stevvooe stevvooe modified the milestones: v1.0.0-rc6, v1.0.0 Mar 8, 2017
@cyphar
Copy link
Member Author

cyphar commented Mar 8, 2017

I agree with moving platform to descriptor annotations (now that we have them). Though we still would need to add some implementor notes about how they are expected to handle the hierarchy of annotations that you could run into and that the recommended referencing implementation would involve effectively tag matching (more of a search engine and less of a simple dereferencer).

I've been working on this on the umoci side and I'll admit it's quite difficult coming up with an interface that is symmetric when it comes to referencing "from" and "to" an image (you are effectively binding the interface quite tightly with the concept of annotations which is a spec detail that I'm not sure users should care about). But we'll see how it goes.

@stevvooe
Copy link
Contributor

stevvooe commented Mar 8, 2017

I agree with moving platform to descriptor annotations (now that we have them).

I think we'd have to support both platform field and annotations, in practice, but the simplification seems straightforward.

I've been working on this on the umoci side and I'll admit it's quite difficult coming up with an interface that is symmetric when it comes to referencing "from" and "to" an image (you are effectively binding the interface quite tightly with the concept of annotations which is a spec detail that I'm not sure users should care about). But we'll see how it goes.

I'll see if I can look deeper into platform qualified pull to give some better guidance here, as promised.

@qianzhangxa
Copy link
Contributor

@stevvooe Did you mean adding Platform into Descriptor as its field and then we do not need ManifestDescriptor anymore?

And could you please provide more details about why it can address this issue (e.g., how to handle duplicated org.opencontainers.ref.name in index.json)?

@xiekeyang
Copy link
Contributor

@cyphar

more complex Index data structure

Could you please put up some possible examples of complex index data struct? Actually I'm interest the experimental usage of index.

That the UX for selecting what manifest is being operated on is not defined by the spec, and that implementations are free to either fail when they can't uniquely resolve a manifest, or provide some sort of indication to the user that

If it means the contents(config+layers) in one manifest might be not operable? Instead, manifests in one index can be combined and run a container according to customized order?

@stevvooe
Copy link
Contributor

stevvooe commented Mar 9, 2017

@stevvooe Did you mean adding Platform into Descriptor as its field and then we do not need ManifestDescriptor anymore?

Yes.

And could you please provide more details about why it can address this issue (e.g., how to handle duplicated org.opencontainers.ref.name in index.json)?

This can help with the issue by avoid having to deal with different descriptor types, which was never intended by this specification.

As far as an example of a ux that deals with multiple reference, let's say we have a tool that lists the references in a image layout:

$ oci image references foo.tar.gz
REF    PLATFORM     TYPE                                                 DIGEST      
foo    arm64/linux  application/vnd.oci.image.manifest.v1+json           sha256:ffff
foo    amd64/linux  application/vnd.oci.image.manifest.v1+json           sha256:aa

Then we can unpack with something like this:

$ oci image unpack target/ foo foo.tar.gz
error: reference foo is ambiguous

Now, just qualify the reference:

$ oci image unpack --platform.arch arm64  target/ foo foo.tar.gz

Or:

$ oci image unpack target/ foo@sha256:ffff foo.tar.gz

@qianzhangxa
Copy link
Contributor

Thanks @stevvooe for the example!

And I am thinking another case:

$ oci image references foo.tar.gz
REF    PLATFORM     TYPE                                                 DIGEST      
foo    N/A          application/vnd.oci.image.index.v1+json              sha256:ffff
foo    amd64/linux  application/vnd.oci.image.manifest.v1+json           sha256:aa

In this case, what will be the expected behavior of oci image unpack target/ foo foo.tar.gz? Should it fail with error: reference foo is ambiguous too? Or it should go with the first foo whose media type is application/vnd.oci.image.index.v1+json.

@stevvooe
Copy link
Contributor

In this case, what will be the expected behavior of oci image unpack target/ foo foo.tar.gz? Should it fail with error: reference foo is ambiguous too? Or it should go with the first foo whose media type is application/vnd.oci.image.index.v1+json.

I think you would need to unroll the references in the targeted index. Perhaps, we could print that as a tree?

@qianzhangxa
Copy link
Contributor

Do we really need to allow multiple descriptors with the name but with different media types in an image layout? I think it is definitely OK if there are multiple manifest descriptors with the same name but for different platforms. But if there is already an index descriptor which is for multiple platforms, why should there be a manifest descriptor with the same name for a specific platform? I think the later should be part of the former rather than part of index.json.

@stevvooe
Copy link
Contributor

@qianzhangxa @cyphar Here is my visitor implementation for containerd: https://github.com/docker/containerd/pull/638. It let's you arbitrarily walk image resources and process them in the vein discussed above. Let me know if you have questions.

@qianzhangxa
Copy link
Contributor

Thanks @stevvooe I took a quick look at that PR, but it seems that its implementation is still based on the old image layout (./ref) rather than the latest one (index.json)? I do not see the code for fetching and processing index.json. Please correct me if I was wrong.

@stevvooe
Copy link
Contributor

Thanks @stevvooe I took a quick look at that PR, but it seems that its implementation is still based on the old image layout (./ref) rather than the latest one (index.json)? I do not see the code for fetching and processing index.json. Please correct me if I was wrong.

In fact, it is not based either. As I've said multiple times, I think the focus on the archive format has greatly confused things. "Fetching" an index.json file makes absolutely no sense.

That PR shows the simple method of walking a tree with a visitor pattern to process image related resources. It also provides the ability to filter at pull, push or assembly. This can be generalized to deal with the exact problems described here. Adding support for new types is straightforward.

@cyphar
Copy link
Member Author

cyphar commented Mar 21, 2017

Is metadata meant to apply to all children of a particular descriptor? If so, how are conflicts handled (the lowest statement wins?), and does that mean that the only way to implement a reference resolver is to walk everything that a descriptor points to and then collate those objects and their metadata?

Surely there needs to be some clarification on this point. I mean, I'm not even sure how you would expose the concept of a hierarchy of metadata to users, but I'm trying to figure it out.

@wking
Copy link
Contributor

wking commented Mar 21, 2017 via email

@stevvooe
Copy link
Contributor

Surely there needs to be some clarification on this point. I mean, I'm not even sure how you would expose the concept of a hierarchy of metadata to users, but I'm trying to figure it out.

There is an equivalence between trees and a linearization of a tree based on the traversal order. Exploit this and you can do all these things.

If so, how are conflicts handled (the lowest statement wins?), and does that mean that the only way to implement a reference resolver is to walk everything that a descriptor points to and then collate those objects and their metadata?

As I've said, you can do whatever you want but having the lowest statement win would be lossy. I would just collect these references under a key and display them together. You can maintain a stack to recover the reference path or just have a list for each reference that you add descriptors to (map[string][]Descriptor).

I'll add some more examples to containerd and consider contributing them back here, but I ask that you at least try out some algorithms before dismissing the approach.

@cyphar
Copy link
Member Author

cyphar commented Mar 22, 2017

@stevvooe

There is an equivalence between trees and a linearization of a tree based on the traversal order.

It's a lossy transformation. Given the following in-order traversal, which of the following trees did it come from?

A B C D E F G H I
        F
    +---+---+
    D       G
  +-+-+    +++
  B   E    H I
 +++
 A C
    E
  +-+-+
  C   I
 +++  |
 B D  G
 |   +++
 A   F H

I could make more but you get the idea. The same problem exists for preorder and postorder traversals.

IIRC from my discrete maths classes, there is not a bijection between a traversal and the arbitrary tree that can produce it. While it is true that in binary trees there is a bijection if you have both inorder and postorder traversals, the OCI images aren't binary trees.

Not to mention that now users have to describe an arbitrary tree whenever they want to create a reference. I mean, in some aspects it's better than the original system but I don't see any practical way of doing this without some graphical assistance.

@cyphar
Copy link
Member Author

cyphar commented Mar 22, 2017

I'll add some more examples to containerd and consider contributing them back here, but I ask that you at least try out some algorithms before dismissing the approach.

I'm not dismissing the approach, I'm asking for clarification. As it stands, it's ambiguous what should be done. I do know how to implement a traversal of a tree, what I'm not sure of is whether it's a good idea for everyone to expose a completely different UX and concepts when handling references. I mean, you've said that's not an issue in the past and I begrudgingly agreed, but the whole point of this issue was to provide guidance about the UX.

Currently (or at least a few weeks ago) I'm discussing this issue with @mtrmac (one of the maintainers of skopeo) and I'm unsure if umoci and skopeo will be able to implement a similar UX (because umoci would ideally implement the most generic possible reference handling so that users can interact with the maximum amount of valid images). You said this was a non-issue before but I'm not so convinced anymore.

Thank god we don't need to resolve this in image-tools as well.

@mtrmac
Copy link
Contributor

mtrmac commented Mar 22, 2017

(I haven’t been following any of the previous conversations, so I apologize if this rehashes long-settled discussions.)

Yeah, from my POV the index format prioritizes the wrong thing: it wants to be abstract enough to be able to represent any possible situation, but it gives absolutely no guidance how to consume the content.

If two different implementations running on the same computer, given the same input, can choose two different images from the same index as “best”, the spec is not interoperable. It should be practical and easy to write clients which follow the spec, are interoperable, and behave consistently.

It seems to me it is much better to start with a narrow but clearly working and interoperable spec (which has a clear promise, and is obviously possible to extend/improve) rather than a wide and unimplementable spec (which fails in its very purpose from the start, so is likely to be abandoned or replaced).

AFAICS the index specification at https://github.com/opencontainers/image-spec/blob/master/image-index.md utterly fails in providing any “how to consume” guidance; though perhaps I am just missing the existence of another document which describes this. But then this ticket seems to advocate for explicitly documenting that there is no guidance; I suppose that’s better than saying nothing, because it saves readers time, but just giving up does not give us an interoperable standard.


A clear example is the platform.{os.version,os.features,variant,features} members, which are completely unspecified, i.e. completely non-interoperable from the start. I also can see no definition of which matches are “better” that others (e.g. if one index entry provides a better-matching os.version and one provides a better-matching os.features), again different implementations will not be consistent in this unless the spec helps them.

The references to a human UI which lists all of the index contents and expect the user to choose are, for me, not at all helpful with trying to decide how to consume such an index by software.

Neither is a reference to SQL (or, with nesting, Prolog or something?); if the field values are not standardized, the SQL can’t be autogenerated/hard-coded in an application, the user has to specify it; but the user is similarly hampered by lack of specification of the values, so the user has to specify the selector also manually by individually inspecting a specific manifest or by blindly assuming that the index has been created using a specific implementation. The user is asked to predict future contents of indexes which have not yet been generated, without any guidance.

I suppose in practice this will play out the way overly-complex interoperability specs usually do: by most implementation settling on implementing a small subset, perhaps a formally specified and interoperable “profile”, perhaps each implementation making its own incompatible choice.


Right now my preference, as someone very ignorant about the wider ecosystem concerns and past practice, would be to standardize only the architecture and os fields, which have existing prior art, are clearly defined, and provide ways to specify clearly non-overlapping platforms, and to define indexes with duplicate combinations of (architecture, os) to be invalid.

Future versions of the index (with a new mediaType or schemaVersion) can add more fields as needed, if and when OCI is ready to specify their values and the treatment of the values in detail; after that happens future consumers of those future versions of indexes can make decisions based on more fields, and old consumers will see an unknown version number and refuse to consume an index which they don’t understand.

@qianzhangxa
Copy link
Contributor

qianzhangxa commented Mar 28, 2017

@stevvooe @cyphar Any updates for this issue?

I think for this issue, we have 4 cases need clarification:

  1. In index.json, there are more than one index (application/vnd.oci.image.index.v1+json) which have the same ref name (org.opencontainers.image.ref.name).
  2. In index.json, there are more than one manifest (application/vnd.oci.image.manifest.v1+json) which have the same ref name and the same platform (which is the platform of the current machine).
  3. In index.json, there are one index and one manifest which have the same ref name, and inside the index, there is a manifest which has the same platform (which is the platform of the current machine) with the manifest in the index.json.
  4. In an index, there are more than one manifest which have the same platform (which is the platform of the current machine).

My idea is, for all the above 4 cases, the implementation should report an error since it can not find a unique manifest for the ref. And since platform is an optional property in the descriptor, if there is any manifest in the index.json or in the index which has no platform property, implementation should just ignore the manifest.

Comments?

@stevvooe
Copy link
Contributor

@mtrmac If you think there is an area that is under-specified, please make an attempt to specify those fields. As it is, the referenced fields are mostly owned by the given platform. We can create a registry of those platform fields and their meanings, if that helps, but it is a big effort. Either way, I think that has little to do with this issue.

IIRC from my discrete maths classes, there is not a bijection between a traversal and the arbitrary tree that can produce it. While it is true that in binary trees there is a bijection if you have both inorder and postorder traversals, the OCI images aren't binary trees.

@cyphar The classic rules of a lack of bijection for binary trees and their linearizations don't apply here. The actual nodes also contain the links, so if you linearize, you can still recover the original tree. In fact, given any node of an OCI tree, the subtree can be reconstructed because it is itself the vertex and the edges.

Currently (or at least a few weeks ago) I'm discussing this issue with @mtrmac (one of the maintainers of skopeo) and I'm unsure if umoci and skopeo will be able to implement a similar UX (because umoci would ideally implement the most generic possible reference handling so that users can interact with the maximum amount of valid images). You said this was a non-issue before but I'm not so convinced anymore.

I'm not sure what to say here other than UX decisions were made that may need to be reconsidered. If they want to take an opinionated view of images, that is perfectly acceptable, but I don't think that should be projected on this specification.


@qianzhangxa Thank you for providing concrete questions! This will allow us to keep the discussion technical and moving forward.

In index.json, there are more than one index (application/vnd.oci.image.index.v1+json) which have the same ref name (org.opencontainers.image.ref.name).

Why couldn't all of these descriptors be linearized under that same name? The children have both type and order. The result is equivalent to a single index with a unique name.

In index.json, there are more than one manifest (application/vnd.oci.image.manifest.v1+json) which have the same ref name and the same platform (which is the platform of the current machine).

This is up to the implementation. I would say err on taking the first match, but I am not sure we should specify this.

In index.json, there are one index and one manifest which have the same ref name, and inside the index, there is a manifest which has the same platform (which is the platform of the current machine) with the manifest in the index.json.

Same as case one. All of these index under the same name when trying to build the ref.

In an index, there are more than one manifest which have the same platform (which is the platform of the current machine).

You can err on taking the first entry.

My idea is, for all the above 4 cases, the implementation should report an error since it can not find a unique manifest for the ref. And since platform is an optional property in the descriptor, if there is any manifest in the index.json or in the index which has no platform property, implementation should just ignore the manifest.

I think this is vastly outside the scope of the specification. We define the data structures and their meaning. If something is possible in the specification, but not useful, it should be up to the implementer to decide that the case doesn't work for them.

To tell you the truth, none of these cases fall outside the original presented dichotomy of erring out versus presenting all the options. If you don't want your images to be ambiguous, don't create ambiguous images for your tools and it is perfectly acceptable for your tools to reject them.

@mtrmac
Copy link
Contributor

mtrmac commented Mar 30, 2017

If you don't want your images to be ambiguous, don't create ambiguous images for your tools and it is perfectly acceptable for your tools to reject them.

This verges towards “you can produce anything and call itself a compliant OCI producer, and you can refuse to consume anything and call yourself a compliant OCi consumer”, and the poor users are left with… pretty much no interoperability promise, i.e. no standard to speak of. (Or have these principles been already decided in the past, so that OCI is satisfied with documenting “data structures” and not ensuring interoperabity? If so, I apologize.)

If the interoperable subset of values is only the non-ambiguous images, it would be far better for the spec to actually define this subset instead of the vague undefined universe of names and values. Then producers would know what to target, and consumers would know what they are required to consume, and both would be (more) likely to be interoperable.

(Specifing the interoperable well-defined subset of doesn’t prevent any implementation from defining non-standard extensions, only they would be clearly non-standard instead of using “OCI-compliant data structures with not-prohibited values and undefined semantics”.)


In index.json, there are more than one manifest (application/vnd.oci.image.manifest.v1+json) which have the same ref name and the same platform (which is the platform of the current machine).

This is up to the implementation. I would say err on taking the first match, but I am not sure we should specify this.

How does answering “this is up to the implementation” benefit interoperability? Or if interoperability between independent OCI implementations is not the primary goal, what is the primary goal of this spec?

If the behavior is unspecified, producers can’t produce interoperable indexes with such duplicate name/platform pairs. So they shouldn’t do that.

If the behavior is specified to be “use the first match”, producing indexes with duplicates is clearly useless. So the producers shouldn’t do that either.

Really I can see no situation, except for a producer which is knowingly producing an index to be consumed by a specific consumer, relying on a data format defined beyond the OCI spec (which is of course possible, but in that case OCI “allowing” such data, or not, is by definition irrelevant), when a producer would ever want to create such duplicates legitimately.

If the duplicates are not legitimate, consumers should always reject them; hence the spec should describe duplicates as “forbidden in this version, may have defined semantics in the future” so that producers don’t create them.

@stevvooe
Copy link
Contributor

@mtrmac I can see my response was lost on you. When I said it would be nice if you could avoid histrionics, I meant that you should avoid statements like there is "pretty much no interoperability promise, i.e. no standard to speak of". Come on. This isn't even close to being true and you know it. I am wasting no more time on you.

Seeing as this conversation is going absolutely no where, I am closing this issue. If you would like to have me consult for you on proper implementation, you can join the OCI call.

@cyphar
Copy link
Member Author

cyphar commented Mar 31, 2017

@stevvooe We still haven't added implementor's notes, which is what this issue was about in the first place.

FWIW, I do echo @mtrmac's concern about the abundance of statements that basically amount to "it's up to the implementation if they support / how they interpret " -- mainly because I'd like to make umoci as un-opinionated as possible and this discussion is really forcing me to become opinionated about how references work. The spec is too un-opinionated that it is going to force implementations to become opinionated (or as @mtrmac said, end up implementing a subset of the spec). And we still haven't decided whether annotations are meant to apply to children (which is vital to agree on if you want to have implementations that agree what a reference might mean).

From my side, I'm just going to make the current UX of umoci only allow accessing unique reference names in index.json. If someone actually wants the feature of being able to construct ambiguous images, we'll cross that bridge when we come to it (I'd like to fully support the spec but having guaranteed interoperability with other tools is more important to me). I advise other implementations to do the same thing.

@cyphar The classic rules of a lack of bijection for binary trees and their linearizations don't apply here. The actual nodes also contain the links, so if you linearize, you can still recover the original tree. In fact, given any node of an OCI tree, the subtree can be reconstructed because it is itself the vertex and the edges.

Okay, but that assumes that all of the ancestors have already been created. What if they haven't? I mean, ultimately this UX is going to have to look like "here's a tree I want you to construct from this root" -- which isn't intuitive to users who aren't familiar with the spec / don't care about the spec.

As an aside, is there any other @opencontainers/image-spec-maintainers who wants to speak up on this topic?

@stevvooe
Copy link
Contributor

stevvooe commented Mar 31, 2017

@cyphar There two choices here:

  1. Allow the user to resolve ambiguity.
  2. Error out on ambiguity.

To run all compliant images, you probably have to go with option 1 but this is a rare case, so option 2 is sufficient in practice. We can make implementer's notes that describe this trade off.

@wking
Copy link
Contributor

wking commented Mar 31, 2017 via email

@jonboulle
Copy link
Contributor

@stevvooe Please have more empathy for our community. If issues are being brought up repeatedly by different parties -- including a number of contributors actively working on implementations and attempting to use this specification -- it is more likely because the issues have not been adequately explained or resolved by us. Not because said contributors are stupid or seeking attention.

This GitHub issue was opened in the first place at your suggestion as a follow up to #581 - wherein once again multiple independent contributors raised similar concerns which we have not yet satisfactorily addressed.

@cyphar commented:

As an aside, is there any other @opencontainers/image-spec-maintainers who wants to speak up on this topic?

Broadly speaking, I share your and @mtrmac's concerns.

@duglin
Copy link
Collaborator

duglin commented Apr 7, 2017

I'm trying to follow this discussion, does this capture the basic idea/concerns:

  • index.json can represent single images or multiple images - typically for cases where the images are alternative representations of the same thing - e.g. multi-arch images
  • at any level in the tree of images being described we can have a set of properties that are used to identify, or describe, the image at that level (well defined properties and user extensible ones)
  • each implementation of tooling that opens the index.json might have their own UX allowing people specify a sort-of query expression telling the tool which of the (possibly many) images in there the user is interested in
  • the issues being discussed are:
  1. should we limit the fields that can be used for the query?
  2. what should we do if the result of the query is ambiguous?
  3. what if there is conflicting metadata between the layers?

I feel like there might be more, but hopefully I'm not too far off. If I'm totally wrong then ignore the rest of this ramble :-)

My view:

  1. limiting the fields for queries
  • this feels limiting :-) One of the points of having extensibility/annotations is to allow for some degree of fine tuning.
  • So, if one implementation wants to allow people to take an annotation called "foo" into account then it can, but other might not.
  • If one implementation wants to ignore the platform info because it thinks its only going to get x86 images, then its free to do it. Whether it works or not is its problem.
  • So, from a spec perspective it might be best to remain silent on which fields can be specified in the queries.
  1. what should we do it the result of the query is ambiguous?
  • regardless of which fields are used in the UX/query (if any), if the tool is not able to uniquely identify which image to use then it SHOULD probably stop processing.
  • Note: SHOULD not MUST because I'm not sure its correct to mandate that in all cases it would be wrong for a tool to warn its users that in the event of ambiguity it'll randomly choose one. I personally would not like this, but that's why we have choices in which tooling we use. But, in the end there may be some environments where its ok to just do something like "log the situation and keep going because our processing can not stop".
  • Note: "stop processing" could mean error out, or it could mean prompt with a list to choose from, or something else.
  • To me, the key thing is that the spec can probably only go so far as to RECOMMEND what should happen I don't think we can mandate much here w/o limiting implementation's freedom to differentiate themselves.
  1. what if there is conflicting metadata between the layers?
  • if the conflicting properties are not part of the query then I'm not sure it should have any impact on anything
  • if the conflicting properties ARE part of the query then I'm leaning towards treating this as an error.
  • one of the aspects of this that I'm not clear on is whether any particular property is meant to be just for that layer or all of its children. Its easy to look at something like "platform.arch" and say that should apply to all children too (I think), but something like "timestamp" wouldn't. Yet, with annotations, aside from the ones we define, we don't really know what the intent is. So do we treat all metadata the same or do some have special semantics/meaning w.r.t. their scope?
  • I need to think more about this, but for now I'm leaning towards saying that our well defined properties (not annotations), e.g. platform, apply to children and any conflict is an error. Annotations are just for the level on which they are specified, or put another way, children override their parents.

So, given the above (and ignoring number 3 for a sec), what about something like:

When processing an image (index.json) that represents multiple nested images, implementations
MAY use a variety of mechanisms to identify the specific nested image to be used.
For example, some might have implementation specific search criteria while others might
allow their users to specify some search criteria to be used. In the end an implementation MUST
narrow the list down to one before it can continue its processing. An
implementation MAY choose to use whatever imaging matching algorithm it deems necessary
to make this choice. Likewise, an implementation MAY choose to
simple generate an error and stop processing in the face of an ambiguous choice.

As for 3, I don't have spec-ish text yet but I'm thinking of something like:

  • non-annotation search properties (e.g. platform, not size) apply to the current level and children
  • conflicts are errors
  • annotation properties apply to just the level their specified, unless otherwise noted and supported by an annotation-aware impl - yuck, this needs work

Anyway, hopefully my reading of this issue isn't too far off.

@wking
Copy link
Contributor

wking commented Apr 7, 2017 via email

@duglin
Copy link
Collaborator

duglin commented Apr 7, 2017

I'm not sure we can hand-hold people enough to prevent them from doing silly things. If someone wants to define two images under one index.json such that all of the properties are the exact same then they get what they asked for - ambiguity. And people will stop using that image.

This, IMO, is about interop not preventing silliness. If we have cases where we can look at an index.json and two different impls claim that w/o any ambiguity resolver they come up with different results then I agree the spec might need more, but I'm not sure that's the case. Do we have examples of that?

@wking
Copy link
Contributor

wking commented Apr 7, 2017 via email

@duglin
Copy link
Collaborator

duglin commented Apr 7, 2017

So, let me ask this... is the real issue here around the relationship (e.g. inheritance) of metadata as you walk the tree?

If its just about the last case you mentioned (the A and B example), then I don't think there's much we can do. In the case where both set of features are supported then yes its ambiguous and the platform will decide how to deal with it - error, toss a coin, etc... I'll admit that part of me would love to mandate that we error out on all ambiguous cases but that doesn't seem fair to implementations/users that are comfortable with picking either one. After all, if they're all part of the same grouping then the author is saying (to me) they're the same just with some variant. And if that variant wasn't important enough to include as part of the query then it shouldn't matter which one is picked.

@wking
Copy link
Contributor

wking commented Apr 7, 2017 via email

@cyphar
Copy link
Member Author

cyphar commented Apr 7, 2017

@duglin I'm going to throw my hat at this one again. God help me.

I'm trying to follow this discussion, does this capture the basic idea/concerns:

Those three concerns are things that we've discussed, and I will discuss them in a second. But first, I would argue that with #634 we should revisit some of the arguments we originally had about how flexible the spec is (all of this discussion comes from not wanting to make refs.name unique, which results in the first dereference from index.json to require all of this additional semantics with SQL and so on). However, since refs.name will only apply to the top-level index.json as far as I can tell there is no restriction of the flexibility of the format -- it just adds an extra dereference step while allowing you to have guarantees about refs.name that makes the dereferencing of refs.name no longer have ambiguity (the lower levels might have some ambiguity but those were always going to be ambiguous at some level).

tl;dr: Can someone show me an example (with #634 applied) of an image where making refs.name unique will make it no longer possible to represent the same structure?

However, if we assume we keep the current state of affairs with regards to refs.name:

should we limit the fields that can be used for the query?

I'm not sure I can put my finger on it, but I don't like that refs.name is being conflated with things like platform and os, because refs.name is in the "reference layer" while platform and os are properties of the blobs themselves. It feels wrong (to me at least) to mix these two "spaces" of annotations. I mean, I think the (unspecified) bubbling up and down of annotations and their meaning is a bit concerning because it leads to arguments about how two different "levels" of tagging should be combined into reference handling.

Yes, I get that they're all "just annotations" but annotations do have different semantic meanings (and especially since some of the annotations are "sourced" [in an unspecified way] from the lower-level configuration).

what should we do if the result of the query is ambiguous?

I think that it's fine if this will just cause errors and require the user to either resolve the ambiguity or add more specificity. This doesn't seem particularly controversial, my main concern is that the only instance of ambiguity I understand the argument for is when the platform-specific variants and features are being compared. refs.name shouldn't (in my opinion) be ambiguous because it will only apply to the top-level and there's no loss of generality.

what if there is conflicting metadata between the layers?

Well, currently the entire meaning of conflicting keys and what the metadata means and recommendations how it should be used are all quite unspecified at the moment. To be honest, I'm not sure what I would even recommend here. We'd have to discuss it more.

each implementation of tooling that opens the index.json might have their own UX allowing people specify a sort-of query expression telling the tool which of the (possibly many) images in there the user is interested in

My main concern with UX is that by not specifying a way to handle references in any meaningful way, then you're going to end up with different opinions on how the UX should look that make the user experience much worse than it needs to be. I'm sure skopeo, umoci and possibly docker will come to a "gentleman's agreement" but it's not clear to me that this won't result in incompatibility down the line.

@stevvooe
Copy link
Contributor

stevvooe commented Apr 7, 2017

@duglin Thanks for your analysis on this issue!

I've also filed #634, which greatly simplifies the problem.

If we have cases where we can look at an index.json and two different impls claim that w/o any ambiguity resolver they come up with different results then I agree the spec might need more, but I'm not sure that's the case. Do we have examples of that?

We need to be very careful about the direction of what an implementation must accept or reject, as we risk rendering otherwise operable implementations non-compliant if we do this haphazardly.

is the real issue here around the relationship (e.g. inheritance) of metadata as you walk the tree?

This was a concern I brought up multiple times during the introductions of extra annotations on descriptors and manifests. However, I think the meaning of the annotation is really incumbent on the annotation itself. Many annotations apply to the target, rather than the object itself, but things like Config.Labels apply directly to the object. Since we are working with what are effectively tagged pointers, this seems to work well. Generically, we are lifting facts from the leaves and bringing to the branches to make for more efficient processing.

Either way, after some thought, we need to focus on general meaning of the data structures and that will reconcile the issues with developing working algorithms. Since we are working with such a de-normalization, assigning a particular component as the source of truth will help to eliminate ambiguity. #634 does this for the ref.name annotation. If we need this in other places, such as the platform, that may help, as well.

@erikh
Copy link
Contributor

erikh commented Apr 7, 2017 via email

@stevvooe
Copy link
Contributor

stevvooe commented Apr 7, 2017

@erikh The main problem here is that naming is hard. Even worse, embedded naming was tried in docker schema1 and it was not a good design. It presents a number of problems, in practice, including authority delegation. The addition of ref.name is a narrow annotation use to allow an image layout to reproduce the behavior of the docker save/load format. Most of the complaints of ambiguity here are really about a wide interpretation of the annotation, which doesn't seem valid in practice.

Note that this annotation is not name, it is ref.name. It has nothing to do with the image name. It is effectively the analog of the tag. If you interpret it in this narrow context, rather than trying to make it do 1000 things, it works well, for purpose. When you try to expand its scope, as happens when you expand scope in many cases, the meaning and behavior breaks down and that is what we are seeing here.

@erikh
Copy link
Contributor

erikh commented Apr 8, 2017 via email

@cyphar
Copy link
Member Author

cyphar commented Apr 9, 2017

@stevvooe

It is effectively the analog of the tag.

I completely agree with you on this point 😉. All of my concerns come from a place of "why do we need to make tag resolution ambiguous in certain cases"? Since I'm fairly sure that there would be no loss of generality if we made refs.name unique in the top layer (adding an extra indirection after the unique refs.name). If you're going to be at DockerCon next week I'd love to talk to you in-person about this.

@vbatts
Copy link
Member

vbatts commented May 12, 2017

@stevvooe to comment from dockercon

@stevvooe
Copy link
Contributor

At this time, the original complaints of this issue has mostly been addressed.

  1. There are several extant and recently merged PRs that are addressing the lack of clarity around platform matching. Of note are the clarifications for the ARM platform, pertaining to the "variant" field and the removal of the "features" field.

  2. image-layout.md, annotations.md: restrict scope of ref.name #634 deals with the transitive issues of reference naming, which created ambiguous naming issues.

  3. We still allow multiple of tags with the same name pointing at different digests in image index. Let's say we have the following:

    $ oci-image-layout list image-layout.tar.gz
    REF        SIZE   DIGEST
    foo        1000   sha256:abcdef...
    foo        1001   sha256:012356...
    

    These can be disambiguated using the digest:

    $ oci-image-tool unpack image-layout.tar.gz foo@sha256:abcdef...
    

If there are still items to be addressed, please open specific issues pertaining to that area.

@qianzhangxa
Copy link
Contributor

These can be disambiguated using the digest:
$ oci-image-tool unpack image-layout.tar.gz foo@sha256:abcdef...

So what if the digest is not specified? Should the action fail and report an error to complain there are multiple refs with the same name?

@cyphar
Copy link
Member Author

cyphar commented Jul 12, 2017

@stevvooe We still don't have any normative language for how the reference walk should work as I mentioned in the original issue #581. Do we have any plans to include that language, as we agreed in the call that spawned this issue to be opened?

In my mind this issue is still not resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants