github.com/ishita82/trivy-gitaction@v0.0.0-20240206054925-e937cc05f8e3/integration/integration_test.go (about)

     1  //go:build integration || vm_integration || module_integration || k8s_integration
     2  
     3  package integration
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"github.com/aquasecurity/trivy/pkg/clock"
    11  	"io"
    12  	"net"
    13  	"os"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	cdx "github.com/CycloneDX/cyclonedx-go"
    21  	"github.com/samber/lo"
    22  	spdxjson "github.com/spdx/tools-golang/json"
    23  	"github.com/spdx/tools-golang/spdx"
    24  	"github.com/spdx/tools-golang/spdxlib"
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  	"github.com/xeipuuv/gojsonschema"
    28  
    29  	"github.com/aquasecurity/trivy-db/pkg/db"
    30  	"github.com/aquasecurity/trivy-db/pkg/metadata"
    31  	"github.com/aquasecurity/trivy/pkg/commands"
    32  	"github.com/aquasecurity/trivy/pkg/dbtest"
    33  	"github.com/aquasecurity/trivy/pkg/types"
    34  
    35  	_ "modernc.org/sqlite"
    36  )
    37  
    38  var update = flag.Bool("update", false, "update golden files")
    39  
    40  const SPDXSchema = "https://raw.githubusercontent.com/spdx/spdx-spec/development/v%s/schemas/spdx-schema.json"
    41  
    42  func initDB(t *testing.T) string {
    43  	fixtureDir := filepath.Join("testdata", "fixtures", "db")
    44  	entries, err := os.ReadDir(fixtureDir)
    45  	require.NoError(t, err)
    46  
    47  	var fixtures []string
    48  	for _, entry := range entries {
    49  		if entry.IsDir() {
    50  			continue
    51  		}
    52  		fixtures = append(fixtures, filepath.Join(fixtureDir, entry.Name()))
    53  	}
    54  
    55  	cacheDir := dbtest.InitDB(t, fixtures)
    56  	defer db.Close()
    57  
    58  	dbDir := filepath.Dir(db.Path(cacheDir))
    59  
    60  	metadataFile := filepath.Join(dbDir, "metadata.json")
    61  	f, err := os.Create(metadataFile)
    62  	require.NoError(t, err)
    63  
    64  	err = json.NewEncoder(f).Encode(metadata.Metadata{
    65  		Version:    db.SchemaVersion,
    66  		NextUpdate: time.Now().Add(24 * time.Hour),
    67  		UpdatedAt:  time.Now(),
    68  	})
    69  	require.NoError(t, err)
    70  
    71  	dbtest.InitJavaDB(t, cacheDir)
    72  	return cacheDir
    73  }
    74  
    75  func getFreePort() (int, error) {
    76  	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
    77  	if err != nil {
    78  		return 0, err
    79  	}
    80  
    81  	l, err := net.ListenTCP("tcp", addr)
    82  	if err != nil {
    83  		return 0, err
    84  	}
    85  	defer l.Close()
    86  	return l.Addr().(*net.TCPAddr).Port, nil
    87  }
    88  
    89  func waitPort(ctx context.Context, addr string) error {
    90  	for {
    91  		conn, err := net.Dial("tcp", addr)
    92  		if err == nil && conn != nil {
    93  			return nil
    94  		}
    95  		select {
    96  		case <-ctx.Done():
    97  			return err
    98  		default:
    99  			time.Sleep(1 * time.Second)
   100  		}
   101  	}
   102  }
   103  
   104  func readReport(t *testing.T, filePath string) types.Report {
   105  	t.Helper()
   106  
   107  	f, err := os.Open(filePath)
   108  	require.NoError(t, err, filePath)
   109  	defer f.Close()
   110  
   111  	var report types.Report
   112  	err = json.NewDecoder(f).Decode(&report)
   113  	require.NoError(t, err, filePath)
   114  
   115  	// We don't compare history because the nano-seconds in "created" don't match
   116  	report.Metadata.ImageConfig.History = nil
   117  
   118  	// We don't compare repo tags because the archive doesn't support it
   119  	report.Metadata.RepoTags = nil
   120  	report.Metadata.RepoDigests = nil
   121  
   122  	for i, result := range report.Results {
   123  		for j := range result.Vulnerabilities {
   124  			report.Results[i].Vulnerabilities[j].Layer.Digest = ""
   125  		}
   126  
   127  		sort.Slice(result.CustomResources, func(i, j int) bool {
   128  			if result.CustomResources[i].Type != result.CustomResources[j].Type {
   129  				return result.CustomResources[i].Type < result.CustomResources[j].Type
   130  			}
   131  			return result.CustomResources[i].FilePath < result.CustomResources[j].FilePath
   132  		})
   133  	}
   134  
   135  	return report
   136  }
   137  
   138  func readCycloneDX(t *testing.T, filePath string) *cdx.BOM {
   139  	f, err := os.Open(filePath)
   140  	require.NoError(t, err)
   141  	defer f.Close()
   142  
   143  	bom := cdx.NewBOM()
   144  	decoder := cdx.NewBOMDecoder(f, cdx.BOMFileFormatJSON)
   145  	err = decoder.Decode(bom)
   146  	require.NoError(t, err)
   147  
   148  	// Sort components
   149  	if bom.Components != nil {
   150  		sort.Slice(*bom.Components, func(i, j int) bool {
   151  			return (*bom.Components)[i].Name < (*bom.Components)[j].Name
   152  		})
   153  		for i := range *bom.Components {
   154  			(*bom.Components)[i].BOMRef = ""
   155  			sort.Slice(*(*bom.Components)[i].Properties, func(ii, jj int) bool {
   156  				return (*(*bom.Components)[i].Properties)[ii].Name < (*(*bom.Components)[i].Properties)[jj].Name
   157  			})
   158  		}
   159  		sort.Slice(*bom.Vulnerabilities, func(i, j int) bool {
   160  			return (*bom.Vulnerabilities)[i].ID < (*bom.Vulnerabilities)[j].ID
   161  		})
   162  	}
   163  
   164  	return bom
   165  }
   166  
   167  func readSpdxJson(t *testing.T, filePath string) *spdx.Document {
   168  	f, err := os.Open(filePath)
   169  	require.NoError(t, err)
   170  	defer f.Close()
   171  
   172  	bom, err := spdxjson.Read(f)
   173  	require.NoError(t, err)
   174  
   175  	sort.Slice(bom.Relationships, func(i, j int) bool {
   176  		if bom.Relationships[i].RefA.ElementRefID != bom.Relationships[j].RefA.ElementRefID {
   177  			return bom.Relationships[i].RefA.ElementRefID < bom.Relationships[j].RefA.ElementRefID
   178  		}
   179  		return bom.Relationships[i].RefB.ElementRefID < bom.Relationships[j].RefB.ElementRefID
   180  	})
   181  
   182  	sort.Slice(bom.Files, func(i, j int) bool {
   183  		return bom.Files[i].FileSPDXIdentifier < bom.Files[j].FileSPDXIdentifier
   184  	})
   185  
   186  	// We don't compare values which change each time an SBOM is generated
   187  	bom.CreationInfo.Created = ""
   188  	bom.DocumentNamespace = ""
   189  
   190  	return bom
   191  }
   192  
   193  func execute(osArgs []string) error {
   194  	// Set a fake time
   195  	ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC))
   196  
   197  	// Setup CLI App
   198  	app := commands.NewApp()
   199  	app.SetOut(io.Discard)
   200  	app.SetArgs(osArgs)
   201  
   202  	// Run Trivy
   203  	return app.ExecuteContext(ctx)
   204  }
   205  
   206  func compareReports(t *testing.T, wantFile, gotFile string, override func(*types.Report)) {
   207  	want := readReport(t, wantFile)
   208  	got := readReport(t, gotFile)
   209  	if override != nil {
   210  		override(&want)
   211  	}
   212  	assert.Equal(t, want, got)
   213  }
   214  
   215  func compareCycloneDX(t *testing.T, wantFile, gotFile string) {
   216  	want := readCycloneDX(t, wantFile)
   217  	got := readCycloneDX(t, gotFile)
   218  	assert.Equal(t, want, got)
   219  
   220  	// Validate CycloneDX output against the JSON schema
   221  	validateReport(t, got.JSONSchema, got)
   222  }
   223  
   224  func compareSPDXJson(t *testing.T, wantFile, gotFile string) {
   225  	want := readSpdxJson(t, wantFile)
   226  	got := readSpdxJson(t, gotFile)
   227  	assert.Equal(t, want, got)
   228  
   229  	SPDXVersion, ok := strings.CutPrefix(want.SPDXVersion, "SPDX-")
   230  	assert.True(t, ok)
   231  
   232  	assert.NoError(t, spdxlib.ValidateDocument(got))
   233  
   234  	// Validate SPDX output against the JSON schema
   235  	validateReport(t, fmt.Sprintf(SPDXSchema, SPDXVersion), got)
   236  }
   237  
   238  func validateReport(t *testing.T, schema string, report any) {
   239  	schemaLoader := gojsonschema.NewReferenceLoader(schema)
   240  	documentLoader := gojsonschema.NewGoLoader(report)
   241  	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
   242  	require.NoError(t, err)
   243  
   244  	if valid := result.Valid(); !valid {
   245  		errs := lo.Map(result.Errors(), func(err gojsonschema.ResultError, _ int) string {
   246  			return err.String()
   247  		})
   248  		assert.True(t, valid, strings.Join(errs, "\n"))
   249  	}
   250  }