Skip to content

Commit ff0f8f4

Browse files
authored
Fix b5 module digest calculation with transitive dependencies (#3631)
This fixes the b5 digest calculation for remote Modules when a transitive dependency is no longer a dependency in the parents ModuleSet.
1 parent 6f65ecd commit ff0f8f4

File tree

6 files changed

+140
-203
lines changed

6 files changed

+140
-203
lines changed

private/bufpkg/bufmodule/added_module.go

Lines changed: 44 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -161,134 +161,77 @@ func (a *addedModule) ToModule(
161161
}
162162
return moduleData.V1Beta1OrV1BufLockObjectData()
163163
}
164-
// Imagine the following scenario:
164+
// getDepModuleKeysB5 gets the dependencies for the specific Module.
165165
//
166-
// module-a (local)
167-
// README.md
168-
// a.proto
169-
// b.proto
170-
// module-b:c1
171-
// README.md
172-
// c.proto
173-
// d.proto
174-
// module-b:c2
175-
// README.md
176-
// c.proto
177-
// d.proto
178-
// module-c:c1
179-
// e.proto
180-
// f.proto
181-
// module-c:c2
182-
// e.proto
183-
// f.proto
184-
// g.proto
166+
// This is needed to calculate the digest for the Module. A Module constructed from this
167+
// ModuleData as the target will require all Modules referenced by its DepModuleKeys to
168+
// be present in the ModuleSet.
185169
//
186-
// Then, you have this dependency graph:
170+
// Modules that depend on this remote Module will include this Module and its data.
171+
// However all the dependencies of the remote Module may not be present in the parents ModuleSet.
172+
// As the target Module will use its direct dependencies to resolve the dependencies required.
173+
// The digest of the remote Module is however, unchanged. It is calculated based on the contents
174+
// and its dependencies, not the dependencies of the parent ModuleSet.
187175
//
188-
// module-a -> module-b:c1, module-c:c2
189-
// module-b:c1 -> module-c:c1
176+
// In contrast, a local Module dependency can be thought of as a ModuleKey at the latest commit.
177+
// It will always use the bucket and dependencies, which may be resolved recursively for
178+
// dependencies on other local Modules, to calculate its digest.
179+
// This is the difference between the ModuleData digest calculation and the Module
180+
// digest calculation. As remote Modules are required to have all their dependencies as
181+
// ModuleKeys, they can calculate their digest directly from the contents and dependencies,
182+
// without needing to recursively resolve the digest as local Modules do.
190183
//
191-
// Note that module-b depends on an earlier commit of module-c than module-a does.
184+
// For example, consider the following modules at commits with their dependencies:
185+
// ```
186+
// X:C1 (X has no dependencies)
187+
// A:C1 -> X:C1 (A depends on X)
188+
// B:C1 -> A:C1 ~> X:C1 (B depends on A, transitive dependency on X)
189+
// A:C2 (A removes the dependency on X)
190+
// C:C1 -> A:C2, B:C1 (C depends on A and B, X is not a dependency)
191+
// ```
192+
// The ModuleSet for C:C1 will include B:C1 and A:C2, but not A:C1 or X:C1.
193+
// This is because for C:C1 it will use the direct dependencies to resolve its dependencies.
194+
// A is required by both B:C1 and C:C1, the latest A:C2 is chosen.
192195
//
193-
// If we were to just use the dependencies in the ModuleSet to compute the digest, the following
194-
// would happen as a calculation:
196+
// The ModuleSet for B:C1 will include A:C1 and X:C1.
197+
// When calculating the digest for B:C1 in the ModuleSet of C:C1, the ModuleSet
198+
// ModuleDeps cannot be used to resolve the dependencies of B:C1. It must use
199+
// the dependencies of B:C1, which are A:C1 and X:C1, not A:C2.
195200
//
196-
// DIGEST(module-a) = digest(
197-
// // module-a contents
198-
// README.md,
199-
// a.proto,
200-
// b.proto,
201-
// // module-b:c1 digest
202-
// DIGEST(
203-
// README.md,
204-
// c.proto,
205-
// d.proto,
206-
// // module-c:c2 digest
207-
// DIGEST(
208-
// README.md,
209-
// e.proto,
210-
// f.proto,
211-
// g.proto,
212-
// ),
213-
// ),
214-
// // module-c:c2 digest
215-
// DIGEST(
216-
// README.md,
217-
// e.proto,
218-
// f.proto,
219-
// g.proto,
220-
// ),
221-
// )
222-
//
223-
// Note that to compute the digest of module-b:c1, we would actually use the digest of
224-
// module-c:c2, as opposed to module-c:c1, since within the ModuleSet, we would resolve
225-
// to use module-c:c2 instead of module-c:c1.
226-
//
227-
// We should be using module-c:c1 to compute the digest of module-b:c1:
228-
//
229-
// DIGEST(module-a) = digest(
230-
// // module-a contents
231-
// README.md,
232-
// a.proto,
233-
// b.proto,
234-
// // module-b:c1 digest
235-
// DIGEST(
236-
// README.md,
237-
// c.proto,
238-
// d.proto,
239-
// // module-c:c1 digest
240-
// DIGEST(
241-
// README.md,
242-
// e.proto,
243-
// f.proto,
244-
// ),
245-
// ),
246-
// // module-c:c2 digest
247-
// DIGEST(
248-
// README.md,
249-
// e.proto,
250-
// f.proto,
251-
// g.proto,
252-
// ),
253-
// )
254-
//
255-
// To accomplish this, we need to take the dependencies of the declared ModuleKeys (ie what
256-
// the Module actually says is in its buf.lock). This function enables us to do that for
257-
// digest calculations. Within the Module, we say that if we get a remote Module, use the
258-
// declared ModuleKeys instead of whatever Module we have resolved to for a given FullName.
259-
getDeclaredDepModuleKeysB5 := func() ([]ModuleKey, error) {
201+
// This is used for digest calculations. It is not used otherwise.
202+
getDepModuleKeysB5 := func() ([]ModuleKey, error) {
260203
moduleData, err := getModuleData()
261204
if err != nil {
262205
return nil, err
263206
}
264-
declaredDepModuleKeys, err := moduleData.DeclaredDepModuleKeys()
207+
depModuleKeys, err := moduleData.DepModuleKeys()
265208
if err != nil {
266209
return nil, err
267210
}
268-
if len(declaredDepModuleKeys) == 0 {
211+
if len(depModuleKeys) == 0 {
269212
return nil, nil
270213
}
271214
var digestType DigestType
272-
for i, moduleKey := range declaredDepModuleKeys {
215+
for i, moduleKey := range depModuleKeys {
273216
digest, err := moduleKey.Digest()
274217
if err != nil {
275218
return nil, err
276219
}
277220
if i == 0 {
278221
digestType = digest.Type()
279222
} else if digestType != digest.Type() {
280-
return nil, syserror.Newf("multiple digest types found in DeclaredDepModuleKeys: %v, %v", digestType, digest.Type())
223+
return nil, syserror.Newf("multiple digest types found in DepModuleKeys: %v, %v", digestType, digest.Type())
281224
}
282225
}
283226
switch digestType {
284227
case DigestTypeB4:
285-
// The declared ModuleKey dependencies for a commit may be stored in v1 buf.lock file,
228+
// The ModuleKey dependencies for a commit may be stored in v1 buf.lock file,
286229
// in which case they will use B4 digests. B4 digests aren't allowed to be used as
287230
// input to the B5 digest calculation, so we perform a call to convert all ModuleKeys
288231
// from B4 to B5 by using the commit provider.
289-
commitKeysToFetch := make([]CommitKey, len(declaredDepModuleKeys))
290-
for i, declaredDepModuleKey := range declaredDepModuleKeys {
291-
commitKey, err := NewCommitKey(declaredDepModuleKey.FullName().Registry(), declaredDepModuleKey.CommitID(), DigestTypeB5)
232+
commitKeysToFetch := make([]CommitKey, len(depModuleKeys))
233+
for i, depModuleKey := range depModuleKeys {
234+
commitKey, err := NewCommitKey(depModuleKey.FullName().Registry(), depModuleKey.CommitID(), DigestTypeB5)
292235
if err != nil {
293236
return nil, err
294237
}
@@ -305,8 +248,8 @@ func (a *addedModule) ToModule(
305248
return commit.ModuleKey()
306249
}), nil
307250
case DigestTypeB5:
308-
// No need to fetch b5 digests - we've already got them stored in the module's declared dependencies.
309-
return declaredDepModuleKeys, nil
251+
// No need to fetch b5 digests - we've already got them stored in the module's dependencies.
252+
return depModuleKeys, nil
310253
default:
311254
return nil, syserror.Newf("unsupported digest type: %v", digestType)
312255
}
@@ -322,7 +265,7 @@ func (a *addedModule) ToModule(
322265
false,
323266
getV1BufYAMLObjectData,
324267
getV1BufLockObjectData,
325-
getDeclaredDepModuleKeysB5,
268+
getDepModuleKeysB5,
326269
a.remoteTargetPaths,
327270
a.remoteTargetExcludePaths,
328271
"",

private/bufpkg/bufmodule/bufmodulestore/module_data_store.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,9 @@ func (p *moduleDataStore) getModuleDataForModuleKey(
262262
// We don't want to do this lazily (or anything else in this function) as we want to
263263
// make sure everything we have is valid before returning so we can auto-correct
264264
// the cache if necessary.
265-
declaredDepModuleKeys, err := slicesext.MapError(
265+
depModuleKeys, err := slicesext.MapError(
266266
externalModuleData.Deps,
267-
getDeclaredDepModuleKeyForExternalModuleDataDep,
267+
getDepModuleKeyForExternalModuleDataDep,
268268
)
269269
if err != nil {
270270
return nil, err
@@ -316,7 +316,7 @@ func (p *moduleDataStore) getModuleDataForModuleKey(
316316
), nil
317317
},
318318
func() ([]bufmodule.ModuleKey, error) {
319-
return declaredDepModuleKeys, nil
319+
return depModuleKeys, nil
320320
},
321321
func() (bufmodule.ObjectData, error) {
322322
return v1BufYAMLObjectData, nil
@@ -461,7 +461,7 @@ func (p *moduleDataStore) putModuleData(
461461
}
462462
}
463463
// Proceed to writing module data.
464-
depModuleKeys, err := moduleData.DeclaredDepModuleKeys()
464+
depModuleKeys, err := moduleData.DepModuleKeys()
465465
if err != nil {
466466
return err
467467
}
@@ -653,7 +653,7 @@ func getModuleDataStoreTarPath(moduleKey bufmodule.ModuleKey) (string, error) {
653653
), nil
654654
}
655655

656-
func getDeclaredDepModuleKeyForExternalModuleDataDep(dep externalModuleDataDep) (bufmodule.ModuleKey, error) {
656+
func getDepModuleKeyForExternalModuleDataDep(dep externalModuleDataDep) (bufmodule.ModuleKey, error) {
657657
if dep.Name == "" {
658658
return nil, errors.New("no module name specified")
659659
}

private/bufpkg/bufmodule/bufmoduletesting/bufmoduletesting.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ func (o *omniProvider) getModuleDataForModuleKey(
311311
if err != nil {
312312
return nil, err
313313
}
314-
declaredDepModuleKeys, err := slicesext.MapError(
314+
depModuleKeys, err := slicesext.MapError(
315315
moduleDeps,
316316
func(moduleDep bufmodule.ModuleDep) (bufmodule.ModuleKey, error) {
317317
return bufmodule.ModuleToModuleKey(moduleDep, digest.Type())
@@ -327,7 +327,7 @@ func (o *omniProvider) getModuleDataForModuleKey(
327327
return bufmodule.ModuleReadBucketToStorageReadBucket(module), nil
328328
},
329329
func() ([]bufmodule.ModuleKey, error) {
330-
return declaredDepModuleKeys, nil
330+
return depModuleKeys, nil
331331
},
332332
func() (bufmodule.ObjectData, error) {
333333
return module.V1Beta1OrV1BufYAMLObjectData()

0 commit comments

Comments
 (0)