-
Notifications
You must be signed in to change notification settings - Fork 553
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
Start to build up a mutate
package.
#729
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// | ||
// Copyright 2021 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package mutate | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
"github.com/google/go-containerregistry/pkg/v1/empty" | ||
"github.com/google/go-containerregistry/pkg/v1/mutate" | ||
"github.com/google/go-containerregistry/pkg/v1/types" | ||
"github.com/sigstore/cosign/internal/oci" | ||
) | ||
|
||
// Mutator is the signature of the callback supplied to Map. | ||
// The oci.SignedEntity is either an oci.SignedImageIndex or an oci.SignedImage. | ||
// This callback is called on oci.SignedImageIndex *before* its children are | ||
// processed with a context that returns IsBeforeChildren(ctx) == true. | ||
// If the images within the SignedImageIndex change after the Before pass, then | ||
// the Mutator will be invoked again on the new SignedImageIndex with a context | ||
// that returns IsAfterChildren(ctx) == true. | ||
// If the returned entity is nil, it is filtered from the result of Map. | ||
type Mutator func(context.Context, oci.SignedEntity) (oci.SignedEntity, error) | ||
|
||
// ErrSkipChildren is a special error that may be returned from a Mutator | ||
// to skip processing of an index's child entities. | ||
var ErrSkipChildren = errors.New("skip child entities") | ||
|
||
// Map calls `fn` on the signed entity and each of its constituent entities (`SignedImageIndex` | ||
// or `SignedImage`) transitively. | ||
// Any errors returned by an `fn` are returned by `Map`. | ||
func Map(ctx context.Context, parent oci.SignedEntity, fn Mutator) (oci.SignedEntity, error) { | ||
parent, err := fn(before(ctx), parent) | ||
switch { | ||
case errors.Is(err, ErrSkipChildren): | ||
return parent, nil | ||
case err != nil: | ||
return nil, err | ||
case parent == nil: | ||
// If the function returns nil, it filters it. | ||
return nil, nil | ||
} | ||
|
||
sii, ok := parent.(oci.SignedImageIndex) | ||
if !ok { | ||
return parent, nil | ||
} | ||
im, err := sii.IndexManifest() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Track whether any of the child entities change. | ||
changed := false | ||
|
||
adds := []IndexAddendum{} | ||
for _, desc := range im.Manifests { | ||
switch desc.MediaType { | ||
case types.OCIImageIndex, types.DockerManifestList: | ||
x, err := sii.SignedImageIndex(desc.Digest) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
se, err := Map(ctx, x, fn) | ||
if err != nil { | ||
return nil, err | ||
} else if se == nil { | ||
// If the function returns nil, it filters it. | ||
changed = true | ||
continue | ||
} | ||
|
||
changed = changed || (x != se) | ||
adds = append(adds, IndexAddendum{ | ||
Add: se.(oci.SignedImageIndex), // Must be an image index. | ||
Descriptor: v1.Descriptor{ | ||
URLs: desc.URLs, | ||
MediaType: desc.MediaType, | ||
Annotations: desc.Annotations, | ||
Platform: desc.Platform, | ||
}, | ||
}) | ||
|
||
case types.OCIManifestSchema1, types.DockerManifestSchema2: | ||
x, err := sii.SignedImage(desc.Digest) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
se, err := fn(ctx, x) | ||
if err != nil { | ||
return nil, err | ||
} else if se == nil { | ||
// If the function returns nil, it filters it. | ||
changed = true | ||
continue | ||
} | ||
|
||
changed = changed || (x != se) | ||
adds = append(adds, IndexAddendum{ | ||
Add: se.(oci.SignedImage), // Must be an image | ||
Descriptor: v1.Descriptor{ | ||
URLs: desc.URLs, | ||
MediaType: desc.MediaType, | ||
Annotations: desc.Annotations, | ||
Platform: desc.Platform, | ||
}, | ||
}) | ||
|
||
default: | ||
return nil, fmt.Errorf("unknown mime type: %v", desc.MediaType) | ||
} | ||
} | ||
|
||
if !changed { | ||
return parent, nil | ||
} | ||
|
||
// Preserve the key attributes from the base IndexManifest. | ||
e := mutate.IndexMediaType(empty.Index, im.MediaType) | ||
e = mutate.Annotations(e, im.Annotations).(v1.ImageIndex) | ||
|
||
// Construct a new ImageIndex from the new consituent signed images. | ||
result := AppendManifests(e, adds...) | ||
|
||
// Since the children changed, give the callback a crack at the new image index. | ||
return fn(after(ctx), result) | ||
} | ||
|
||
// This is used to associate which pass of the Map a particular | ||
// callback is being invoked for. | ||
type mapPassKey struct{} | ||
|
||
// before decorates the context such that IsBeforeChildren(ctx) is true. | ||
func before(ctx context.Context) context.Context { | ||
return context.WithValue(ctx, mapPassKey{}, "before") | ||
} | ||
|
||
// after decorates the context such that IsAfterChildren(ctx) is true. | ||
func after(ctx context.Context) context.Context { | ||
return context.WithValue(ctx, mapPassKey{}, "after") | ||
} | ||
|
||
// IsBeforeChildren is true within a Mutator when it is called before the children | ||
// have been processed. | ||
func IsBeforeChildren(ctx context.Context) bool { | ||
return ctx.Value(mapPassKey{}) == "before" | ||
} | ||
|
||
// IsAfterChildren is true within a Mutator when it is called after the children | ||
// have been processed; however, this call is only made if the set of children | ||
// changes since the Before call. | ||
func IsAfterChildren(ctx context.Context) bool { | ||
return ctx.Value(mapPassKey{}) == "after" | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
turn "before" and "after" into consts and/or enums?
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 was going to say "but I'll just call them before/after", but I can't because that's what the methods are called. 🙃
Given the proximity of the functions, and the tests to make sure they agree, I'm sort of inclined to leave it 😅