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  }