@@ -175,12 +175,13 @@ func (d *Document) AddPackage(pkg *Package) error {
175175
176176 if pkg .SPDXID () == "" {
177177 pkg .BuildID (pkg .Name )
178+ d .ensureUniqueElementID (pkg )
178179 }
179180 if pkg .SPDXID () == "" {
180- return errors .New ("package id is needed to add a new package" )
181+ return errors .New ("package ID is needed to add a new package" )
181182 }
182183 if _ , ok := d .Packages [pkg .SPDXID ()]; ok {
183- return errors .New ("a package named " + pkg . SPDXID () + " already exists in the document" )
184+ return errors .Errorf ("a package with ID %s already exists in the document" , pkg . SPDXID () )
184185 }
185186
186187 d .Packages [pkg .SPDXID ()] = pkg
@@ -278,6 +279,7 @@ func (d *Document) AddFile(file *File) error {
278279 }
279280 file .ID = "SPDXRef-File-" + fmt .Sprintf ("%x" , h .Sum (nil ))
280281 }
282+ d .ensureUniqueElementID (file )
281283 d .Files [file .ID ] = file
282284 return nil
283285}
@@ -398,3 +400,75 @@ func (d *Document) WriteProvenanceStatement(opts *ProvenanceOptions, path string
398400 "writing sbom as provenance statement" ,
399401 )
400402}
403+
404+ // ensureUniquePackageID takes a string and checks if
405+ // there is another string with the same name in the document.
406+ // If there is one, it will append a digit until a unique name
407+ // is found.
408+ func (d * Document ) ensureUniqueElementID (o Object ) {
409+ newID := o .SPDXID ()
410+ i := 0
411+ for {
412+ // Check if there us already an element with the same ID
413+ if el := d .GetElementByID (newID ); el == nil {
414+ if o .SPDXID () != newID {
415+ logrus .Infof (
416+ "Element name changed from %s to %s to ensure it is unique" ,
417+ o .SPDXID (), newID ,
418+ )
419+ }
420+ o .SetSPDXID (newID )
421+ break
422+ }
423+ i ++
424+ newID = fmt .Sprintf ("%s-%04d" , o .SPDXID (), i )
425+ }
426+ }
427+
428+ // ensureUniquePeerIDs gets a relationship collection and ensures all peers
429+ // have unique IDs
430+ func (d * Document ) ensureUniquePeerIDs (rels * []* Relationship ) {
431+ // First, ensure peer names are unique among themselves
432+ seen := map [string ]struct {}{}
433+ for _ , rel := range * rels {
434+ if rel .Peer == nil || rel .Peer .SPDXID () == "" {
435+ continue
436+ }
437+ testName := rel .Peer .SPDXID ()
438+ i := 0
439+ for {
440+ if _ , ok := seen [testName ]; ! ok {
441+ rel .Peer .SetSPDXID (testName )
442+ seen [testName ] = struct {}{}
443+ break
444+ }
445+ i ++
446+ testName = fmt .Sprintf ("%s-%04d" , rel .Peer .SPDXID (), i )
447+ }
448+ }
449+
450+ // And then check against the document
451+ for _ , rel := range * rels {
452+ if rel .Peer == nil {
453+ continue
454+ }
455+ d .ensureUniqueElementID (rel .Peer )
456+ }
457+ }
458+
459+ // GetPackageByID queries the packages to search for a specific entity by name
460+ // note that this method returns a copy of the entity if found.
461+ func (d * Document ) GetElementByID (id string ) Object {
462+ seen := map [string ]struct {}{}
463+ for _ , p := range d .Packages {
464+ if sub := recursiveSearch (id , p , & seen ); sub != nil {
465+ return sub
466+ }
467+ }
468+ for _ , f := range d .Files {
469+ if sub := recursiveSearch (id , f , & seen ); sub != nil {
470+ return sub
471+ }
472+ }
473+ return nil
474+ }
0 commit comments