| // Copyright 2020-2024 Buf Technologies, Inc. |
| // |
| // 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 bufmodule |
| |
| import ( |
| "time" |
| |
| "github.com/bufbuild/buf/private/pkg/syncext" |
| ) |
| |
| // Commit represents a Commit on the BSR. |
| type Commit interface { |
| // ModuleKey returns the ModuleKey for the Commit. |
| ModuleKey() ModuleKey |
| // CreateTime returns the time the Commit was created on the BSR. |
| CreateTime() (time.Time, error) |
| |
| isCommit() |
| } |
| |
| // NewCommit returns a new Commit. |
| func NewCommit( |
| moduleKey ModuleKey, |
| getCreateTime func() (time.Time, error), |
| options ...CommitOption, |
| ) Commit { |
| return newCommit( |
| moduleKey, |
| getCreateTime, |
| options..., |
| ) |
| } |
| |
| // CommitOption is an option for a new Commit. |
| type CommitOption func(*commitOptions) |
| |
| // CommitWithExpectedDigest returns a new CommitOption that will compare the Digest |
| // of the ModuleKey provided at construction with this digest whenever any lazy method is called. |
| // If the digests do not match, an error is returned |
| // |
| // This is used in situations where we have a Digest from our read location (such as the BSR |
| // or the cache), and we want to compare it with a ModuleKey we were provided from a local location. |
| func CommitWithExpectedDigest(expectedDigest Digest) CommitOption { |
| return func(commitOptions *commitOptions) { |
| commitOptions.expectedDigest = expectedDigest |
| } |
| } |
| |
| // *** PRIVATE *** |
| |
| type commit struct { |
| moduleKey ModuleKey |
| getCreateTime func() (time.Time, error) |
| } |
| |
| func newCommit( |
| moduleKey ModuleKey, |
| getCreateTime func() (time.Time, error), |
| options ...CommitOption, |
| ) *commit { |
| commitOptions := newCommitOptions() |
| for _, option := range options { |
| option(commitOptions) |
| } |
| if commitOptions.expectedDigest != nil { |
| // We need to preserve this, as if we do not, the new value for moduleKey |
| // will end up recursively calling itself when moduleKey.Digest() is called |
| // in the anonymous function. We could just extract moduleKeyDigestFunc := moduleKey.Digest |
| // and call that, but we make a variable to reference the original ModuleKey just for constency. |
| originalModuleKey := moduleKey |
| moduleKey = newModuleKeyNoValidate( |
| originalModuleKey.ModuleFullName(), |
| originalModuleKey.CommitID(), |
| func() (Digest, error) { |
| moduleKeyDigest, err := originalModuleKey.Digest() |
| if err != nil { |
| return nil, err |
| } |
| if !DigestEqual(commitOptions.expectedDigest, moduleKeyDigest) { |
| return nil, &DigestMismatchError{ |
| ModuleFullName: originalModuleKey.ModuleFullName(), |
| CommitID: originalModuleKey.CommitID(), |
| ExpectedDigest: commitOptions.expectedDigest, |
| ActualDigest: moduleKeyDigest, |
| } |
| } |
| return moduleKeyDigest, nil |
| }, |
| ) |
| } |
| return &commit{ |
| moduleKey: moduleKey, |
| getCreateTime: syncext.OnceValues(getCreateTime), |
| } |
| } |
| |
| func (c *commit) ModuleKey() ModuleKey { |
| return c.moduleKey |
| } |
| |
| func (c *commit) CreateTime() (time.Time, error) { |
| // This may invoke tamper-proofing per newCommit construction. |
| if _, err := c.moduleKey.Digest(); err != nil { |
| return time.Time{}, err |
| } |
| return c.getCreateTime() |
| } |
| |
| func (*commit) isCommit() {} |
| |
| type commitOptions struct { |
| expectedDigest Digest |
| } |
| |
| func newCommitOptions() *commitOptions { |
| return &commitOptions{} |
| } |