-
Notifications
You must be signed in to change notification settings - Fork 54
Add digests.Options and set one up inside c/image/copy
#530
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
Open
mtrmac
wants to merge
2
commits into
containers:main
Choose a base branch
from
mtrmac:digests-value
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+363
−0
Open
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 hidden or 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
This file contains hidden or 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,146 @@ | ||
| // Package digests provides an internal representation of users’ digest use preferences. | ||
| // | ||
| // Something like this _might_ be eventually made available as a public API: | ||
| // before doing so, carefully think whether the API should be modified before we commit to it. | ||
|
|
||
| package digests | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/opencontainers/go-digest" | ||
| ) | ||
|
|
||
| // Options records users’ preferences for used digest algorithm usage. | ||
| // It is a value type and can be copied using ordinary assignment. | ||
| // | ||
| // It can only be created using one of the provided constructors. | ||
| type Options struct { | ||
| initialized bool // To prevent uses that don’t call a public constructor; this is necessary to enforce the .Available() promise. | ||
|
|
||
| // If any of the fields below is set, it is guaranteed to be .Available(). | ||
|
|
||
| mustUse digest.Algorithm // If not "", written digests must use this algorithm. | ||
| prefer digest.Algorithm // If not "", use this algorithm whenever possible. | ||
| defaultAlgo digest.Algorithm // If not "", use this algorithm if there is no reason to use anything else. | ||
| } | ||
|
|
||
| // CanonicalDefault is Options which default to using digest.Canonical if there is no reason to use a different algorithm | ||
| // (e.g. when there is no pre-existing digest). | ||
| // | ||
| // The configuration can be customized using .WithPreferred() or .WithDefault(). | ||
| func CanonicalDefault() Options { | ||
| // This does not set .defaultAlgo so that .WithDefault() can be called (once). | ||
| return Options{ | ||
| initialized: true, | ||
| } | ||
| } | ||
|
|
||
| // MustUse constructs Options which always use algo. | ||
| func MustUse(algo digest.Algorithm) (Options, error) { | ||
| // We don’t provide Options.WithMustUse because there is no other option that makes a difference | ||
| // once .mustUse is set. | ||
| if !algo.Available() { | ||
| return Options{}, fmt.Errorf("attempt to use an unavailable digest algorithm %q", algo.String()) | ||
| } | ||
| return Options{ | ||
| initialized: true, | ||
| mustUse: algo, | ||
| }, nil | ||
| } | ||
|
|
||
| // WithPreferred returns a copy of o with a “preferred” algorithm set to algo. | ||
| // The preferred algorithm is used whenever possible (but if there is a strict requirement to use something else, it will be overridden). | ||
| func (o Options) WithPreferred(algo digest.Algorithm) (Options, error) { | ||
| if err := o.ensureInitialized(); err != nil { | ||
| return Options{}, err | ||
| } | ||
| if o.prefer != "" { | ||
| return Options{}, errors.New("digests.Options already have a 'prefer' algorithm configured") | ||
| } | ||
|
|
||
| if !algo.Available() { | ||
| return Options{}, fmt.Errorf("attempt to use an unavailable digest algorithm %q", algo.String()) | ||
| } | ||
| o.prefer = algo | ||
| return o, nil | ||
| } | ||
|
|
||
| // WithDefault returns a copy of o with a “default” algorithm set to algo. | ||
| // The default algorithm is used if there is no reason to use anything else (e.g. when there is no pre-existing digest). | ||
| func (o Options) WithDefault(algo digest.Algorithm) (Options, error) { | ||
| if err := o.ensureInitialized(); err != nil { | ||
| return Options{}, err | ||
| } | ||
| if o.defaultAlgo != "" { | ||
| return Options{}, errors.New("digests.Options already have a 'default' algorithm configured") | ||
| } | ||
|
|
||
| if !algo.Available() { | ||
| return Options{}, fmt.Errorf("attempt to use an unavailable digest algorithm %q", algo.String()) | ||
| } | ||
| o.defaultAlgo = algo | ||
| return o, nil | ||
| } | ||
|
|
||
| // ensureInitialized returns an error if o is not initialized. | ||
| func (o Options) ensureInitialized() error { | ||
| if !o.initialized { | ||
| return errors.New("internal error: use of uninitialized digests.Options") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Situation records the context in which a digest is being chosen. | ||
| type Situation struct { | ||
| Preexisting digest.Digest // If not "", a pre-existing digest value (frequently one which is cheaper to use than others) | ||
| CannotChangeAlgorithmReason string // The reason why we must use Preexisting, or "" if we can use other algorithms. | ||
| } | ||
|
|
||
| // Choose chooses a digest algorithm based on the options and the situation. | ||
| func (o Options) Choose(s Situation) (digest.Algorithm, error) { | ||
| if err := o.ensureInitialized(); err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| if s.CannotChangeAlgorithmReason != "" && s.Preexisting == "" { | ||
| return "", fmt.Errorf("internal error: digests.Situation.CannotChangeAlgorithmReason is set but Preexisting is empty") | ||
| } | ||
|
|
||
| var choice digest.Algorithm // = what we want to use | ||
| switch { | ||
| case o.mustUse != "": | ||
| choice = o.mustUse | ||
| case s.CannotChangeAlgorithmReason != "": | ||
| choice = s.Preexisting.Algorithm() | ||
| if !choice.Available() { | ||
| return "", fmt.Errorf("existing digest uses unimplemented algorithm %s", choice) | ||
| } | ||
| case o.prefer != "": | ||
| choice = o.prefer | ||
| case s.Preexisting != "" && s.Preexisting.Algorithm().Available(): | ||
| choice = s.Preexisting.Algorithm() | ||
| case o.defaultAlgo != "": | ||
| choice = o.defaultAlgo | ||
| default: | ||
| choice = digest.Canonical // We assume digest.Canonical is always available. | ||
| } | ||
|
|
||
| if s.CannotChangeAlgorithmReason != "" && choice != s.Preexisting.Algorithm() { | ||
| return "", fmt.Errorf("requested to always use digest algorithm %s but we cannot replace existing digest algorithm %s: %s", | ||
| choice, s.Preexisting.Algorithm(), s.CannotChangeAlgorithmReason) | ||
| } | ||
|
|
||
| return choice, nil | ||
| } | ||
|
|
||
| // MustUseSet returns an algorithm if o is set to always use a specific algorithm, "" if it is flexible. | ||
| func (o Options) MustUseSet() digest.Algorithm { | ||
| // We don’t do .ensureInitialized() because that would require an extra error value just for that. | ||
| // This should not be a part of any public API either way. | ||
| if o.mustUse != "" { | ||
| return o.mustUse | ||
| } | ||
| return "" | ||
| } |
This file contains hidden or 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,201 @@ | ||
| package digests | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/opencontainers/go-digest" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestCanonicalDefault(t *testing.T) { | ||
| o := CanonicalDefault() | ||
| assert.Equal(t, Options{initialized: true}, o) | ||
| } | ||
|
|
||
| func TestMustUse(t *testing.T) { | ||
| o, err := MustUse(digest.SHA512) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, Options{ | ||
| initialized: true, | ||
| mustUse: digest.SHA512, | ||
| }, o) | ||
|
|
||
| _, err = MustUse(digest.Algorithm("this is not a known algorithm")) | ||
| require.Error(t, err) | ||
| } | ||
|
|
||
| func TestOptionsWithPreferred(t *testing.T) { | ||
| preferSHA512, err := CanonicalDefault().WithPreferred(digest.SHA512) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, Options{ | ||
| initialized: true, | ||
| prefer: digest.SHA512, | ||
| }, preferSHA512) | ||
|
|
||
| for _, c := range []struct { | ||
| base Options | ||
| algo digest.Algorithm | ||
| }{ | ||
| { // Uninitialized Options | ||
| base: Options{}, | ||
| algo: digest.SHA256, | ||
| }, | ||
| { // Unavailable algorithm | ||
| base: CanonicalDefault(), | ||
| algo: digest.Algorithm("this is not a known algorithm"), | ||
| }, | ||
| { // WithPreferred already called | ||
| base: preferSHA512, | ||
| algo: digest.SHA512, | ||
| }, | ||
| } { | ||
| _, err := c.base.WithPreferred(c.algo) | ||
| assert.Error(t, err) | ||
| } | ||
| } | ||
|
|
||
| func TestOptionsWithDefault(t *testing.T) { | ||
| defaultSHA512, err := CanonicalDefault().WithDefault(digest.SHA512) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, Options{ | ||
| initialized: true, | ||
| defaultAlgo: digest.SHA512, | ||
| }, defaultSHA512) | ||
|
|
||
| for _, c := range []struct { | ||
| base Options | ||
| algo digest.Algorithm | ||
| }{ | ||
| { // Uninitialized Options | ||
| base: Options{}, | ||
| algo: digest.SHA256, | ||
| }, | ||
| { // Unavailable algorithm | ||
| base: CanonicalDefault(), | ||
| algo: digest.Algorithm("this is not a known algorithm"), | ||
| }, | ||
| { // WithDefault already called | ||
| base: defaultSHA512, | ||
| algo: digest.SHA512, | ||
| }, | ||
| } { | ||
| _, err := c.base.WithDefault(c.algo) | ||
| assert.Error(t, err) | ||
| } | ||
| } | ||
|
|
||
| func TestOptionsChoose(t *testing.T) { | ||
| sha512Digest := digest.Digest("sha512:cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e") | ||
| unknownDigest := digest.Digest("unknown:abcd1234") | ||
|
|
||
| // The tests use sha512 = pre-existing if any; sha384 = primary configured; sha256 = supposedly irrelevant. | ||
|
|
||
| mustSHA384, err := MustUse(digest.SHA384) | ||
| require.NoError(t, err) | ||
| mustSHA384, err = mustSHA384.WithPreferred(digest.SHA256) | ||
| require.NoError(t, err) | ||
| mustSHA384, err = mustSHA384.WithDefault(digest.SHA256) | ||
| require.NoError(t, err) | ||
|
|
||
| preferSHA384, err := CanonicalDefault().WithPreferred(digest.SHA384) | ||
| require.NoError(t, err) | ||
| preferSHA384, err = preferSHA384.WithDefault(digest.SHA256) | ||
| require.NoError(t, err) | ||
|
|
||
| defaultSHA384, err := CanonicalDefault().WithDefault(digest.SHA384) | ||
| require.NoError(t, err) | ||
|
|
||
| cases := []struct { | ||
| opts Options | ||
| wantDefault digest.Algorithm | ||
| wantPreexisting digest.Algorithm // Pre-existing sha512 | ||
| wantCannotChange digest.Algorithm // Pre-existing sha512, CannotChange | ||
| wantUnavailable digest.Algorithm // Pre-existing unavailable | ||
| }{ | ||
| { | ||
| opts: Options{}, // uninitialized | ||
| wantDefault: "", | ||
| wantPreexisting: "", | ||
| wantCannotChange: "", | ||
| wantUnavailable: "", | ||
| }, | ||
| { | ||
| opts: mustSHA384, | ||
| wantDefault: digest.SHA384, | ||
| wantPreexisting: digest.SHA384, | ||
| wantCannotChange: "", | ||
| // Warning: We don’t generally _promise_ that unavailable digests are going to be silently ignored | ||
| // in these situations (e.g. we might still try to validate them when reading inputs). | ||
| wantUnavailable: digest.SHA384, | ||
| }, | ||
| { | ||
| opts: preferSHA384, | ||
| wantDefault: digest.SHA384, | ||
| wantPreexisting: digest.SHA384, | ||
| wantCannotChange: digest.SHA512, | ||
| wantUnavailable: digest.SHA384, | ||
| }, | ||
| { | ||
| opts: defaultSHA384, | ||
| wantDefault: digest.SHA384, | ||
| wantPreexisting: digest.SHA512, | ||
| wantCannotChange: digest.SHA512, | ||
| wantUnavailable: digest.SHA384, | ||
| }, | ||
| { | ||
| opts: CanonicalDefault(), | ||
| wantDefault: digest.SHA256, | ||
| wantPreexisting: digest.SHA512, | ||
| wantCannotChange: digest.SHA512, | ||
| wantUnavailable: digest.SHA256, | ||
| }, | ||
| } | ||
| for _, c := range cases { | ||
| run := func(s Situation, want digest.Algorithm) { | ||
| res, err := c.opts.Choose(s) | ||
| if want == "" { | ||
| require.Error(t, err) | ||
| } else { | ||
| require.NoError(t, err) | ||
| assert.Equal(t, want, res) | ||
| } | ||
| } | ||
|
|
||
| run(Situation{}, c.wantDefault) | ||
| run(Situation{Preexisting: sha512Digest}, c.wantPreexisting) | ||
| run(Situation{Preexisting: sha512Digest, CannotChangeAlgorithmReason: "test reason"}, c.wantCannotChange) | ||
| run(Situation{Preexisting: unknownDigest}, c.wantUnavailable) | ||
|
|
||
| run(Situation{Preexisting: unknownDigest, CannotChangeAlgorithmReason: "test reason"}, "") | ||
| run(Situation{CannotChangeAlgorithmReason: "test reason"}, "") // CannotChangeAlgorithm with missing Preexisting | ||
| } | ||
| } | ||
|
|
||
| func TestOptionsMustUseSet(t *testing.T) { | ||
| mustSHA512, err := MustUse(digest.SHA512) | ||
| require.NoError(t, err) | ||
| prefersSHA512, err := CanonicalDefault().WithPreferred(digest.SHA512) | ||
| require.NoError(t, err) | ||
| defaultSHA512, err := CanonicalDefault().WithDefault(digest.SHA512) | ||
| require.NoError(t, err) | ||
| for _, c := range []struct { | ||
| opts Options | ||
| want digest.Algorithm | ||
| }{ | ||
| { | ||
| opts: mustSHA512, | ||
| want: digest.SHA512, | ||
| }, | ||
| { | ||
| opts: prefersSHA512, | ||
| want: "", | ||
| }, | ||
| { | ||
| opts: defaultSHA512, | ||
| want: "", | ||
| }, | ||
| } { | ||
| assert.Equal(t, c.want, c.opts.MustUseSet()) | ||
| } | ||
| } |
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.
For experiments, this can be modified to refer to
storage/pkg/supported-digests/algorithm.goor be hard-coded todigests.MustUse(digests.SHA512).