github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/whitesource/scan.go (about) 1 package whitesource 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "github.com/SAP/jenkins-library/pkg/log" 10 "github.com/SAP/jenkins-library/pkg/piperutils" 11 "github.com/SAP/jenkins-library/pkg/versioning" 12 ) 13 14 // Scan stores information about scanned WhiteSource projects (modules). 15 type Scan struct { 16 // AggregateProjectName stores the name of the WhiteSource project where scans shall be aggregated. 17 // It does not include the ProductVersion. 18 AggregateProjectName string 19 // ProductVersion is the global version that is used across all Projects (modules) during the scan. 20 BuildTool string 21 ProductToken string 22 ProductVersion string 23 scannedProjects map[string]Project 24 scanTimes map[string]time.Time 25 AgentName string 26 AgentVersion string 27 Coordinates versioning.Coordinates 28 } 29 30 func (s *Scan) init() { 31 if s.scannedProjects == nil { 32 s.scannedProjects = make(map[string]Project) 33 } 34 if s.scanTimes == nil { 35 s.scanTimes = make(map[string]time.Time) 36 } 37 } 38 39 func (s *Scan) versionSuffix() string { 40 return " - " + s.ProductVersion 41 } 42 43 // AppendScannedProject checks that no Project with the same name is already contained in the list of scanned projects, 44 // and appends a new Project with the given name. The global product version is appended to the name. 45 func (s *Scan) AppendScannedProject(projectName string) error { 46 if len(projectName) == 0 { 47 return fmt.Errorf("projectName must not be empty") 48 } 49 if strings.HasSuffix(projectName, s.versionSuffix()) { 50 return fmt.Errorf("projectName is not expected to include the product version already") 51 } 52 return s.AppendScannedProjectVersion(projectName + s.versionSuffix()) 53 } 54 55 // AppendScannedProjectVersion checks that no Project with the same name is already contained in the list of scanned 56 // projects, and appends a new Project with the given name (which is expected to include the product version). 57 func (s *Scan) AppendScannedProjectVersion(projectName string) error { 58 if !strings.HasSuffix(projectName, s.versionSuffix()) { 59 return fmt.Errorf("projectName is expected to include the product version") 60 } 61 if len(projectName) == len(s.versionSuffix()) { 62 return fmt.Errorf("projectName consists only of the product version") 63 } 64 s.init() 65 _, exists := s.scannedProjects[projectName] 66 if exists { 67 log.Entry().Errorf("A module with the name '%s' was already scanned. "+ 68 "Your project's modules must have unique names.", projectName) 69 return fmt.Errorf("project with name '%s' was already scanned", projectName) 70 } 71 s.scannedProjects[projectName] = Project{Name: projectName} 72 s.scanTimes[projectName] = time.Now() 73 return nil 74 } 75 76 // ProjectByName returns a WhiteSource Project previously established via AppendScannedProject(). 77 func (s *Scan) ProjectByName(projectName string) (Project, bool) { 78 project, exists := s.scannedProjects[projectName] 79 return project, exists 80 } 81 82 // ScannedProjects returns the WhiteSource projects that have been added via AppendScannedProject() as a slice. 83 func (s *Scan) ScannedProjects() []Project { 84 var projects []Project 85 for _, project := range s.scannedProjects { 86 projects = append(projects, project) 87 } 88 return projects 89 } 90 91 // ScannedProjectNames returns a sorted list of all scanned project names 92 func (s *Scan) ScannedProjectNames() []string { 93 projectNames := []string{} 94 for _, project := range s.ScannedProjects() { 95 projectNames = append(projectNames, project.Name) 96 } 97 // Sorting helps the list become stable across pipeline runs (and in the unit tests), 98 // as the order in which we travers map keys is not deterministic. 99 sort.Strings(projectNames) 100 return projectNames 101 } 102 103 // ScannedProjectTokens returns a sorted list of all scanned project's tokens 104 func (s *Scan) ScannedProjectTokens() []string { 105 projectTokens := []string{} 106 for _, project := range s.ScannedProjects() { 107 if len(project.Token) > 0 { 108 projectTokens = append(projectTokens, project.Token) 109 } 110 } 111 // Sorting helps the list become stable across pipeline runs (and in the unit tests), 112 // as the order in which we travers map keys is not deterministic. 113 sort.Strings(projectTokens) 114 return projectTokens 115 } 116 117 // ScanTime returns the time at which the respective WhiteSource Project was scanned, or the the 118 // zero value of time.Time, if AppendScannedProject() was not called with that name. 119 func (s *Scan) ScanTime(projectName string) time.Time { 120 if s.scanTimes == nil { 121 return time.Time{} 122 } 123 return s.scanTimes[projectName] 124 } 125 126 type whitesource interface { 127 GetProjectsMetaInfo(productToken string) ([]Project, error) 128 GetProjectRiskReport(projectToken string) ([]byte, error) 129 GetProjectVulnerabilityReport(projectToken string, format string) ([]byte, error) 130 } 131 132 // UpdateProjects pulls the current backend metadata for all WhiteSource projects in the product with 133 // the given productToken, and updates all scanned projects with the obtained information. 134 func (s *Scan) UpdateProjects(productToken string, sys whitesource) error { 135 s.init() 136 projects, err := sys.GetProjectsMetaInfo(productToken) 137 if err != nil { 138 return fmt.Errorf("failed to retrieve WhiteSource projects meta info: %w", err) 139 } 140 141 var projectsToUpdate []string 142 for projectName := range s.scannedProjects { 143 projectsToUpdate = append(projectsToUpdate, projectName) 144 } 145 146 for _, project := range projects { 147 _, exists := s.scannedProjects[project.Name] 148 if exists { 149 s.scannedProjects[project.Name] = project 150 projectsToUpdate, _ = piperutils.RemoveAll(projectsToUpdate, project.Name) 151 } 152 } 153 if len(projectsToUpdate) != 0 { 154 log.Entry().Warnf("Could not fetch metadata for projects %v", projectsToUpdate) 155 } 156 return nil 157 }