diff --git a/internal/testing/graphqlClients/guacdata.go b/internal/testing/graphqlClients/guacdata.go index 9b5185154f..b68712bb3d 100644 --- a/internal/testing/graphqlClients/guacdata.go +++ b/internal/testing/graphqlClients/guacdata.go @@ -17,6 +17,7 @@ package clients import ( "context" + "strings" "testing" "time" @@ -64,7 +65,7 @@ const ( defaultHasSlsaPredicateValue = "test-predicate-value" ) -// Defines the Guac graph, to test clients of the Graphql server. +// GuacData Defines the Guac graph, to test clients of the Graphql server. // // This type, along with the Ingest function, is similar to the backend IngestPredicates // type and the corresponding assembler function, but allows for significantly less verbose @@ -76,10 +77,11 @@ const ( // could be added if needed. type GuacData struct { /** the nouns need to be specified here in order to be referenced from a verb **/ - Packages []string // packages are specified by purl - Artifacts []string // artifacts are specified by digest - Sources []string // sources are specified by the name in the SourceName node - Builders []string // builders are specified by URI + Packages []string // packages are specified by purl + Artifacts []string // artifacts are specified by digest + Sources []string // sources are specified by the name in the SourceName node + Builders []string // builders are specified by URI + Vulnerabilities []string // vulnerabilities are specified by type and ID /** verbs **/ HasSboms []HasSbom @@ -87,6 +89,7 @@ type GuacData struct { IsDependencies []IsDependency HashEquals []HashEqual HasSlsas []HasSlsa + CertifyVulns []CertifyVuln // Other graphql verbs still need to be added here } @@ -124,13 +127,19 @@ type HasSlsa struct { Spec *gql.SLSAInputSpec // if nil, a default will be used } +type CertifyVuln struct { + Package string + Vulnerability string + Metadata *gql.ScanMetadataInput // if nil, a default will be used +} + // maintains the ids of nouns, to use when ingesting verbs type nounIds struct { - PackageIds map[string]string // map from purls to IDs of PackageName nodes - ArtifactIds map[string]string // map from digest to IDs of Artifact nodes - SourceIds map[string]string // map from source names to IDs of SourceName nodes - BuilderIds map[string]string // map from URI to IDs of Builder nodes - + PackageIds map[string]string // map from purls to IDs of PackageName nodes + ArtifactIds map[string]string // map from digest to IDs of Artifact nodes + SourceIds map[string]string // map from source names to IDs of SourceName nodes + BuilderIds map[string]string // map from URI to IDs of Builder nodes + VulnerabilityIds map[string]string // map from vulnerability type and ID to IDs of Vulnerability nodes } func Ingest(ctx context.Context, t *testing.T, gqlClient graphql.Client, data GuacData) nounIds { @@ -154,11 +163,17 @@ func Ingest(ctx context.Context, t *testing.T, gqlClient graphql.Client, data Gu builderIds[builder] = ingestBuilder(ctx, t, gqlClient, builder) } + vulnerabilityIds := map[string]string{} + for _, vuln := range data.Vulnerabilities { + vulnerabilityIds[vuln] = ingestVulnerability(ctx, t, gqlClient, vuln) + } + i := nounIds{ - PackageIds: packageIds, - ArtifactIds: artifactIds, - SourceIds: sourceIds, - BuilderIds: builderIds, + PackageIds: packageIds, + ArtifactIds: artifactIds, + SourceIds: sourceIds, + BuilderIds: builderIds, + VulnerabilityIds: vulnerabilityIds, } for _, sbom := range data.HasSboms { @@ -181,6 +196,10 @@ func Ingest(ctx context.Context, t *testing.T, gqlClient graphql.Client, data Gu i.ingestHasSlsa(ctx, t, gqlClient, hasSlsa) } + for _, certifyVuln := range data.CertifyVulns { + i.ingestCertifyVuln(ctx, t, gqlClient, certifyVuln) + } + return i } @@ -438,3 +457,46 @@ func ingestBuilder(ctx context.Context, t *testing.T, gqlClient graphql.Client, } return res.GetIngestBuilder() } + +func ingestVulnerability(ctx context.Context, t *testing.T, gqlClient graphql.Client, vuln string) string { + parts := strings.SplitN(vuln, "/", 2) + if len(parts) != 2 { + t.Fatalf("Invalid vulnerability format: %s", vuln) + } + vulnType, vulnID := parts[0], parts[1] + + spec := gql.VulnerabilityInputSpec{ + Type: vulnType, + VulnerabilityID: vulnID, + } + idOrInputSpec := gql.IDorVulnerabilityInput{VulnerabilityInput: &spec} + res, err := gql.IngestVulnerability(ctx, gqlClient, idOrInputSpec) + if err != nil { + t.Fatalf("Error ingesting vulnerability when setting up test: %s", err) + } + return res.IngestVulnerability.VulnerabilityNodeID +} + +func (i nounIds) ingestCertifyVuln(ctx context.Context, t *testing.T, gqlClient graphql.Client, certifyVuln CertifyVuln) { + spec := certifyVuln.Metadata + if spec == nil { + spec = &gql.ScanMetadataInput{} + } + + packageId, ok := i.PackageIds[certifyVuln.Package] + if !ok { + t.Fatalf("The package %s has not been ingested", certifyVuln.Package) + } + pkgSpec := gql.IDorPkgInput{PackageVersionID: &packageId} + + vulnerabilityId, ok := i.VulnerabilityIds[certifyVuln.Vulnerability] + if !ok { + t.Fatalf("The vulnerability %s has not been ingested", certifyVuln.Vulnerability) + } + vulnSpec := gql.IDorVulnerabilityInput{VulnerabilityNodeID: &vulnerabilityId} + + _, err := gql.IngestCertifyVulnPkg(ctx, gqlClient, pkgSpec, vulnSpec, *spec) + if err != nil { + t.Fatalf("Error ingesting CertifyVuln when setting up test: %s", err) + } +} diff --git a/pkg/guacrest/client/client.go b/pkg/guacrest/client/client.go index 7ff6a4fe7e..7e15369c1e 100644 --- a/pkg/guacrest/client/client.go +++ b/pkg/guacrest/client/client.go @@ -94,23 +94,20 @@ type ClientInterface interface { // HealthCheck request HealthCheck(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // RetrieveDependencies request - RetrieveDependencies(ctx context.Context, params *RetrieveDependenciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetArtifactDeps request + GetArtifactDeps(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetArtifactDependencies request - GetArtifactDependencies(ctx context.Context, artifact string, params *GetArtifactDependenciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetArtifactVulns request + GetArtifactVulns(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetArtifactVulnerabilities request - GetArtifactVulnerabilities(ctx context.Context, artifact string, params *GetArtifactVulnerabilitiesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetPackagePurls request + GetPackagePurls(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetPackagePurlsByPurl request - GetPackagePurlsByPurl(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetPackageDeps request + GetPackageDeps(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetPackageDependenciesByPurl request - GetPackageDependenciesByPurl(ctx context.Context, purl string, params *GetPackageDependenciesByPurlParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetPackageVulnerabilitiesByPurl request - GetPackageVulnerabilitiesByPurl(ctx context.Context, purl string, params *GetPackageVulnerabilitiesByPurlParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetPackageVulns request + GetPackageVulns(ctx context.Context, purl string, params *GetPackageVulnsParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) AnalyzeDependencies(ctx context.Context, params *AnalyzeDependenciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -137,20 +134,8 @@ func (c *Client) HealthCheck(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) RetrieveDependencies(ctx context.Context, params *RetrieveDependenciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRetrieveDependenciesRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetArtifactDependencies(ctx context.Context, artifact string, params *GetArtifactDependenciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetArtifactDependenciesRequest(c.Server, artifact, params) +func (c *Client) GetArtifactDeps(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetArtifactDepsRequest(c.Server, digest) if err != nil { return nil, err } @@ -161,8 +146,8 @@ func (c *Client) GetArtifactDependencies(ctx context.Context, artifact string, p return c.Client.Do(req) } -func (c *Client) GetArtifactVulnerabilities(ctx context.Context, artifact string, params *GetArtifactVulnerabilitiesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetArtifactVulnerabilitiesRequest(c.Server, artifact, params) +func (c *Client) GetArtifactVulns(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetArtifactVulnsRequest(c.Server, digest) if err != nil { return nil, err } @@ -173,8 +158,8 @@ func (c *Client) GetArtifactVulnerabilities(ctx context.Context, artifact string return c.Client.Do(req) } -func (c *Client) GetPackagePurlsByPurl(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetPackagePurlsByPurlRequest(c.Server, purl) +func (c *Client) GetPackagePurls(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetPackagePurlsRequest(c.Server, purl) if err != nil { return nil, err } @@ -185,8 +170,8 @@ func (c *Client) GetPackagePurlsByPurl(ctx context.Context, purl string, reqEdit return c.Client.Do(req) } -func (c *Client) GetPackageDependenciesByPurl(ctx context.Context, purl string, params *GetPackageDependenciesByPurlParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetPackageDependenciesByPurlRequest(c.Server, purl, params) +func (c *Client) GetPackageDeps(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetPackageDepsRequest(c.Server, purl) if err != nil { return nil, err } @@ -197,8 +182,8 @@ func (c *Client) GetPackageDependenciesByPurl(ctx context.Context, purl string, return c.Client.Do(req) } -func (c *Client) GetPackageVulnerabilitiesByPurl(ctx context.Context, purl string, params *GetPackageVulnerabilitiesByPurlParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetPackageVulnerabilitiesByPurlRequest(c.Server, purl, params) +func (c *Client) GetPackageVulns(ctx context.Context, purl string, params *GetPackageVulnsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetPackageVulnsRequest(c.Server, purl, params) if err != nil { return nil, err } @@ -297,110 +282,13 @@ func NewHealthCheckRequest(server string) (*http.Request, error) { return req, nil } -// NewRetrieveDependenciesRequest generates requests for RetrieveDependencies -func NewRetrieveDependenciesRequest(server string, params *RetrieveDependenciesParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/query/dependencies") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if params.PaginationSpec != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "paginationSpec", runtime.ParamLocationQuery, *params.PaginationSpec); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.LinkCondition != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "linkCondition", runtime.ParamLocationQuery, *params.LinkCondition); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Purl != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "purl", runtime.ParamLocationQuery, *params.Purl); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Digest != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "digest", runtime.ParamLocationQuery, *params.Digest); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetArtifactDependenciesRequest generates requests for GetArtifactDependencies -func NewGetArtifactDependenciesRequest(server string, artifact string, params *GetArtifactDependenciesParams) (*http.Request, error) { +// NewGetArtifactDepsRequest generates requests for GetArtifactDeps +func NewGetArtifactDepsRequest(server string, digest string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "artifact", runtime.ParamLocationPath, artifact) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "digest", runtime.ParamLocationPath, digest) if err != nil { return nil, err } @@ -420,28 +308,6 @@ func NewGetArtifactDependenciesRequest(server string, artifact string, params *G return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.LatestSBOM != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "latestSBOM", runtime.ParamLocationQuery, *params.LatestSBOM); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -450,13 +316,13 @@ func NewGetArtifactDependenciesRequest(server string, artifact string, params *G return req, nil } -// NewGetArtifactVulnerabilitiesRequest generates requests for GetArtifactVulnerabilities -func NewGetArtifactVulnerabilitiesRequest(server string, artifact string, params *GetArtifactVulnerabilitiesParams) (*http.Request, error) { +// NewGetArtifactVulnsRequest generates requests for GetArtifactVulns +func NewGetArtifactVulnsRequest(server string, digest string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "artifact", runtime.ParamLocationPath, artifact) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "digest", runtime.ParamLocationPath, digest) if err != nil { return nil, err } @@ -476,28 +342,6 @@ func NewGetArtifactVulnerabilitiesRequest(server string, artifact string, params return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.LatestSBOM != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "latestSBOM", runtime.ParamLocationQuery, *params.LatestSBOM); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -506,8 +350,8 @@ func NewGetArtifactVulnerabilitiesRequest(server string, artifact string, params return req, nil } -// NewGetPackagePurlsByPurlRequest generates requests for GetPackagePurlsByPurl -func NewGetPackagePurlsByPurlRequest(server string, purl string) (*http.Request, error) { +// NewGetPackagePurlsRequest generates requests for GetPackagePurls +func NewGetPackagePurlsRequest(server string, purl string) (*http.Request, error) { var err error var pathParam0 string @@ -540,8 +384,8 @@ func NewGetPackagePurlsByPurlRequest(server string, purl string) (*http.Request, return req, nil } -// NewGetPackageDependenciesByPurlRequest generates requests for GetPackageDependenciesByPurl -func NewGetPackageDependenciesByPurlRequest(server string, purl string, params *GetPackageDependenciesByPurlParams) (*http.Request, error) { +// NewGetPackageDepsRequest generates requests for GetPackageDeps +func NewGetPackageDepsRequest(server string, purl string) (*http.Request, error) { var err error var pathParam0 string @@ -566,28 +410,6 @@ func NewGetPackageDependenciesByPurlRequest(server string, purl string, params * return nil, err } - if params != nil { - queryValues := queryURL.Query() - - if params.LatestSBOM != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "latestSBOM", runtime.ParamLocationQuery, *params.LatestSBOM); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -596,8 +418,8 @@ func NewGetPackageDependenciesByPurlRequest(server string, purl string, params * return req, nil } -// NewGetPackageVulnerabilitiesByPurlRequest generates requests for GetPackageVulnerabilitiesByPurl -func NewGetPackageVulnerabilitiesByPurlRequest(server string, purl string, params *GetPackageVulnerabilitiesByPurlParams) (*http.Request, error) { +// NewGetPackageVulnsRequest generates requests for GetPackageVulns +func NewGetPackageVulnsRequest(server string, purl string, params *GetPackageVulnsParams) (*http.Request, error) { var err error var pathParam0 string @@ -625,9 +447,9 @@ func NewGetPackageVulnerabilitiesByPurlRequest(server string, purl string, param if params != nil { queryValues := queryURL.Query() - if params.LatestSBOM != nil { + if params.IncludeDependencies != nil { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "latestSBOM", runtime.ParamLocationQuery, *params.LatestSBOM); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "includeDependencies", runtime.ParamLocationQuery, *params.IncludeDependencies); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -701,23 +523,20 @@ type ClientWithResponsesInterface interface { // HealthCheckWithResponse request HealthCheckWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*HealthCheckResponse, error) - // RetrieveDependenciesWithResponse request - RetrieveDependenciesWithResponse(ctx context.Context, params *RetrieveDependenciesParams, reqEditors ...RequestEditorFn) (*RetrieveDependenciesResponse, error) - - // GetArtifactDependenciesWithResponse request - GetArtifactDependenciesWithResponse(ctx context.Context, artifact string, params *GetArtifactDependenciesParams, reqEditors ...RequestEditorFn) (*GetArtifactDependenciesResponse, error) + // GetArtifactDepsWithResponse request + GetArtifactDepsWithResponse(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*GetArtifactDepsResponse, error) - // GetArtifactVulnerabilitiesWithResponse request - GetArtifactVulnerabilitiesWithResponse(ctx context.Context, artifact string, params *GetArtifactVulnerabilitiesParams, reqEditors ...RequestEditorFn) (*GetArtifactVulnerabilitiesResponse, error) + // GetArtifactVulnsWithResponse request + GetArtifactVulnsWithResponse(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*GetArtifactVulnsResponse, error) - // GetPackagePurlsByPurlWithResponse request - GetPackagePurlsByPurlWithResponse(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*GetPackagePurlsByPurlResponse, error) + // GetPackagePurlsWithResponse request + GetPackagePurlsWithResponse(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*GetPackagePurlsResponse, error) - // GetPackageDependenciesByPurlWithResponse request - GetPackageDependenciesByPurlWithResponse(ctx context.Context, purl string, params *GetPackageDependenciesByPurlParams, reqEditors ...RequestEditorFn) (*GetPackageDependenciesByPurlResponse, error) + // GetPackageDepsWithResponse request + GetPackageDepsWithResponse(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*GetPackageDepsResponse, error) - // GetPackageVulnerabilitiesByPurlWithResponse request - GetPackageVulnerabilitiesByPurlWithResponse(ctx context.Context, purl string, params *GetPackageVulnerabilitiesByPurlParams, reqEditors ...RequestEditorFn) (*GetPackageVulnerabilitiesByPurlResponse, error) + // GetPackageVulnsWithResponse request + GetPackageVulnsWithResponse(ctx context.Context, purl string, params *GetPackageVulnsParams, reqEditors ...RequestEditorFn) (*GetPackageVulnsResponse, error) } type AnalyzeDependenciesResponse struct { @@ -767,7 +586,7 @@ func (r HealthCheckResponse) StatusCode() int { return 0 } -type RetrieveDependenciesResponse struct { +type GetArtifactDepsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *PurlList @@ -777,7 +596,7 @@ type RetrieveDependenciesResponse struct { } // Status returns HTTPResponse.Status -func (r RetrieveDependenciesResponse) Status() string { +func (r GetArtifactDepsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -785,47 +604,24 @@ func (r RetrieveDependenciesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r RetrieveDependenciesResponse) StatusCode() int { +func (r GetArtifactDepsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetArtifactDependenciesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *PurlList - JSON400 *BadRequest - JSON500 *InternalServerError -} - -// Status returns HTTPResponse.Status -func (r GetArtifactDependenciesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetArtifactDependenciesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetArtifactVulnerabilitiesResponse struct { +type GetArtifactVulnsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *VulnerabilityList JSON400 *BadRequest JSON500 *InternalServerError + JSON502 *BadGateway } // Status returns HTTPResponse.Status -func (r GetArtifactVulnerabilitiesResponse) Status() string { +func (r GetArtifactVulnsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -833,23 +629,24 @@ func (r GetArtifactVulnerabilitiesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetArtifactVulnerabilitiesResponse) StatusCode() int { +func (r GetArtifactVulnsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetPackagePurlsByPurlResponse struct { +type GetPackagePurlsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]string + JSON200 *PurlList JSON400 *BadRequest JSON500 *InternalServerError + JSON502 *BadGateway } // Status returns HTTPResponse.Status -func (r GetPackagePurlsByPurlResponse) Status() string { +func (r GetPackagePurlsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -857,23 +654,24 @@ func (r GetPackagePurlsByPurlResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetPackagePurlsByPurlResponse) StatusCode() int { +func (r GetPackagePurlsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetPackageDependenciesByPurlResponse struct { +type GetPackageDepsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *PurlList JSON400 *BadRequest JSON500 *InternalServerError + JSON502 *BadGateway } // Status returns HTTPResponse.Status -func (r GetPackageDependenciesByPurlResponse) Status() string { +func (r GetPackageDepsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -881,23 +679,24 @@ func (r GetPackageDependenciesByPurlResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetPackageDependenciesByPurlResponse) StatusCode() int { +func (r GetPackageDepsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetPackageVulnerabilitiesByPurlResponse struct { +type GetPackageVulnsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *VulnerabilityList JSON400 *BadRequest JSON500 *InternalServerError + JSON502 *BadGateway } // Status returns HTTPResponse.Status -func (r GetPackageVulnerabilitiesByPurlResponse) Status() string { +func (r GetPackageVulnsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -905,7 +704,7 @@ func (r GetPackageVulnerabilitiesByPurlResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetPackageVulnerabilitiesByPurlResponse) StatusCode() int { +func (r GetPackageVulnsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -930,58 +729,49 @@ func (c *ClientWithResponses) HealthCheckWithResponse(ctx context.Context, reqEd return ParseHealthCheckResponse(rsp) } -// RetrieveDependenciesWithResponse request returning *RetrieveDependenciesResponse -func (c *ClientWithResponses) RetrieveDependenciesWithResponse(ctx context.Context, params *RetrieveDependenciesParams, reqEditors ...RequestEditorFn) (*RetrieveDependenciesResponse, error) { - rsp, err := c.RetrieveDependencies(ctx, params, reqEditors...) +// GetArtifactDepsWithResponse request returning *GetArtifactDepsResponse +func (c *ClientWithResponses) GetArtifactDepsWithResponse(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*GetArtifactDepsResponse, error) { + rsp, err := c.GetArtifactDeps(ctx, digest, reqEditors...) if err != nil { return nil, err } - return ParseRetrieveDependenciesResponse(rsp) + return ParseGetArtifactDepsResponse(rsp) } -// GetArtifactDependenciesWithResponse request returning *GetArtifactDependenciesResponse -func (c *ClientWithResponses) GetArtifactDependenciesWithResponse(ctx context.Context, artifact string, params *GetArtifactDependenciesParams, reqEditors ...RequestEditorFn) (*GetArtifactDependenciesResponse, error) { - rsp, err := c.GetArtifactDependencies(ctx, artifact, params, reqEditors...) +// GetArtifactVulnsWithResponse request returning *GetArtifactVulnsResponse +func (c *ClientWithResponses) GetArtifactVulnsWithResponse(ctx context.Context, digest string, reqEditors ...RequestEditorFn) (*GetArtifactVulnsResponse, error) { + rsp, err := c.GetArtifactVulns(ctx, digest, reqEditors...) if err != nil { return nil, err } - return ParseGetArtifactDependenciesResponse(rsp) + return ParseGetArtifactVulnsResponse(rsp) } -// GetArtifactVulnerabilitiesWithResponse request returning *GetArtifactVulnerabilitiesResponse -func (c *ClientWithResponses) GetArtifactVulnerabilitiesWithResponse(ctx context.Context, artifact string, params *GetArtifactVulnerabilitiesParams, reqEditors ...RequestEditorFn) (*GetArtifactVulnerabilitiesResponse, error) { - rsp, err := c.GetArtifactVulnerabilities(ctx, artifact, params, reqEditors...) +// GetPackagePurlsWithResponse request returning *GetPackagePurlsResponse +func (c *ClientWithResponses) GetPackagePurlsWithResponse(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*GetPackagePurlsResponse, error) { + rsp, err := c.GetPackagePurls(ctx, purl, reqEditors...) if err != nil { return nil, err } - return ParseGetArtifactVulnerabilitiesResponse(rsp) + return ParseGetPackagePurlsResponse(rsp) } -// GetPackagePurlsByPurlWithResponse request returning *GetPackagePurlsByPurlResponse -func (c *ClientWithResponses) GetPackagePurlsByPurlWithResponse(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*GetPackagePurlsByPurlResponse, error) { - rsp, err := c.GetPackagePurlsByPurl(ctx, purl, reqEditors...) +// GetPackageDepsWithResponse request returning *GetPackageDepsResponse +func (c *ClientWithResponses) GetPackageDepsWithResponse(ctx context.Context, purl string, reqEditors ...RequestEditorFn) (*GetPackageDepsResponse, error) { + rsp, err := c.GetPackageDeps(ctx, purl, reqEditors...) if err != nil { return nil, err } - return ParseGetPackagePurlsByPurlResponse(rsp) + return ParseGetPackageDepsResponse(rsp) } -// GetPackageDependenciesByPurlWithResponse request returning *GetPackageDependenciesByPurlResponse -func (c *ClientWithResponses) GetPackageDependenciesByPurlWithResponse(ctx context.Context, purl string, params *GetPackageDependenciesByPurlParams, reqEditors ...RequestEditorFn) (*GetPackageDependenciesByPurlResponse, error) { - rsp, err := c.GetPackageDependenciesByPurl(ctx, purl, params, reqEditors...) +// GetPackageVulnsWithResponse request returning *GetPackageVulnsResponse +func (c *ClientWithResponses) GetPackageVulnsWithResponse(ctx context.Context, purl string, params *GetPackageVulnsParams, reqEditors ...RequestEditorFn) (*GetPackageVulnsResponse, error) { + rsp, err := c.GetPackageVulns(ctx, purl, params, reqEditors...) if err != nil { return nil, err } - return ParseGetPackageDependenciesByPurlResponse(rsp) -} - -// GetPackageVulnerabilitiesByPurlWithResponse request returning *GetPackageVulnerabilitiesByPurlResponse -func (c *ClientWithResponses) GetPackageVulnerabilitiesByPurlWithResponse(ctx context.Context, purl string, params *GetPackageVulnerabilitiesByPurlParams, reqEditors ...RequestEditorFn) (*GetPackageVulnerabilitiesByPurlResponse, error) { - rsp, err := c.GetPackageVulnerabilitiesByPurl(ctx, purl, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetPackageVulnerabilitiesByPurlResponse(rsp) + return ParseGetPackageVulnsResponse(rsp) } // ParseAnalyzeDependenciesResponse parses an HTTP response from a AnalyzeDependenciesWithResponse call @@ -1057,15 +847,15 @@ func ParseHealthCheckResponse(rsp *http.Response) (*HealthCheckResponse, error) return response, nil } -// ParseRetrieveDependenciesResponse parses an HTTP response from a RetrieveDependenciesWithResponse call -func ParseRetrieveDependenciesResponse(rsp *http.Response) (*RetrieveDependenciesResponse, error) { +// ParseGetArtifactDepsResponse parses an HTTP response from a GetArtifactDepsWithResponse call +func ParseGetArtifactDepsResponse(rsp *http.Response) (*GetArtifactDepsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RetrieveDependenciesResponse{ + response := &GetArtifactDepsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1104,22 +894,22 @@ func ParseRetrieveDependenciesResponse(rsp *http.Response) (*RetrieveDependencie return response, nil } -// ParseGetArtifactDependenciesResponse parses an HTTP response from a GetArtifactDependenciesWithResponse call -func ParseGetArtifactDependenciesResponse(rsp *http.Response) (*GetArtifactDependenciesResponse, error) { +// ParseGetArtifactVulnsResponse parses an HTTP response from a GetArtifactVulnsWithResponse call +func ParseGetArtifactVulnsResponse(rsp *http.Response) (*GetArtifactVulnsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetArtifactDependenciesResponse{ + response := &GetArtifactVulnsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PurlList + var dest VulnerabilityList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -1139,67 +929,34 @@ func ParseGetArtifactDependenciesResponse(rsp *http.Response) (*GetArtifactDepen } response.JSON500 = &dest - } - - return response, nil -} - -// ParseGetArtifactVulnerabilitiesResponse parses an HTTP response from a GetArtifactVulnerabilitiesWithResponse call -func ParseGetArtifactVulnerabilitiesResponse(rsp *http.Response) (*GetArtifactVulnerabilitiesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetArtifactVulnerabilitiesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest VulnerabilityList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequest - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalServerError + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 502: + var dest BadGateway if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON500 = &dest + response.JSON502 = &dest } return response, nil } -// ParseGetPackagePurlsByPurlResponse parses an HTTP response from a GetPackagePurlsByPurlWithResponse call -func ParseGetPackagePurlsByPurlResponse(rsp *http.Response) (*GetPackagePurlsByPurlResponse, error) { +// ParseGetPackagePurlsResponse parses an HTTP response from a GetPackagePurlsWithResponse call +func ParseGetPackagePurlsResponse(rsp *http.Response) (*GetPackagePurlsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetPackagePurlsByPurlResponse{ + response := &GetPackagePurlsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []string + var dest PurlList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -1219,20 +976,27 @@ func ParseGetPackagePurlsByPurlResponse(rsp *http.Response) (*GetPackagePurlsByP } response.JSON500 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 502: + var dest BadGateway + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON502 = &dest + } return response, nil } -// ParseGetPackageDependenciesByPurlResponse parses an HTTP response from a GetPackageDependenciesByPurlWithResponse call -func ParseGetPackageDependenciesByPurlResponse(rsp *http.Response) (*GetPackageDependenciesByPurlResponse, error) { +// ParseGetPackageDepsResponse parses an HTTP response from a GetPackageDepsWithResponse call +func ParseGetPackageDepsResponse(rsp *http.Response) (*GetPackageDepsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetPackageDependenciesByPurlResponse{ + response := &GetPackageDepsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1259,20 +1023,27 @@ func ParseGetPackageDependenciesByPurlResponse(rsp *http.Response) (*GetPackageD } response.JSON500 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 502: + var dest BadGateway + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON502 = &dest + } return response, nil } -// ParseGetPackageVulnerabilitiesByPurlResponse parses an HTTP response from a GetPackageVulnerabilitiesByPurlWithResponse call -func ParseGetPackageVulnerabilitiesByPurlResponse(rsp *http.Response) (*GetPackageVulnerabilitiesByPurlResponse, error) { +// ParseGetPackageVulnsResponse parses an HTTP response from a GetPackageVulnsWithResponse call +func ParseGetPackageVulnsResponse(rsp *http.Response) (*GetPackageVulnsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetPackageVulnerabilitiesByPurlResponse{ + response := &GetPackageVulnsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1299,6 +1070,13 @@ func ParseGetPackageVulnerabilitiesByPurlResponse(rsp *http.Response) (*GetPacka } response.JSON500 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 502: + var dest BadGateway + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON502 = &dest + } return response, nil diff --git a/pkg/guacrest/client/models.go b/pkg/guacrest/client/models.go index c963f4ccd3..deaac689fc 100644 --- a/pkg/guacrest/client/models.go +++ b/pkg/guacrest/client/models.go @@ -13,12 +13,6 @@ const ( Scorecard AnalyzeDependenciesParamsSort = "scorecard" ) -// Defines values for RetrieveDependenciesParamsLinkCondition. -const ( - Digest RetrieveDependenciesParamsLinkCondition = "digest" - Name RetrieveDependenciesParamsLinkCondition = "name" -) - // Error defines model for Error. type Error struct { Message string `json:"Message"` @@ -53,13 +47,15 @@ type ScanMetadata struct { // Vulnerability defines model for Vulnerability. type Vulnerability struct { Metadata ScanMetadata `json:"metadata"` - Packages []string `json:"packages"` + Package string `json:"package"` Vulnerability VulnerabilityDetails `json:"vulnerability"` } // VulnerabilityDetails defines model for VulnerabilityDetails. type VulnerabilityDetails struct { - Type *string `json:"type,omitempty"` + Type *string `json:"type,omitempty"` + + // VulnerabilityIDs A list of vulnerability identifiers. These can be CVE IDs or other formats used to identify vulnerabilities. VulnerabilityIDs []string `json:"vulnerabilityIDs"` } @@ -107,46 +103,8 @@ type AnalyzeDependenciesParams struct { // AnalyzeDependenciesParamsSort defines parameters for AnalyzeDependencies. type AnalyzeDependenciesParamsSort string -// RetrieveDependenciesParams defines parameters for RetrieveDependencies. -type RetrieveDependenciesParams struct { - // PaginationSpec The pagination configuration for the query. - // * 'PageSize' specifies the number of results returned - // * 'Cursor' is returned by previous calls and specifies what page to return - PaginationSpec *PaginationSpec `form:"paginationSpec,omitempty" json:"paginationSpec,omitempty"` - - // LinkCondition Whether links between nouns must be made by digest or if they can be made just by name (i.e. purl). Specify 'name' to allow using SBOMs that don't provide the digest of the subject. The default is 'digest'. To search by purl, 'name' must be specified. - LinkCondition *RetrieveDependenciesParamsLinkCondition `form:"linkCondition,omitempty" json:"linkCondition,omitempty"` - - // Purl The purl of the dependent package. - Purl *string `form:"purl,omitempty" json:"purl,omitempty"` - - // Digest The digest of the dependent package. - Digest *string `form:"digest,omitempty" json:"digest,omitempty"` -} - -// RetrieveDependenciesParamsLinkCondition defines parameters for RetrieveDependencies. -type RetrieveDependenciesParamsLinkCondition string - -// GetArtifactDependenciesParams defines parameters for GetArtifactDependencies. -type GetArtifactDependenciesParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` -} - -// GetArtifactVulnerabilitiesParams defines parameters for GetArtifactVulnerabilities. -type GetArtifactVulnerabilitiesParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` -} - -// GetPackageDependenciesByPurlParams defines parameters for GetPackageDependenciesByPurl. -type GetPackageDependenciesByPurlParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` -} - -// GetPackageVulnerabilitiesByPurlParams defines parameters for GetPackageVulnerabilitiesByPurl. -type GetPackageVulnerabilitiesByPurlParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` +// GetPackageVulnsParams defines parameters for GetPackageVulns. +type GetPackageVulnsParams struct { + // IncludeDependencies A flag to include vulnerabilities of the dependencies. If true, the response will include vulnerabilities for the purl and its dependencies instead of the vulnerabilities of just the purl. + IncludeDependencies *bool `form:"includeDependencies,omitempty" json:"includeDependencies,omitempty"` } diff --git a/pkg/guacrest/generated/models.go b/pkg/guacrest/generated/models.go index 84a86ed17d..cbab6ae21b 100644 --- a/pkg/guacrest/generated/models.go +++ b/pkg/guacrest/generated/models.go @@ -13,12 +13,6 @@ const ( Scorecard AnalyzeDependenciesParamsSort = "scorecard" ) -// Defines values for RetrieveDependenciesParamsLinkCondition. -const ( - Digest RetrieveDependenciesParamsLinkCondition = "digest" - Name RetrieveDependenciesParamsLinkCondition = "name" -) - // Error defines model for Error. type Error struct { Message string `json:"Message"` @@ -53,13 +47,15 @@ type ScanMetadata struct { // Vulnerability defines model for Vulnerability. type Vulnerability struct { Metadata ScanMetadata `json:"metadata"` - Packages []string `json:"packages"` + Package string `json:"package"` Vulnerability VulnerabilityDetails `json:"vulnerability"` } // VulnerabilityDetails defines model for VulnerabilityDetails. type VulnerabilityDetails struct { - Type *string `json:"type,omitempty"` + Type *string `json:"type,omitempty"` + + // VulnerabilityIDs A list of vulnerability identifiers. These can be CVE IDs or other formats used to identify vulnerabilities. VulnerabilityIDs []string `json:"vulnerabilityIDs"` } @@ -107,46 +103,8 @@ type AnalyzeDependenciesParams struct { // AnalyzeDependenciesParamsSort defines parameters for AnalyzeDependencies. type AnalyzeDependenciesParamsSort string -// RetrieveDependenciesParams defines parameters for RetrieveDependencies. -type RetrieveDependenciesParams struct { - // PaginationSpec The pagination configuration for the query. - // * 'PageSize' specifies the number of results returned - // * 'Cursor' is returned by previous calls and specifies what page to return - PaginationSpec *PaginationSpec `form:"paginationSpec,omitempty" json:"paginationSpec,omitempty"` - - // LinkCondition Whether links between nouns must be made by digest or if they can be made just by name (i.e. purl). Specify 'name' to allow using SBOMs that don't provide the digest of the subject. The default is 'digest'. To search by purl, 'name' must be specified. - LinkCondition *RetrieveDependenciesParamsLinkCondition `form:"linkCondition,omitempty" json:"linkCondition,omitempty"` - - // Purl The purl of the dependent package. - Purl *string `form:"purl,omitempty" json:"purl,omitempty"` - - // Digest The digest of the dependent package. - Digest *string `form:"digest,omitempty" json:"digest,omitempty"` -} - -// RetrieveDependenciesParamsLinkCondition defines parameters for RetrieveDependencies. -type RetrieveDependenciesParamsLinkCondition string - -// GetArtifactDependenciesParams defines parameters for GetArtifactDependencies. -type GetArtifactDependenciesParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` -} - -// GetArtifactVulnerabilitiesParams defines parameters for GetArtifactVulnerabilities. -type GetArtifactVulnerabilitiesParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` -} - -// GetPackageDependenciesByPurlParams defines parameters for GetPackageDependenciesByPurl. -type GetPackageDependenciesByPurlParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` -} - -// GetPackageVulnerabilitiesByPurlParams defines parameters for GetPackageVulnerabilitiesByPurl. -type GetPackageVulnerabilitiesByPurlParams struct { - // LatestSBOM Whether the query should search in the latest sbom - LatestSBOM *bool `form:"latestSBOM,omitempty" json:"latestSBOM,omitempty"` +// GetPackageVulnsParams defines parameters for GetPackageVulns. +type GetPackageVulnsParams struct { + // IncludeDependencies A flag to include vulnerabilities of the dependencies. If true, the response will include vulnerabilities for the purl and its dependencies instead of the vulnerabilities of just the purl. + IncludeDependencies *bool `form:"includeDependencies,omitempty" json:"includeDependencies,omitempty"` } diff --git a/pkg/guacrest/generated/server.go b/pkg/guacrest/generated/server.go index b701ebef72..31c947d551 100644 --- a/pkg/guacrest/generated/server.go +++ b/pkg/guacrest/generated/server.go @@ -22,24 +22,21 @@ type ServerInterface interface { // Health check the server // (GET /healthz) HealthCheck(w http.ResponseWriter, r *http.Request) - // Retrieve transitive dependencies - // (GET /query/dependencies) - RetrieveDependencies(w http.ResponseWriter, r *http.Request, params RetrieveDependenciesParams) - // Get dependencies for a specific Artifact (:) - // (GET /v0/artifact/{artifact}/dependencies) - GetArtifactDependencies(w http.ResponseWriter, r *http.Request, artifact string, params GetArtifactDependenciesParams) - // Get vulnerabilities for a specific Artifact (:) - // (GET /v0/artifact/{artifact}/vulns) - GetArtifactVulnerabilities(w http.ResponseWriter, r *http.Request, artifact string, params GetArtifactVulnerabilitiesParams) - // Get purls related to the specific Package URL (PURL) + // Get dependencies for a specific digest + // (GET /v0/artifact/{digest}/dependencies) + GetArtifactDeps(w http.ResponseWriter, r *http.Request, digest string) + // Get vulnerabilities for a specific digest + // (GET /v0/artifact/{digest}/vulns) + GetArtifactVulns(w http.ResponseWriter, r *http.Request, digest string) + // Get all purls related to the given purl // (GET /v0/package/{purl}) - GetPackagePurlsByPurl(w http.ResponseWriter, r *http.Request, purl string) - // Get dependencies for a specific Package URL (PURL) + GetPackagePurls(w http.ResponseWriter, r *http.Request, purl string) + // Get dependencies for a specific Package URL (purl) // (GET /v0/package/{purl}/dependencies) - GetPackageDependenciesByPurl(w http.ResponseWriter, r *http.Request, purl string, params GetPackageDependenciesByPurlParams) - // Get vulnerabilities for a specific Package URL (PURL) + GetPackageDeps(w http.ResponseWriter, r *http.Request, purl string) + // Get vulnerabilities for a Package URL (purl) // (GET /v0/package/{purl}/vulns) - GetPackageVulnerabilitiesByPurl(w http.ResponseWriter, r *http.Request, purl string, params GetPackageVulnerabilitiesByPurlParams) + GetPackageVulns(w http.ResponseWriter, r *http.Request, purl string, params GetPackageVulnsParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -58,39 +55,33 @@ func (_ Unimplemented) HealthCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Retrieve transitive dependencies -// (GET /query/dependencies) -func (_ Unimplemented) RetrieveDependencies(w http.ResponseWriter, r *http.Request, params RetrieveDependenciesParams) { +// Get dependencies for a specific digest +// (GET /v0/artifact/{digest}/dependencies) +func (_ Unimplemented) GetArtifactDeps(w http.ResponseWriter, r *http.Request, digest string) { w.WriteHeader(http.StatusNotImplemented) } -// Get dependencies for a specific Artifact (:) -// (GET /v0/artifact/{artifact}/dependencies) -func (_ Unimplemented) GetArtifactDependencies(w http.ResponseWriter, r *http.Request, artifact string, params GetArtifactDependenciesParams) { +// Get vulnerabilities for a specific digest +// (GET /v0/artifact/{digest}/vulns) +func (_ Unimplemented) GetArtifactVulns(w http.ResponseWriter, r *http.Request, digest string) { w.WriteHeader(http.StatusNotImplemented) } -// Get vulnerabilities for a specific Artifact (:) -// (GET /v0/artifact/{artifact}/vulns) -func (_ Unimplemented) GetArtifactVulnerabilities(w http.ResponseWriter, r *http.Request, artifact string, params GetArtifactVulnerabilitiesParams) { - w.WriteHeader(http.StatusNotImplemented) -} - -// Get purls related to the specific Package URL (PURL) +// Get all purls related to the given purl // (GET /v0/package/{purl}) -func (_ Unimplemented) GetPackagePurlsByPurl(w http.ResponseWriter, r *http.Request, purl string) { +func (_ Unimplemented) GetPackagePurls(w http.ResponseWriter, r *http.Request, purl string) { w.WriteHeader(http.StatusNotImplemented) } -// Get dependencies for a specific Package URL (PURL) +// Get dependencies for a specific Package URL (purl) // (GET /v0/package/{purl}/dependencies) -func (_ Unimplemented) GetPackageDependenciesByPurl(w http.ResponseWriter, r *http.Request, purl string, params GetPackageDependenciesByPurlParams) { +func (_ Unimplemented) GetPackageDeps(w http.ResponseWriter, r *http.Request, purl string) { w.WriteHeader(http.StatusNotImplemented) } -// Get vulnerabilities for a specific Package URL (PURL) +// Get vulnerabilities for a Package URL (purl) // (GET /v0/package/{purl}/vulns) -func (_ Unimplemented) GetPackageVulnerabilitiesByPurl(w http.ResponseWriter, r *http.Request, purl string, params GetPackageVulnerabilitiesByPurlParams) { +func (_ Unimplemented) GetPackageVulns(w http.ResponseWriter, r *http.Request, purl string, params GetPackageVulnsParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -159,48 +150,22 @@ func (siw *ServerInterfaceWrapper) HealthCheck(w http.ResponseWriter, r *http.Re handler.ServeHTTP(w, r) } -// RetrieveDependencies operation middleware -func (siw *ServerInterfaceWrapper) RetrieveDependencies(w http.ResponseWriter, r *http.Request) { +// GetArtifactDeps operation middleware +func (siw *ServerInterfaceWrapper) GetArtifactDeps(w http.ResponseWriter, r *http.Request) { var err error - // Parameter object where we will unmarshal all parameters from the context - var params RetrieveDependenciesParams - - // ------------- Optional query parameter "paginationSpec" ------------- - - err = runtime.BindQueryParameter("form", true, false, "paginationSpec", r.URL.Query(), ¶ms.PaginationSpec) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "paginationSpec", Err: err}) - return - } - - // ------------- Optional query parameter "linkCondition" ------------- - - err = runtime.BindQueryParameter("form", true, false, "linkCondition", r.URL.Query(), ¶ms.LinkCondition) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "linkCondition", Err: err}) - return - } - - // ------------- Optional query parameter "purl" ------------- + // ------------- Path parameter "digest" ------------- + var digest string - err = runtime.BindQueryParameter("form", true, false, "purl", r.URL.Query(), ¶ms.Purl) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "purl", Err: err}) - return - } - - // ------------- Optional query parameter "digest" ------------- - - err = runtime.BindQueryParameter("form", true, false, "digest", r.URL.Query(), ¶ms.Digest) + err = runtime.BindStyledParameterWithOptions("simple", "digest", chi.URLParam(r, "digest"), &digest, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "digest", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.RetrieveDependencies(w, r, params) + siw.Handler.GetArtifactDeps(w, r, digest) })) for _, middleware := range siw.HandlerMiddlewares { @@ -210,69 +175,22 @@ func (siw *ServerInterfaceWrapper) RetrieveDependencies(w http.ResponseWriter, r handler.ServeHTTP(w, r) } -// GetArtifactDependencies operation middleware -func (siw *ServerInterfaceWrapper) GetArtifactDependencies(w http.ResponseWriter, r *http.Request) { +// GetArtifactVulns operation middleware +func (siw *ServerInterfaceWrapper) GetArtifactVulns(w http.ResponseWriter, r *http.Request) { var err error - // ------------- Path parameter "artifact" ------------- - var artifact string + // ------------- Path parameter "digest" ------------- + var digest string - err = runtime.BindStyledParameterWithOptions("simple", "artifact", chi.URLParam(r, "artifact"), &artifact, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "digest", chi.URLParam(r, "digest"), &digest, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "artifact", Err: err}) - return - } - - // Parameter object where we will unmarshal all parameters from the context - var params GetArtifactDependenciesParams - - // ------------- Optional query parameter "latestSBOM" ------------- - - err = runtime.BindQueryParameter("form", true, false, "latestSBOM", r.URL.Query(), ¶ms.LatestSBOM) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "latestSBOM", Err: err}) - return - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetArtifactDependencies(w, r, artifact, params) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// GetArtifactVulnerabilities operation middleware -func (siw *ServerInterfaceWrapper) GetArtifactVulnerabilities(w http.ResponseWriter, r *http.Request) { - - var err error - - // ------------- Path parameter "artifact" ------------- - var artifact string - - err = runtime.BindStyledParameterWithOptions("simple", "artifact", chi.URLParam(r, "artifact"), &artifact, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "artifact", Err: err}) - return - } - - // Parameter object where we will unmarshal all parameters from the context - var params GetArtifactVulnerabilitiesParams - - // ------------- Optional query parameter "latestSBOM" ------------- - - err = runtime.BindQueryParameter("form", true, false, "latestSBOM", r.URL.Query(), ¶ms.LatestSBOM) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "latestSBOM", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "digest", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetArtifactVulnerabilities(w, r, artifact, params) + siw.Handler.GetArtifactVulns(w, r, digest) })) for _, middleware := range siw.HandlerMiddlewares { @@ -282,8 +200,8 @@ func (siw *ServerInterfaceWrapper) GetArtifactVulnerabilities(w http.ResponseWri handler.ServeHTTP(w, r) } -// GetPackagePurlsByPurl operation middleware -func (siw *ServerInterfaceWrapper) GetPackagePurlsByPurl(w http.ResponseWriter, r *http.Request) { +// GetPackagePurls operation middleware +func (siw *ServerInterfaceWrapper) GetPackagePurls(w http.ResponseWriter, r *http.Request) { var err error @@ -297,7 +215,7 @@ func (siw *ServerInterfaceWrapper) GetPackagePurlsByPurl(w http.ResponseWriter, } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetPackagePurlsByPurl(w, r, purl) + siw.Handler.GetPackagePurls(w, r, purl) })) for _, middleware := range siw.HandlerMiddlewares { @@ -307,8 +225,8 @@ func (siw *ServerInterfaceWrapper) GetPackagePurlsByPurl(w http.ResponseWriter, handler.ServeHTTP(w, r) } -// GetPackageDependenciesByPurl operation middleware -func (siw *ServerInterfaceWrapper) GetPackageDependenciesByPurl(w http.ResponseWriter, r *http.Request) { +// GetPackageDeps operation middleware +func (siw *ServerInterfaceWrapper) GetPackageDeps(w http.ResponseWriter, r *http.Request) { var err error @@ -321,19 +239,8 @@ func (siw *ServerInterfaceWrapper) GetPackageDependenciesByPurl(w http.ResponseW return } - // Parameter object where we will unmarshal all parameters from the context - var params GetPackageDependenciesByPurlParams - - // ------------- Optional query parameter "latestSBOM" ------------- - - err = runtime.BindQueryParameter("form", true, false, "latestSBOM", r.URL.Query(), ¶ms.LatestSBOM) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "latestSBOM", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetPackageDependenciesByPurl(w, r, purl, params) + siw.Handler.GetPackageDeps(w, r, purl) })) for _, middleware := range siw.HandlerMiddlewares { @@ -343,8 +250,8 @@ func (siw *ServerInterfaceWrapper) GetPackageDependenciesByPurl(w http.ResponseW handler.ServeHTTP(w, r) } -// GetPackageVulnerabilitiesByPurl operation middleware -func (siw *ServerInterfaceWrapper) GetPackageVulnerabilitiesByPurl(w http.ResponseWriter, r *http.Request) { +// GetPackageVulns operation middleware +func (siw *ServerInterfaceWrapper) GetPackageVulns(w http.ResponseWriter, r *http.Request) { var err error @@ -358,18 +265,18 @@ func (siw *ServerInterfaceWrapper) GetPackageVulnerabilitiesByPurl(w http.Respon } // Parameter object where we will unmarshal all parameters from the context - var params GetPackageVulnerabilitiesByPurlParams + var params GetPackageVulnsParams - // ------------- Optional query parameter "latestSBOM" ------------- + // ------------- Optional query parameter "includeDependencies" ------------- - err = runtime.BindQueryParameter("form", true, false, "latestSBOM", r.URL.Query(), ¶ms.LatestSBOM) + err = runtime.BindQueryParameter("form", true, false, "includeDependencies", r.URL.Query(), ¶ms.IncludeDependencies) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "latestSBOM", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "includeDependencies", Err: err}) return } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetPackageVulnerabilitiesByPurl(w, r, purl, params) + siw.Handler.GetPackageVulns(w, r, purl, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -499,22 +406,19 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/healthz", wrapper.HealthCheck) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/query/dependencies", wrapper.RetrieveDependencies) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v0/artifact/{artifact}/dependencies", wrapper.GetArtifactDependencies) + r.Get(options.BaseURL+"/v0/artifact/{digest}/dependencies", wrapper.GetArtifactDeps) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v0/artifact/{artifact}/vulns", wrapper.GetArtifactVulnerabilities) + r.Get(options.BaseURL+"/v0/artifact/{digest}/vulns", wrapper.GetArtifactVulns) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v0/package/{purl}", wrapper.GetPackagePurlsByPurl) + r.Get(options.BaseURL+"/v0/package/{purl}", wrapper.GetPackagePurls) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v0/package/{purl}/dependencies", wrapper.GetPackageDependenciesByPurl) + r.Get(options.BaseURL+"/v0/package/{purl}/dependencies", wrapper.GetPackageDeps) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/v0/package/{purl}/vulns", wrapper.GetPackageVulnerabilitiesByPurl) + r.Get(options.BaseURL+"/v0/package/{purl}/vulns", wrapper.GetPackageVulns) }) return r @@ -598,241 +502,237 @@ func (response HealthCheck200JSONResponse) VisitHealthCheckResponse(w http.Respo return json.NewEncoder(w).Encode(response) } -type RetrieveDependenciesRequestObject struct { - Params RetrieveDependenciesParams +type GetArtifactDepsRequestObject struct { + Digest string `json:"digest"` } -type RetrieveDependenciesResponseObject interface { - VisitRetrieveDependenciesResponse(w http.ResponseWriter) error +type GetArtifactDepsResponseObject interface { + VisitGetArtifactDepsResponse(w http.ResponseWriter) error } -type RetrieveDependencies200JSONResponse struct{ PurlListJSONResponse } +type GetArtifactDeps200JSONResponse struct{ PurlListJSONResponse } -func (response RetrieveDependencies200JSONResponse) VisitRetrieveDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactDeps200JSONResponse) VisitGetArtifactDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type RetrieveDependencies400JSONResponse struct{ BadRequestJSONResponse } +type GetArtifactDeps400JSONResponse struct{ BadRequestJSONResponse } -func (response RetrieveDependencies400JSONResponse) VisitRetrieveDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactDeps400JSONResponse) VisitGetArtifactDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type RetrieveDependencies500JSONResponse struct { +type GetArtifactDeps500JSONResponse struct { InternalServerErrorJSONResponse } -func (response RetrieveDependencies500JSONResponse) VisitRetrieveDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactDeps500JSONResponse) VisitGetArtifactDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type RetrieveDependencies502JSONResponse struct{ BadGatewayJSONResponse } +type GetArtifactDeps502JSONResponse struct{ BadGatewayJSONResponse } -func (response RetrieveDependencies502JSONResponse) VisitRetrieveDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactDeps502JSONResponse) VisitGetArtifactDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(502) return json.NewEncoder(w).Encode(response) } -type GetArtifactDependenciesRequestObject struct { - Artifact string `json:"artifact"` - Params GetArtifactDependenciesParams +type GetArtifactVulnsRequestObject struct { + Digest string `json:"digest"` } -type GetArtifactDependenciesResponseObject interface { - VisitGetArtifactDependenciesResponse(w http.ResponseWriter) error +type GetArtifactVulnsResponseObject interface { + VisitGetArtifactVulnsResponse(w http.ResponseWriter) error } -type GetArtifactDependencies200JSONResponse struct{ PurlListJSONResponse } +type GetArtifactVulns200JSONResponse struct{ VulnerabilityListJSONResponse } -func (response GetArtifactDependencies200JSONResponse) VisitGetArtifactDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactVulns200JSONResponse) VisitGetArtifactVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetArtifactDependencies400JSONResponse struct{ BadRequestJSONResponse } +type GetArtifactVulns400JSONResponse struct{ BadRequestJSONResponse } -func (response GetArtifactDependencies400JSONResponse) VisitGetArtifactDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactVulns400JSONResponse) VisitGetArtifactVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type GetArtifactDependencies500JSONResponse struct { +type GetArtifactVulns500JSONResponse struct { InternalServerErrorJSONResponse } -func (response GetArtifactDependencies500JSONResponse) VisitGetArtifactDependenciesResponse(w http.ResponseWriter) error { +func (response GetArtifactVulns500JSONResponse) VisitGetArtifactVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type GetArtifactVulnerabilitiesRequestObject struct { - Artifact string `json:"artifact"` - Params GetArtifactVulnerabilitiesParams -} +type GetArtifactVulns502JSONResponse struct{ BadGatewayJSONResponse } -type GetArtifactVulnerabilitiesResponseObject interface { - VisitGetArtifactVulnerabilitiesResponse(w http.ResponseWriter) error -} - -type GetArtifactVulnerabilities200JSONResponse struct{ VulnerabilityListJSONResponse } - -func (response GetArtifactVulnerabilities200JSONResponse) VisitGetArtifactVulnerabilitiesResponse(w http.ResponseWriter) error { +func (response GetArtifactVulns502JSONResponse) VisitGetArtifactVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type GetArtifactVulnerabilities400JSONResponse struct{ BadRequestJSONResponse } - -func (response GetArtifactVulnerabilities400JSONResponse) VisitGetArtifactVulnerabilitiesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type GetArtifactVulnerabilities500JSONResponse struct { - InternalServerErrorJSONResponse -} - -func (response GetArtifactVulnerabilities500JSONResponse) VisitGetArtifactVulnerabilitiesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) + w.WriteHeader(502) return json.NewEncoder(w).Encode(response) } -type GetPackagePurlsByPurlRequestObject struct { +type GetPackagePurlsRequestObject struct { Purl string `json:"purl"` } -type GetPackagePurlsByPurlResponseObject interface { - VisitGetPackagePurlsByPurlResponse(w http.ResponseWriter) error +type GetPackagePurlsResponseObject interface { + VisitGetPackagePurlsResponse(w http.ResponseWriter) error } -type GetPackagePurlsByPurl200JSONResponse []string +type GetPackagePurls200JSONResponse struct{ PurlListJSONResponse } -func (response GetPackagePurlsByPurl200JSONResponse) VisitGetPackagePurlsByPurlResponse(w http.ResponseWriter) error { +func (response GetPackagePurls200JSONResponse) VisitGetPackagePurlsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetPackagePurlsByPurl400JSONResponse struct{ BadRequestJSONResponse } +type GetPackagePurls400JSONResponse struct{ BadRequestJSONResponse } -func (response GetPackagePurlsByPurl400JSONResponse) VisitGetPackagePurlsByPurlResponse(w http.ResponseWriter) error { +func (response GetPackagePurls400JSONResponse) VisitGetPackagePurlsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type GetPackagePurlsByPurl500JSONResponse struct { +type GetPackagePurls500JSONResponse struct { InternalServerErrorJSONResponse } -func (response GetPackagePurlsByPurl500JSONResponse) VisitGetPackagePurlsByPurlResponse(w http.ResponseWriter) error { +func (response GetPackagePurls500JSONResponse) VisitGetPackagePurlsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type GetPackageDependenciesByPurlRequestObject struct { - Purl string `json:"purl"` - Params GetPackageDependenciesByPurlParams +type GetPackagePurls502JSONResponse struct{ BadGatewayJSONResponse } + +func (response GetPackagePurls502JSONResponse) VisitGetPackagePurlsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(502) + + return json.NewEncoder(w).Encode(response) +} + +type GetPackageDepsRequestObject struct { + Purl string `json:"purl"` } -type GetPackageDependenciesByPurlResponseObject interface { - VisitGetPackageDependenciesByPurlResponse(w http.ResponseWriter) error +type GetPackageDepsResponseObject interface { + VisitGetPackageDepsResponse(w http.ResponseWriter) error } -type GetPackageDependenciesByPurl200JSONResponse struct{ PurlListJSONResponse } +type GetPackageDeps200JSONResponse struct{ PurlListJSONResponse } -func (response GetPackageDependenciesByPurl200JSONResponse) VisitGetPackageDependenciesByPurlResponse(w http.ResponseWriter) error { +func (response GetPackageDeps200JSONResponse) VisitGetPackageDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetPackageDependenciesByPurl400JSONResponse struct{ BadRequestJSONResponse } +type GetPackageDeps400JSONResponse struct{ BadRequestJSONResponse } -func (response GetPackageDependenciesByPurl400JSONResponse) VisitGetPackageDependenciesByPurlResponse(w http.ResponseWriter) error { +func (response GetPackageDeps400JSONResponse) VisitGetPackageDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type GetPackageDependenciesByPurl500JSONResponse struct { +type GetPackageDeps500JSONResponse struct { InternalServerErrorJSONResponse } -func (response GetPackageDependenciesByPurl500JSONResponse) VisitGetPackageDependenciesByPurlResponse(w http.ResponseWriter) error { +func (response GetPackageDeps500JSONResponse) VisitGetPackageDepsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type GetPackageVulnerabilitiesByPurlRequestObject struct { +type GetPackageDeps502JSONResponse struct{ BadGatewayJSONResponse } + +func (response GetPackageDeps502JSONResponse) VisitGetPackageDepsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(502) + + return json.NewEncoder(w).Encode(response) +} + +type GetPackageVulnsRequestObject struct { Purl string `json:"purl"` - Params GetPackageVulnerabilitiesByPurlParams + Params GetPackageVulnsParams } -type GetPackageVulnerabilitiesByPurlResponseObject interface { - VisitGetPackageVulnerabilitiesByPurlResponse(w http.ResponseWriter) error +type GetPackageVulnsResponseObject interface { + VisitGetPackageVulnsResponse(w http.ResponseWriter) error } -type GetPackageVulnerabilitiesByPurl200JSONResponse struct{ VulnerabilityListJSONResponse } +type GetPackageVulns200JSONResponse struct{ VulnerabilityListJSONResponse } -func (response GetPackageVulnerabilitiesByPurl200JSONResponse) VisitGetPackageVulnerabilitiesByPurlResponse(w http.ResponseWriter) error { +func (response GetPackageVulns200JSONResponse) VisitGetPackageVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetPackageVulnerabilitiesByPurl400JSONResponse struct{ BadRequestJSONResponse } +type GetPackageVulns400JSONResponse struct{ BadRequestJSONResponse } -func (response GetPackageVulnerabilitiesByPurl400JSONResponse) VisitGetPackageVulnerabilitiesByPurlResponse(w http.ResponseWriter) error { +func (response GetPackageVulns400JSONResponse) VisitGetPackageVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type GetPackageVulnerabilitiesByPurl500JSONResponse struct { +type GetPackageVulns500JSONResponse struct { InternalServerErrorJSONResponse } -func (response GetPackageVulnerabilitiesByPurl500JSONResponse) VisitGetPackageVulnerabilitiesByPurlResponse(w http.ResponseWriter) error { +func (response GetPackageVulns500JSONResponse) VisitGetPackageVulnsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } +type GetPackageVulns502JSONResponse struct{ BadGatewayJSONResponse } + +func (response GetPackageVulns502JSONResponse) VisitGetPackageVulnsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(502) + + return json.NewEncoder(w).Encode(response) +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { // Identify the most important dependencies @@ -841,24 +741,21 @@ type StrictServerInterface interface { // Health check the server // (GET /healthz) HealthCheck(ctx context.Context, request HealthCheckRequestObject) (HealthCheckResponseObject, error) - // Retrieve transitive dependencies - // (GET /query/dependencies) - RetrieveDependencies(ctx context.Context, request RetrieveDependenciesRequestObject) (RetrieveDependenciesResponseObject, error) - // Get dependencies for a specific Artifact (:) - // (GET /v0/artifact/{artifact}/dependencies) - GetArtifactDependencies(ctx context.Context, request GetArtifactDependenciesRequestObject) (GetArtifactDependenciesResponseObject, error) - // Get vulnerabilities for a specific Artifact (:) - // (GET /v0/artifact/{artifact}/vulns) - GetArtifactVulnerabilities(ctx context.Context, request GetArtifactVulnerabilitiesRequestObject) (GetArtifactVulnerabilitiesResponseObject, error) - // Get purls related to the specific Package URL (PURL) + // Get dependencies for a specific digest + // (GET /v0/artifact/{digest}/dependencies) + GetArtifactDeps(ctx context.Context, request GetArtifactDepsRequestObject) (GetArtifactDepsResponseObject, error) + // Get vulnerabilities for a specific digest + // (GET /v0/artifact/{digest}/vulns) + GetArtifactVulns(ctx context.Context, request GetArtifactVulnsRequestObject) (GetArtifactVulnsResponseObject, error) + // Get all purls related to the given purl // (GET /v0/package/{purl}) - GetPackagePurlsByPurl(ctx context.Context, request GetPackagePurlsByPurlRequestObject) (GetPackagePurlsByPurlResponseObject, error) - // Get dependencies for a specific Package URL (PURL) + GetPackagePurls(ctx context.Context, request GetPackagePurlsRequestObject) (GetPackagePurlsResponseObject, error) + // Get dependencies for a specific Package URL (purl) // (GET /v0/package/{purl}/dependencies) - GetPackageDependenciesByPurl(ctx context.Context, request GetPackageDependenciesByPurlRequestObject) (GetPackageDependenciesByPurlResponseObject, error) - // Get vulnerabilities for a specific Package URL (PURL) + GetPackageDeps(ctx context.Context, request GetPackageDepsRequestObject) (GetPackageDepsResponseObject, error) + // Get vulnerabilities for a Package URL (purl) // (GET /v0/package/{purl}/vulns) - GetPackageVulnerabilitiesByPurl(ctx context.Context, request GetPackageVulnerabilitiesByPurlRequestObject) (GetPackageVulnerabilitiesByPurlResponseObject, error) + GetPackageVulns(ctx context.Context, request GetPackageVulnsRequestObject) (GetPackageVulnsResponseObject, error) } type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc @@ -940,25 +837,25 @@ func (sh *strictHandler) HealthCheck(w http.ResponseWriter, r *http.Request) { } } -// RetrieveDependencies operation middleware -func (sh *strictHandler) RetrieveDependencies(w http.ResponseWriter, r *http.Request, params RetrieveDependenciesParams) { - var request RetrieveDependenciesRequestObject +// GetArtifactDeps operation middleware +func (sh *strictHandler) GetArtifactDeps(w http.ResponseWriter, r *http.Request, digest string) { + var request GetArtifactDepsRequestObject - request.Params = params + request.Digest = digest handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.RetrieveDependencies(ctx, request.(RetrieveDependenciesRequestObject)) + return sh.ssi.GetArtifactDeps(ctx, request.(GetArtifactDepsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "RetrieveDependencies") + handler = middleware(handler, "GetArtifactDeps") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(RetrieveDependenciesResponseObject); ok { - if err := validResponse.VisitRetrieveDependenciesResponse(w); err != nil { + } else if validResponse, ok := response.(GetArtifactDepsResponseObject); ok { + if err := validResponse.VisitGetArtifactDepsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -966,26 +863,25 @@ func (sh *strictHandler) RetrieveDependencies(w http.ResponseWriter, r *http.Req } } -// GetArtifactDependencies operation middleware -func (sh *strictHandler) GetArtifactDependencies(w http.ResponseWriter, r *http.Request, artifact string, params GetArtifactDependenciesParams) { - var request GetArtifactDependenciesRequestObject +// GetArtifactVulns operation middleware +func (sh *strictHandler) GetArtifactVulns(w http.ResponseWriter, r *http.Request, digest string) { + var request GetArtifactVulnsRequestObject - request.Artifact = artifact - request.Params = params + request.Digest = digest handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetArtifactDependencies(ctx, request.(GetArtifactDependenciesRequestObject)) + return sh.ssi.GetArtifactVulns(ctx, request.(GetArtifactVulnsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetArtifactDependencies") + handler = middleware(handler, "GetArtifactVulns") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetArtifactDependenciesResponseObject); ok { - if err := validResponse.VisitGetArtifactDependenciesResponse(w); err != nil { + } else if validResponse, ok := response.(GetArtifactVulnsResponseObject); ok { + if err := validResponse.VisitGetArtifactVulnsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -993,52 +889,25 @@ func (sh *strictHandler) GetArtifactDependencies(w http.ResponseWriter, r *http. } } -// GetArtifactVulnerabilities operation middleware -func (sh *strictHandler) GetArtifactVulnerabilities(w http.ResponseWriter, r *http.Request, artifact string, params GetArtifactVulnerabilitiesParams) { - var request GetArtifactVulnerabilitiesRequestObject - - request.Artifact = artifact - request.Params = params - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetArtifactVulnerabilities(ctx, request.(GetArtifactVulnerabilitiesRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetArtifactVulnerabilities") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetArtifactVulnerabilitiesResponseObject); ok { - if err := validResponse.VisitGetArtifactVulnerabilitiesResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// GetPackagePurlsByPurl operation middleware -func (sh *strictHandler) GetPackagePurlsByPurl(w http.ResponseWriter, r *http.Request, purl string) { - var request GetPackagePurlsByPurlRequestObject +// GetPackagePurls operation middleware +func (sh *strictHandler) GetPackagePurls(w http.ResponseWriter, r *http.Request, purl string) { + var request GetPackagePurlsRequestObject request.Purl = purl handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetPackagePurlsByPurl(ctx, request.(GetPackagePurlsByPurlRequestObject)) + return sh.ssi.GetPackagePurls(ctx, request.(GetPackagePurlsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetPackagePurlsByPurl") + handler = middleware(handler, "GetPackagePurls") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetPackagePurlsByPurlResponseObject); ok { - if err := validResponse.VisitGetPackagePurlsByPurlResponse(w); err != nil { + } else if validResponse, ok := response.(GetPackagePurlsResponseObject); ok { + if err := validResponse.VisitGetPackagePurlsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -1046,26 +915,25 @@ func (sh *strictHandler) GetPackagePurlsByPurl(w http.ResponseWriter, r *http.Re } } -// GetPackageDependenciesByPurl operation middleware -func (sh *strictHandler) GetPackageDependenciesByPurl(w http.ResponseWriter, r *http.Request, purl string, params GetPackageDependenciesByPurlParams) { - var request GetPackageDependenciesByPurlRequestObject +// GetPackageDeps operation middleware +func (sh *strictHandler) GetPackageDeps(w http.ResponseWriter, r *http.Request, purl string) { + var request GetPackageDepsRequestObject request.Purl = purl - request.Params = params handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetPackageDependenciesByPurl(ctx, request.(GetPackageDependenciesByPurlRequestObject)) + return sh.ssi.GetPackageDeps(ctx, request.(GetPackageDepsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetPackageDependenciesByPurl") + handler = middleware(handler, "GetPackageDeps") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetPackageDependenciesByPurlResponseObject); ok { - if err := validResponse.VisitGetPackageDependenciesByPurlResponse(w); err != nil { + } else if validResponse, ok := response.(GetPackageDepsResponseObject); ok { + if err := validResponse.VisitGetPackageDepsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -1073,26 +941,26 @@ func (sh *strictHandler) GetPackageDependenciesByPurl(w http.ResponseWriter, r * } } -// GetPackageVulnerabilitiesByPurl operation middleware -func (sh *strictHandler) GetPackageVulnerabilitiesByPurl(w http.ResponseWriter, r *http.Request, purl string, params GetPackageVulnerabilitiesByPurlParams) { - var request GetPackageVulnerabilitiesByPurlRequestObject +// GetPackageVulns operation middleware +func (sh *strictHandler) GetPackageVulns(w http.ResponseWriter, r *http.Request, purl string, params GetPackageVulnsParams) { + var request GetPackageVulnsRequestObject request.Purl = purl request.Params = params handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetPackageVulnerabilitiesByPurl(ctx, request.(GetPackageVulnerabilitiesByPurlRequestObject)) + return sh.ssi.GetPackageVulns(ctx, request.(GetPackageVulnsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetPackageVulnerabilitiesByPurl") + handler = middleware(handler, "GetPackageVulns") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetPackageVulnerabilitiesByPurlResponseObject); ok { - if err := validResponse.VisitGetPackageVulnerabilitiesByPurlResponse(w); err != nil { + } else if validResponse, ok := response.(GetPackageVulnsResponseObject); ok { + if err := validResponse.VisitGetPackageVulnsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { diff --git a/pkg/guacrest/generated/spec.go b/pkg/guacrest/generated/spec.go index 2ac76f136f..ecb21c93e8 100644 --- a/pkg/guacrest/generated/spec.go +++ b/pkg/guacrest/generated/spec.go @@ -18,36 +18,34 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xYX3PbuBH/KjtoZ5R0GMlz177oLbbvcp5xLh7LTh8ufoDAlYgYBGgAlKt49N07C5AU", - "SVGy3NZzvbm8SQQW+++3uz/giQmTF0aj9o5Nn1jBLc/Row3/rvhSau6l0bMCBX1J0QkrC/rEpuwmQyia", - "PSCMXshlaeO/hbHgM4SHEu16/EUD/A1GV3yJM/kNR+AKFHIh0YVNusznaMEswKIrlXdg0ZdWY1oJnpXW", - "GTsCuV2B+RoKiytpSgeCK+WA67R18GPGPdmH4E0l9UWzhEmyPZjFEqZ5jmzKiq6rCXMiw5yHmFhToPUS", - "Q0yiIfTLrwuSdN5KvWSbhNXOtRal9rhEyzabpP5k5l9ReLahTxZdYbSLJ5/y9AP3+MjX9E8Y7VF7+smL", - "QkkRjJt8dRT5p5Z5f7W4YFP2l8k2k5O46iY/WWtsVLWbOYd2hRZQC1NqjxZT4BqQRCiVGoWXekmxowyl", - "3HOYc3GPOiVnT3l6jQ8lOv/61p7yFGxUloArRQbcwcKaHKRecSVTMBZy6RzZ24LwJmEX5JnmahacjRpe", - "3d5aKUStUG0khIh7vsRfeY6X8oWRkx5z95xJLQVsCzluLV8PGfoelHSeyq6IgkDl4OBR+oyyLi2kWKBO", - "UXsIMAlBvSqterH93TLatpYLvTDPu9XZ3TPhuMiUVg2EhGrwoZQWUzb9rW9VS83dYP3ujWZpVYjU51Jp", - "tHwulfTr10l5R8XLkr5qiVLD5M4ZIbnHtEFAAwxjgVsvFzz4XvfHYF1TVd0Uf0Tn+BIHWmUv6vXG3Rh3", - "KmZXw3mNzTOC5lDbTVgt+Tw2elYFwaSvY9jGPpa7AT8z2nOp46QTYX5UE8lKXCHkxoY5im4MFwvaZRG4", - "RdAmrCUAv+K/fJw88CiVgjmClmocxlk3JtudgzPqxniu9oZrM+QdBWfoqJng+iN6TpNhNzfCKIXC7zEj", - "nd9auWflM1onYynsrBorl3J4yQmuNdp951bLhw73MsdZ2JbS+sLYnHs2ZSn3+I4WWTKA5J2AdQtyJzB5", - "K2SHQNkJ7yZhVSG6TmPY9aFT/Alb9Y05upWco+eSulivLho7+ocnW9fungtLffhOdKLYgGMdXRfnLwpD", - "z4Odo4abuwzFrEulEmYK1LyQbMp+HJ+MT6jquM+C7gnXXK2ddJN6VIrKlyWGIiP3YndIqfvS7m943t6b", - "dHj3b8NJ2m6Z9Hj5Jhki5s5YD8amkVa3WrmrKPUiUCot1iN4Bzet9W3zz+QyQ+db9LyhA/UpThiLgtt0", - "/ynKPNIhnwrUs9nP0EjEX3spOTnA2pnztsQ2MUdd5pTPxpFA26vDW0ltivWux7h/ODnZVxLNvkmfs20S", - "9vdj5FoEeZOwfxwjMkRWg+wPR6mrbw9hPJd5zu2ayChlSy7WIRW5cR5kXhjrufbQQSyJTTLkymff9sL3", - "l7B+lqG4Z8PRPJrd9LMzQKNTkq6uiNV1RTqINvb9jJaBINNaAtGtAK19BdrV+rPUaZD3lmsnvVxhJ051", - "NUldlH4cIP8Ld7PTTx/D9ZN+X87e0820tp5GeenoehU9aU5bg0UVwuMyWQRxWr9wn4QorUUtMHyEq/vl", - "Tw8lV4OnegMLslkY7SSVOxXMiivi7HUxRqbQTeV1xT9etxX9M0MiNKCkvncwR/+IqEGbUjvIS+eJyuQ8", - "RbrNp3JJjcJYkCHGawDBdbPja9i+DncUeCPHOA5E++0YZuHOv4YRLY0oIlwp8whluBFSbijy3ENq9MhD", - "Yc1KphiTUemMSXVlGAAxrSkueKk8QQ5Gcd9oDDcGHHIrsvD+UFqV1Gprd+oXiHS8t7dRNM6MTmWI0lBT", - "i/pqiYFmNtj0yZ7al+3NrULBeN/bBzG85EBlDqrqBu5oZY1f+9X9Z226vqn90fpzXYb7+k1sYKuTSX39", - "mjzVvzbHUY4P6N9XEodrvXdPrGRAxgkiqfvqkO1IjeFLeXLyo+Bqaaz0WT6NuQ1fsU4/8aRt9psr5KG5", - "/iz86p7SPC+Cy0yp0rowKysV9UkPbm7yfWUYdlB/GALk3BiFXP/hENmB1wfsjvnwLsvrHiWgyfKbXjZj", - "Gqfxazuzbw8iksj1UVD83H18+I7G10Xj7kvU7w7L/vPT/xCZ1RCaPNFs2xyCY0XvqVjd6foqjsKDSLy9", - "vnyHWpgUU6ik4fb6Et5c3V5fvh1GWjVij0fZ3X9Jrpurcdf2QXufuz0feEGkI4bfDRsSFPb8/mALT7KR", - "b0fO3DJSDOVxGEpHT9zqxPbA/T+C1/eR+ooj9XgwPTssq6N6s/I7kP5803AQVJvNvwMAAP//CplXBsEf", - "AAA=", + "H4sIAAAAAAAC/+xZW28buRX+KwdsAbfFRFKz7YueurF3UwPZrRE7ftkE2CPyzIgxh5yQHDmKof9ekDMj", + "zVWWgTpAunmzhjzkuXzfudAPjJu8MJq0d2z5wAq0mJMnG39dYSY1emn0dUE8fBHkuJVF+MSW7GZNUOz3", + "ADc6lVlpq1+pseDXBJ9KstvZew3wNzi7woyu5Rc6A1cQl6kkFzfpMl+RBZOCJVcq78CSL60mUQuel9YZ", + "ewbysAKrLRSWNtKUDjgq5QC1aB18v0Yf9CPwppZ6r1nCZNA9qsUSpjEntmRF19SEOb6mHKNPrCnIeknR", + "J5Ui4S+/LYKk81bqjO0S1hjXWpTaU0aW7XZJ88msPhL3bBc+WXKF0a46+RWK1+jpHrfhFzfak/bhTywK", + "JXlUbv7RBc8/tNT7s6WULdmf5odIzqtVN//JWmOrq4aRc2Q3ZIE0N6X2ZEkAaqAgEkKpiXups+C7ECGB", + "HmGF/I60CMa+QvGWPpXk/PNr+woF2OqyBFzJ14AOUmtykHqDSgowFnLpXNC3BeFdwi6DZRrVdTS2uuHZ", + "9W0uhepWqDcGhPA7zOhXzOmNfKLnpKfcPaZS6wJ2gBxai9sxRX8EJZ0PtCsqQQh0cHAv/TpEXVoQVJAW", + "pD1EmESnXpVWPVn/Lo0OqeVSp+Zxszq7eyqc5pnSqhGXBA5+KqUlwZa/9bVqXfNhlL+T3iytip66LZUm", + "iyuppN8+T8g7Vzwt6JuWaEiY6JzhEj2JPQL2wDAW0HqZYrS9yY9Ruz2ruiH+hZzDjEZSZc/rzcahjzuM", + "Gd5w0WDzPEBzLO0mrJF8HBs9raJg0r9jXMc+lrsOPzfao9RVpeOxftQVyUraEOTGxjpKbgaXadhlCdAS", + "aBPXEoBf6bOvKg/cS6VgRaClmsVy1vXJYedojboxHtWku3Zj1gXnjB11zVH/Qh5DZRjGhhuliPsJNcTq", + "nZUTK7dknayoMFg1VmZyfMlx1Jrs1Ln18rHDvczpOm4TYT01NkfPlkygpxdhkSUjSB44rEvIgWPylsuO", + "gbLj3l3CaiKOar7pX3lywrggjzLkqh76m9v6ZycH/T88Zntz9sAFldhjdlxeuCGXxpPXFmSgaGj7rJvB", + "zZocAUcdeHJ++xNcXriQwEygFkAVWAelIxGIWMtu+/mwotc+CQ/xcqyUDEwZryAyZgxdKpUwU5DGQrIl", + "+2G2mC0CtdGv491z1Ki2Trp5U4957cuMIpODe6sUJIKXwu4vdNHem3Sa+9/GMXLYMu81/7tkrPt3xnow", + "VlS9e6teuLpvT2Pfpvn2DF7ATWv9UGHWMluT860ZYN9zNKc4bixxtGL6FGXuwyH/KUhfX/8Me4nqr8m+", + "PxjA2pHztqR290+6zEM894bE2aA+vBXUfUb40GvrXy4WU4zc75v3G8Ndwv5xilyrC98l7J+niIx1xFH2", + "5UnXNSNK7AHKPEe7DR1vQ6EQitw4DzIvjPWoPXQQG8Tma0Ll118m4fvvuH6+Jn7Hxr15cgvVj85Iry6C", + "dD2H1jORdFDp2Lez0gx4UK0lUJm1WcybFmn+IGRGzu9O4+tr8j/WkhdUjHC1q/NFPDupFeBGizD1+Gok", + "alRopUSQOu6tEh+8LxeLHziqzFjp1/myUjV+pYYmIfMcWFLtOMqT/w0Nmnb7W8P/a+riPL5+YPMWwaF2", + "4DRMQrk4CR+3ceMfFSDDeepbREp/7joOlrrWzR/CVLlrYaSXyFLAGGWJKg6gCRR32TI1Zr7CmNIKdKHj", + "kToCQ4P0cG9KJSCVWgCqSszB6ByYyQ3p+tzmCaZ1/r/+Plsk8f2t/fHlbFE1UQM41wXvKg7Kj6D53ds3", + "L0hzI0hALQfv3r6BvwRt/joOyLD0PV8dReEh4JZUDHb91HcI9QQGp4raI4CcwmAYaPcQbGGvQaOuERqf", + "IdtJNracxtEMfjYW6DPmhaIEZNOJtq4+woUpGvRZ0D7iiRyAm7V0QFoURmoPlcnR/v2dg+rh4xATVTnO", + "oVM6hu8U+uolf8TNE2zq1/5nplG/+vw/sWissraJVL+vwe9Sc1WKzoT8O6QKs2CWIx9zYcD73rWonIFa", + "bNyH3ex0nLQntXFfhbXJ8Ikl+sE/zdro2eiwsAQNgSrfTR3U/I8wgiuEXHrXJRVI7TyhaO4cUeVj6fz+", + "lNnkpD8S8c6/+QSlWCrPlikqR/u5fmWMItTfG9Zhwzqa4Xa7/wYAAP//vcB68FQeAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/guacrest/helpers/package.go b/pkg/guacrest/helpers/package.go index 40162f66d7..38ca68db64 100644 --- a/pkg/guacrest/helpers/package.go +++ b/pkg/guacrest/helpers/package.go @@ -72,25 +72,7 @@ func FindPackageWithPurl(ctx context.Context, gqlClient graphql.Client, return response.GetPackages()[0].AllPkgTree.GetNamespaces()[0].GetNames()[0].GetVersions()[0], nil } -func ConvertPkgInputSpecToPkgSpec(pkgInput *model.PkgInputSpec) model.PkgSpec { - pkgSpec := model.PkgSpec{ - Type: &pkgInput.Type, - Name: &pkgInput.Name, - } - - if pkgInput.Namespace != nil && *pkgInput.Namespace != "" { - pkgSpec.Namespace = pkgInput.Namespace - } - if pkgInput.Version != nil && *pkgInput.Version != "" { - pkgSpec.Version = pkgInput.Version - } - if pkgInput.Subpath != nil && *pkgInput.Subpath != "" { - pkgSpec.Subpath = pkgInput.Subpath - } - - return pkgSpec -} - +// GetPurlsForPkg returns all purls associated with the pkgSpec passed in. func GetPurlsForPkg(ctx context.Context, gqlClient graphql.Client, pkgSpec model.PkgSpec) ([]string, []string, error) { var purls []string var packageIds []string diff --git a/pkg/guacrest/helpers/sbom.go b/pkg/guacrest/helpers/sbom.go deleted file mode 100644 index 419aa33ccd..0000000000 --- a/pkg/guacrest/helpers/sbom.go +++ /dev/null @@ -1,154 +0,0 @@ -// -// Copyright 2024 The GUAC Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package helpers - -import ( - "context" - "fmt" - model "github.com/guacsec/guac/pkg/assembler/clients/generated" - "strings" - - "github.com/Khan/genqlient/graphql" - "github.com/Masterminds/semver" - "github.com/guacsec/guac/pkg/logging" -) - -func LatestSBOMFromID(ctx context.Context, client graphql.Client, IDs []string) (*model.AllHasSBOMTree, error) { - logger := logging.FromContext(ctx) - - latestSBOM := model.HasSBOMsHasSBOM{} - - for _, ID := range IDs { - // Define the spec to filter SBOMs by the package version level ID - spec := model.HasSBOMSpec{ - Subject: &model.PackageOrArtifactSpec{ - Package: &model.PkgSpec{ - Id: &ID, - }, - }, - } - - // Query for SBOMs as a package - sboms, err := model.HasSBOMs(ctx, client, spec) - if err != nil { - logger.Errorw("Failed to query SBOMs for package", "ID", ID, "error", err) - return nil, err - } - - // If no SBOMs found, try querying as an artifact - if len(sboms.HasSBOM) == 0 { - spec.Subject = &model.PackageOrArtifactSpec{ - Artifact: &model.ArtifactSpec{ - Id: &ID, - }, - } - sboms, err = model.HasSBOMs(ctx, client, spec) - if err != nil { - logger.Errorw("Failed to query SBOMs for artifact", "ID", ID, "error", err) - return nil, err - } - } - - if len(sboms.HasSBOM) == 0 { - logger.Errorf("Failed to find any SBOMs with ID: %v", ID) - return nil, fmt.Errorf("error getting sboms, no sboms with ID %v found", ID) - } - - // Find the latest SBOM - for _, sbom := range sboms.HasSBOM { - if latestSBOM.Id == "" || compare(&sbom.AllHasSBOMTree, &latestSBOM.AllHasSBOMTree, client) { - latestSBOM = sbom - } - } - } - - return &latestSBOM.AllHasSBOMTree, nil -} - -func compare(a *model.AllHasSBOMTree, b *model.AllHasSBOMTree, gqlClient graphql.Client) bool { - logger := logging.FromContext(context.Background()) - - aVersion, err := findSubjectBasedOnType(a, gqlClient) - if err != nil { - return false - } - - bVersion, err := findSubjectBasedOnType(b, gqlClient) - if err != nil { - return true - } - - if (aVersion == "" && bVersion != "") || (aVersion != "" && bVersion == "") { - return aVersion != "" - } - - if strings.HasPrefix(aVersion, "sha256") || aVersion == "" || - strings.HasPrefix(bVersion, "sha256") || bVersion == "" || aVersion == bVersion { - return a.KnownSince.After(b.KnownSince) - } - - parsedAVersion, err := semver.NewVersion(aVersion) - if err != nil { - logger.Warnw("Could not parse version, fallback to time", "version", aVersion, "error", err) - return a.KnownSince.After(b.KnownSince) - } - parsedBVersion, err := semver.NewVersion(bVersion) - if err != nil { - logger.Warnw("Could not parse version, fallback to time", "version", bVersion, "error", err) - return a.KnownSince.After(b.KnownSince) - } - - return parsedAVersion.Compare(parsedBVersion) > 0 -} - -func findSubjectBasedOnType(a *model.AllHasSBOMTree, gqlClient graphql.Client) (string, error) { - var version string - switch subject := a.Subject.(type) { - case *model.AllHasSBOMTreeSubjectArtifact: - // Get the package attached to the artifact via an isOccurrence node - pkg, err := GetPkgFromArtifact(gqlClient, subject.Id) - if err != nil { - return "", fmt.Errorf("could not find package for subject: %s, with err: %v", subject.Id, err) - } - version = pkg.Namespaces[0].Names[0].Versions[0].Version - case *model.AllHasSBOMTreeSubjectPackage: - version = subject.Namespaces[0].Names[0].Versions[0].Version - default: - return "", fmt.Errorf("Unknown subject type") - } - return version, nil -} - -func GetPkgFromArtifact(gqlClient graphql.Client, id string) (*model.AllPkgTree, error) { - rsp, err := model.Occurrences(context.Background(), gqlClient, model.IsOccurrenceSpec{ - Artifact: &model.ArtifactSpec{ - Id: &id, - }, - }) - if err != nil { - return nil, fmt.Errorf("error getting occurrences from artifact %s: %v", id, err) - } - for i := range rsp.GetIsOccurrence() { - if *rsp.GetIsOccurrence()[i].GetSubject().GetTypename() == "Package" { - p, ok := rsp.GetIsOccurrence()[i].GetSubject().(*model.AllIsOccurrencesTreeSubjectPackage) - if !ok { - return nil, fmt.Errorf("could not convert package %s to type *model.AllIsOccurrencesTreeSubjectPackage", id) - } - return &p.AllPkgTree, nil - } - } - return nil, nil -} diff --git a/pkg/guacrest/helpers/sbom_test.go b/pkg/guacrest/helpers/sbom_test.go deleted file mode 100644 index 7cd9b8247c..0000000000 --- a/pkg/guacrest/helpers/sbom_test.go +++ /dev/null @@ -1,189 +0,0 @@ -// -// Copyright 2024 The GUAC Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build integration - -package helpers - -import ( - "context" - "testing" - "time" - - clients "github.com/guacsec/guac/internal/testing/graphqlClients" - model "github.com/guacsec/guac/pkg/assembler/clients/generated" - - "github.com/stretchr/testify/assert" -) - -type compareSbomTestData struct { - pkgVersion string - knownSince time.Time -} - -func TestLatestSBOMFromID_Integration(t *testing.T) { - startTime := time.Now() - - tests := []struct { - name string - pkgType string - pkgNamespace string - pkgName string - compareData []compareSbomTestData - knownSince []time.Time - expectedIndex int // Index of the expected latest SBOM - }{ - { - name: "Latest version SBOM", - pkgType: "test-type", - pkgNamespace: "", - pkgName: "test-package", - compareData: []compareSbomTestData{ - { - pkgVersion: "v1.0.0", - knownSince: startTime, - }, - { - pkgVersion: "v1.1.0", - knownSince: startTime, - }, - { - pkgVersion: "v1.2.0", - knownSince: startTime, - }, - }, - expectedIndex: 2, - }, - { - name: "Latest SBOM by time (same version)", - pkgType: "test-type", - pkgNamespace: "", - pkgName: "test-name-same-versions", - compareData: []compareSbomTestData{ - { - pkgVersion: "1.0.0", - knownSince: startTime, - }, - { - pkgVersion: "1.0.0", - knownSince: startTime.Add(time.Hour), - }, - { - pkgVersion: "1.0.0", - knownSince: startTime.Add(2 * time.Hour), - }, - }, - expectedIndex: 2, - }, - { - name: "Latest SBOM by time (empty versions)", - pkgType: "test-type", - pkgNamespace: "test-namespace", - pkgName: "test-name-empty-version", - compareData: []compareSbomTestData{ - { - pkgVersion: "", - knownSince: startTime, - }, - { - pkgVersion: "", - knownSince: startTime.Add(time.Hour), - }, - { - pkgVersion: "", - knownSince: startTime.Add(2 * time.Hour), - }, - }, - expectedIndex: 2, - }, - { - name: "Latest SBOM by time (sha256 versions)", - pkgType: "test-type", - pkgNamespace: "test-namespace", - pkgName: "test-name-sha256", - compareData: []compareSbomTestData{ - { - pkgVersion: "sha256:123", - knownSince: startTime, - }, - { - pkgVersion: "sha256:789", - knownSince: startTime.Add(2 * time.Hour), - }, - { - pkgVersion: "sha256:456", - knownSince: startTime.Add(time.Hour), - }, - }, - expectedIndex: 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - gqlClient := clients.SetupTest(t) - - sbomIDMap := make(map[string]int) - var pkgIds []string - - // Ingest packages and SBOMs - for i, sbomTestData := range tt.compareData { - pkgInput := model.PkgInputSpec{ - Type: tt.pkgType, - Namespace: &tt.pkgNamespace, - Name: tt.pkgName, - Version: &sbomTestData.pkgVersion, - } - - pkg, err := model.IngestPackage(ctx, gqlClient, model.IDorPkgInput{PackageInput: &pkgInput}) - assert.NoError(t, err) - pkgIds = append(pkgIds, pkg.IngestPackage.PackageVersionID) - - sbomInput := model.HasSBOMInputSpec{ - KnownSince: sbomTestData.knownSince, - } - - sbomResult, err := model.IngestHasSBOMPkg(ctx, gqlClient, model.IDorPkgInput{PackageVersionID: &pkg.IngestPackage.PackageVersionID}, sbomInput, model.HasSBOMIncludesInputSpec{ - Packages: []string{}, - Artifacts: []string{}, - Occurrences: []string{}, - Dependencies: []string{}, - }) - assert.NoError(t, err) - - // Store the ID of the ingested SBOM with its index in the array - sbomIDMap[sbomResult.IngestHasSBOM] = i - } - - // Retrieve the latest SBOM - latestPkg, err := LatestSBOMFromID(ctx, gqlClient, pkgIds) - assert.NoError(t, err) - assert.NotNil(t, latestPkg) - - // Check if the retrieved SBOM is the expected one by comparing the index - actualIndex, exists := sbomIDMap[latestPkg.Id] - assert.True(t, exists, "The returned SBOM ID does not exist in the map") - assert.Equal(t, tt.expectedIndex, actualIndex, "The index of the latest SBOM does not match the expected index") - - // Additional checks to ensure the content is correct - pkgSubject, ok := latestPkg.Subject.(*model.AllHasSBOMTreeSubjectPackage) - if !ok { - t.Fatalf("Unexpected subject type: %T", latestPkg.Subject) - } - assert.Equal(t, tt.compareData[tt.expectedIndex].pkgVersion, pkgSubject.Namespaces[0].Names[0].Versions[0].Version) - }) - } -} diff --git a/pkg/guacrest/openapi.yaml b/pkg/guacrest/openapi.yaml index 61cb47ae97..027e8868ae 100644 --- a/pkg/guacrest/openapi.yaml +++ b/pkg/guacrest/openapi.yaml @@ -28,50 +28,6 @@ paths: application/json: schema: type: string - "/query/dependencies": - get: - summary: Retrieve transitive dependencies - description: > - Find the transitive dependencies of the input. The HasSBOM and HasSLSA - predicates are used as the dependency relationship and the IsOccurrence and - PkgEqual predicates are used to find consider equivalent packages. - operationId: retrieveDependencies - parameters: - - $ref: "#/components/parameters/PaginationSpec" - - name: linkCondition - description: > - Whether links between nouns must be made by digest or if they - can be made just by name (i.e. purl). Specify 'name' to allow using - SBOMs that don't provide the digest of the subject. The default is - 'digest'. To search by purl, 'name' must be specified. - in: query - required: false - schema: - type: string - enum: - - digest - - name - - name: purl - description: The purl of the dependent package. - in: query - required: false - schema: - type: string - - name: digest - description: The digest of the dependent package. - in: query - required: false - schema: - type: string - responses: - "200": - $ref: "#/components/responses/PurlList" - "400": - $ref: "#/components/responses/BadRequest" - "500": - $ref: "#/components/responses/InternalServerError" - "502": - $ref: "#/components/responses/BadGateway" "/analysis/dependencies": get: summary: Identify the most important dependencies @@ -101,46 +57,55 @@ paths: $ref: "#/components/responses/BadGateway" "/v0/package/{purl}": get: - summary: Get purls related to the specific Package URL (PURL) - operationId: getPackagePurlsByPurl + summary: Get all purls related to the given purl + description: > + If a partial purl, pkg:foo/bar is passed in, then it would find all purls + associated with the given purl, such as pkg:foo/bar@1.0, and pkg:foo/bar@2.0. + operationId: getPackagePurls parameters: - name: purl in: path required: true - description: URL-encoded Package URL (PURL) + description: URL-encoded Package URL (purl) schema: type: string responses: "200": - description: A list of PURLs associated with the specified PURL - content: - application/json: - schema: - type: array - items: - type: string - description: Package URL (PURL) + $ref: "#/components/responses/PurlList" "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalServerError" + "502": + $ref: "#/components/responses/BadGateway" "/v0/package/{purl}/vulns": get: - summary: Get vulnerabilities for a specific Package URL (PURL) - operationId: getPackageVulnerabilitiesByPurl + summary: Get vulnerabilities for a Package URL (purl) + description: > + If a partial purl is passed in, then it will find all associated purls + and find vulnerabilities of those. For example, if the partial purl + pkg:foo/bar is passed in, it would find all purls associated with + pkg:foo/bar, such as pkg:foo/bar@1.0, and pkg:foo/bar@2.0. This endpoint + will then find all vulnerabilities for these purls. If the `includeDependencies` + flag is set to true, it will also include vulnerabilities of the dependencies. + operationId: getPackageVulns parameters: - name: purl in: path required: true - description: URL-encoded Package URL (PURL) + description: URL-encoded Package URL (purl) schema: type: string - - name: latestSBOM + - name: includeDependencies in: query required: false - description: Whether the query should search in the latest sbom + description: > + A flag to include vulnerabilities of the dependencies. If true, the + response will include vulnerabilities for the purl and its dependencies + instead of the vulnerabilities of just the purl. schema: type: boolean + default: false responses: "200": $ref: "#/components/responses/VulnerabilityList" @@ -148,23 +113,25 @@ paths: $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalServerError" + "502": + $ref: "#/components/responses/BadGateway" "/v0/package/{purl}/dependencies": get: - summary: Get dependencies for a specific Package URL (PURL) - operationId: getPackageDependenciesByPurl + summary: Get dependencies for a specific Package URL (purl) + description: > + If a partial purl is passed in, then it will find all associated purls + and find the dependencies of those. For example, if the partial purl + pkg:foo/bar is passed in, it would find all purls associated with + pkg:foo/bar, such as pkg:foo/bar@1.0, and pkg:foo/bar@2.0. This endpoint + will then find all dependencies for these purls. + operationId: getPackageDeps parameters: - name: purl in: path required: true - description: URL-encoded Package URL (PURL) + description: URL-encoded Package URL (purl) schema: type: string - - name: latestSBOM - in: query - required: false - description: Whether the query should search in the latest sbom - schema: - type: boolean responses: "200": $ref: "#/components/responses/PurlList" @@ -172,23 +139,19 @@ paths: $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalServerError" - "/v0/artifact/{artifact}/vulns": + "502": + $ref: "#/components/responses/BadGateway" + "/v0/artifact/{digest}/vulns": get: - summary: Get vulnerabilities for a specific Artifact (:) - operationId: getArtifactVulnerabilities + summary: Get vulnerabilities for a specific digest + operationId: getArtifactVulns parameters: - - name: artifact + - name: digest in: path required: true - description: Artifact identifier in the format + description: Digest, the second part from artifact identifier in the format schema: type: string - - name: latestSBOM - in: query - required: false - description: Whether the query should search in the latest sbom - schema: - type: boolean responses: "200": $ref: "#/components/responses/VulnerabilityList" @@ -196,23 +159,19 @@ paths: $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalServerError" - "/v0/artifact/{artifact}/dependencies": + "502": + $ref: "#/components/responses/BadGateway" + "/v0/artifact/{digest}/dependencies": get: - summary: Get dependencies for a specific Artifact (:) - operationId: getArtifactDependencies + summary: Get dependencies for a specific digest + operationId: getArtifactDeps parameters: - - name: artifact + - name: digest in: path required: true - description: Artifact identifier in the format + description: Digest, the second part from artifact identifier in the format schema: type: string - - name: latestSBOM - in: query - required: false - description: Whether the query should search in the latest sbom - schema: - type: boolean responses: "200": $ref: "#/components/responses/PurlList" @@ -220,6 +179,8 @@ paths: $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/InternalServerError" + "502": + $ref: "#/components/responses/BadGateway" components: parameters: @@ -271,14 +232,12 @@ components: Vulnerability: type: object required: - - packages + - package - vulnerability - metadata properties: - packages: - type: array - items: - type: string + package: + type: string vulnerability: $ref: '#/components/schemas/VulnerabilityDetails' metadata: @@ -294,6 +253,9 @@ components: type: array items: type: string + description: > + A list of vulnerability identifiers. These can be CVE IDs or other + formats used to identify vulnerabilities. ScanMetadata: type: object properties: @@ -312,10 +274,6 @@ components: type: string collector: type: string - QueryType: - type: string - enum: [ vulns, dependencies, latestSbom ] - x-enum-varnames: [ Vulns, Dependencies, LatestSbom ] responses: # for code 200 PurlList: diff --git a/pkg/guacrest/server/errors.go b/pkg/guacrest/server/errors.go index b90aea45ac..e31f5647be 100644 --- a/pkg/guacrest/server/errors.go +++ b/pkg/guacrest/server/errors.go @@ -17,32 +17,158 @@ package server import ( "context" + "net/http" gen "github.com/guacsec/guac/pkg/guacrest/generated" "github.com/guacsec/guac/pkg/guacrest/helpers" ) -// Maps helpers.Err502 and helpers.Err500 to the corresponding OpenAPI response type. -// Other errors are returned as Client errors. -func handleErr(ctx context.Context, err error) gen.RetrieveDependenciesResponseObject { +// Define a custom type for endpoint types +type EndpointType int + +const ( + GetPackagePurls EndpointType = iota + GetPackageVulns + GetPackageDeps + GetArtifactVulns + GetArtifactDeps +) + +// IsErrorResponse checks if the response status code indicates an error. +func IsErrorResponse(statusCode int) bool { + return statusCode >= 400 +} + +// IsSuccessResponse checks if the response status code indicates success. +func IsSuccessResponse(statusCode int) bool { + return statusCode == http.StatusOK +} + +func handleErr( + ctx context.Context, + err error, + endpointType EndpointType, +) interface{} { if err == nil { - return nil + return createBadRequestResponse(endpointType, "Unknown error") } switch err { case helpers.Err502: - return gen.RetrieveDependencies502JSONResponse{ - BadGatewayJSONResponse: gen.BadGatewayJSONResponse{ - Message: err.Error(), - }} + return createBadGatewayResponse(endpointType, err.Error()) case helpers.Err500: - return gen.RetrieveDependencies500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: err.Error(), - }} + return createInternalServerErrorResponse(endpointType, err.Error()) default: - return gen.RetrieveDependencies400JSONResponse{ + return createBadRequestResponse(endpointType, err.Error()) + } +} + +func createBadRequestResponse(endpointType EndpointType, message string) interface{} { + switch endpointType { + case GetPackagePurls: + return gen.GetPackagePurls400JSONResponse{ + BadRequestJSONResponse: gen.BadRequestJSONResponse{ + Message: message, + }, + } + case GetPackageDeps: + return gen.GetPackageDeps400JSONResponse{ + BadRequestJSONResponse: gen.BadRequestJSONResponse{ + Message: message, + }, + } + case GetPackageVulns: + return gen.GetPackageVulns400JSONResponse{ BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: err.Error(), - }} + Message: message, + }, + } + case GetArtifactDeps: + return gen.GetArtifactDeps400JSONResponse{ + BadRequestJSONResponse: gen.BadRequestJSONResponse{ + Message: message, + }, + } + case GetArtifactVulns: + return gen.GetArtifactVulns400JSONResponse{ + BadRequestJSONResponse: gen.BadRequestJSONResponse{ + Message: message, + }, + } + default: + return nil + } +} + +func createInternalServerErrorResponse(endpointType EndpointType, message string) interface{} { + switch endpointType { + case GetPackagePurls: + return gen.GetPackagePurls500JSONResponse{ + InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ + Message: message, + }, + } + case GetPackageDeps: + return gen.GetPackageDeps500JSONResponse{ + InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ + Message: message, + }, + } + case GetPackageVulns: + return gen.GetPackageVulns500JSONResponse{ + InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ + Message: message, + }, + } + case GetArtifactDeps: + return gen.GetArtifactDeps500JSONResponse{ + InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ + Message: message, + }, + } + case GetArtifactVulns: + return gen.GetArtifactVulns500JSONResponse{ + InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ + Message: message, + }, + } + default: + return nil + } +} + +func createBadGatewayResponse(endpointType EndpointType, message string) interface{} { + switch endpointType { + case GetPackagePurls: + return gen.GetPackagePurls502JSONResponse{ + BadGatewayJSONResponse: gen.BadGatewayJSONResponse{ + Message: message, + }, + } + case GetPackageDeps: + return gen.GetPackageDeps502JSONResponse{ + BadGatewayJSONResponse: gen.BadGatewayJSONResponse{ + Message: message, + }, + } + case GetPackageVulns: + return gen.GetPackageVulns502JSONResponse{ + BadGatewayJSONResponse: gen.BadGatewayJSONResponse{ + Message: message, + }, + } + case GetArtifactDeps: + return gen.GetArtifactDeps502JSONResponse{ + BadGatewayJSONResponse: gen.BadGatewayJSONResponse{ + Message: message, + }, + } + case GetArtifactVulns: + return gen.GetArtifactVulns502JSONResponse{ + BadGatewayJSONResponse: gen.BadGatewayJSONResponse{ + Message: message, + }, + } + default: + return nil } } diff --git a/pkg/guacrest/server/retrieveDependencies.go b/pkg/guacrest/server/retrieveDependencies.go index db09f8e367..bdbcb0dd75 100644 --- a/pkg/guacrest/server/retrieveDependencies.go +++ b/pkg/guacrest/server/retrieveDependencies.go @@ -1,44 +1,58 @@ +// +// Copyright 2024 The GUAC Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( "context" "fmt" + assembler_helpers "github.com/guacsec/guac/pkg/assembler/helpers" + "golang.org/x/exp/maps" + "net/url" "github.com/Khan/genqlient/graphql" gql "github.com/guacsec/guac/pkg/assembler/clients/generated" - assembler_helpers "github.com/guacsec/guac/pkg/assembler/helpers" - gen "github.com/guacsec/guac/pkg/guacrest/generated" "github.com/guacsec/guac/pkg/guacrest/helpers" - "github.com/guacsec/guac/pkg/guacrest/pagination" "github.com/guacsec/guac/pkg/logging" - "golang.org/x/exp/maps" ) -// node is implemented by all graphQL client types +// node is implemented by all GraphQL client types type node interface { GetId() string } // edgeGen defines the edges used by the transitive dependencies graph traversal. type edgeGen interface { - // getDirectDependencies returns the nouns that are direct dependencies of the input noun. + // getDirectDependencies returns the nodes that are direct dependencies of the input noun. getDirectDependencies(ctx context.Context, v node) ([]node, error) - // getEquivalentNodes returns the nouns that are considered equivalent to the input noun. + // getEquivalentNodes returns the nodes that are considered equivalent to the input noun. getEquivalentNodes(ctx context.Context, v node) ([]node, error) } -// byDigest is an edgeGen that observes relationships between nouns when they are +// byDigest is a edgeGen that observes relationships between noun when they are // linked by digest. // // The dependency edges are: -// - artifact -> sbom -> package -// - artifact -> sbom -> artifact -// - artifact -> slsa -> artifact +// - digest -> sbom -> package +// - digest -> sbom -> digest +// - digest -> slsa -> digest // // And the equivalence edges are: -// - artifact -> IsOccurrence -> package -// - artifact -> HashEquals -> artifact +// - digest -> IsOccurrence -> package +// - digest -> HashEquals -> digest // // byDigest lazily generates edges using calls to the GraphQL server, instead // of precomputing the graph. @@ -57,10 +71,10 @@ func newByDigest(gqlClient graphql.Client) byDigest { // It implements all edges defined by byDigest, in addition to the following: // dependency edges: // - package -> sbom -> package -// - package -> sbom -> artifact +// - package -> sbom -> digest // // equivalence edges: -// - package -> IsOccurrence -> artifact +// - package -> IsOccurrence -> digest // // byName lazily generates edges using calls to the GraphQL server, instead of // precomputing the graph. @@ -73,7 +87,7 @@ func newByName(gqlClient graphql.Client) byName { return byName{gqlClient: gqlClient, bd: newByDigest(gqlClient)} } -/********* The graph traversal *********/ +//********* The graph traversal *********/ func getTransitiveDependencies( ctx context.Context, @@ -92,27 +106,28 @@ func getTransitiveDependencies( nodesEquivalentToStart := map[node]any{start: struct{}{}} for len(queue) > 0 { - node := queue[0] + currentNode := queue[0] queue = queue[1:] - if _, ok := visited[node.GetId()]; ok { + if _, ok := visited[currentNode.GetId()]; ok { continue } - visited[node.GetId()] = node + visited[currentNode.GetId()] = currentNode - adjacent, err := edges.getDirectDependencies(ctx, node) + // Get direct dependencies + adjacent, err := edges.getDirectDependencies(ctx, currentNode) if err != nil { return nil, err } queue = append(queue, adjacent...) - adjacent, err = edges.getEquivalentNodes(ctx, node) + adjacent, err = edges.getEquivalentNodes(ctx, currentNode) if err != nil { return nil, err } queue = append(queue, adjacent...) - if _, ok := nodesEquivalentToStart[node]; ok { + if _, ok := nodesEquivalentToStart[currentNode]; ok { for _, equivalentNode := range adjacent { nodesEquivalentToStart[equivalentNode] = struct{}{} } @@ -180,10 +195,10 @@ func (eg byName) getEquivalentNodes(ctx context.Context, v node) ([]node, error) return neighborsTwoHops(ctx, eg.gqlClient, v, edgesToPredicates, edgesFromPredicates) } -/********* Graphql helper functions *********/ +//********* GraphQL helper functions *********/ -// neighborsTwoHops calls the GraphQL Neighbors endpoint once with edgesToPredicates, and -// then again on the result with edgesFromPredicates. +// neighborsTwoHops calls the GraphQL Neighbors endpoint once with edgesToPredicates, +// and then again on the result with edgesFromPredicates. func neighborsTwoHops(ctx context.Context, gqlClient graphql.Client, v node, edgesToPredicates []gql.Edge, edgesFromPredicates []gql.Edge) ([]node, error) { predicates, err := neighbors(ctx, gqlClient, v, edgesToPredicates) @@ -207,7 +222,7 @@ func neighbors(ctx context.Context, gqlClient graphql.Client, v node, edges []gq logger := logging.FromContext(ctx) neighborsResponse, err := gql.Neighbors(ctx, gqlClient, v.GetId(), edges) if err != nil { - logger.Errorf("Neighbors query returned err: ", err) + logger.Errorf("Neighbors query returned err: %v", err) return nil, helpers.Err502 } if neighborsResponse == nil { @@ -230,9 +245,9 @@ func transformWithError[A any, B any](ctx context.Context, lst []A, f func(conte return res, nil } -// Returns the graphQL type that is nested in the neighbors response node. For package tries, -// the leaf version node is returned. Only the types relevant to the retrieveDependecies -// graph traversal are implemented. +// neighborToNode returns the GraphQL type that is nested in the neighbors response node. +// For package trees, the leaf version node is returned. Only the types relevant +// to the retrieveDependencies graph traversal are implemented. func neighborToNode(ctx context.Context, neighborsNode gql.NeighborsNeighborsNode) (node, error) { logger := logging.FromContext(ctx) switch val := neighborsNode.(type) { @@ -288,10 +303,9 @@ func neighborToNode(ctx context.Context, neighborsNode gql.NeighborsNeighborsNod return nil, helpers.Err500 } -// Maps nodes in the input to purls, ignoring nodes that are not package version -// nodes. +// Maps nodes in the input to a map of "pkg id : purl", ignoring nodes that are not package version nodes. func mapPkgNodesToPurls(ctx context.Context, gqlClient graphql.Client, - nodes []node) ([]string, error) { + nodes []node) (map[string]string, error) { logger := logging.FromContext(ctx) // get the IDs of the package nodes @@ -320,75 +334,80 @@ func mapPkgNodesToPurls(ctx context.Context, gqlClient graphql.Client, logger.Warnf("GQL query \"nodes\" did not return the expected number of results") } - // map the package tries to purls - purls := make([]string, 0, len(gqlNodes.GetNodes())) + // map the package tries to a map of "pkg id : purl" + purlMap := make(map[string]string) for _, gqlNode := range gqlNodes.GetNodes() { if v, ok := gqlNode.(*gql.NodesNodesPackage); ok { purl := assembler_helpers.AllPkgTreeToPurl(&v.AllPkgTree) - purls = append(purls, purl) + purlMap[v.AllPkgTree.Namespaces[0].Names[0].Versions[0].Id] = purl } else { logger.Warnf("Nodes query returned an unexpected type: %T", *gqlNode.GetTypename()) } } - return purls, nil + return purlMap, nil } -/********* The endpoint handler *********/ -func (s *DefaultServer) RetrieveDependencies( +// GetDepsForPackage gets all direct and transitive dependencies for a given purl +func GetDepsForPackage( ctx context.Context, - request gen.RetrieveDependenciesRequestObject, -) (gen.RetrieveDependenciesResponseObject, error) { + gqlClient graphql.Client, + purl string, +) (map[string]string, error) { // Find the start node - var start node - if request.Params.Purl != nil { - pkg, err := helpers.FindPackageWithPurl(ctx, s.gqlClient, *request.Params.Purl) - if err != nil { - return handleErr(ctx, err), nil - } - start = &pkg - } else if request.Params.Digest != nil { - artifact, err := helpers.FindArtifactWithDigest(ctx, s.gqlClient, *request.Params.Digest) - if err != nil { - return handleErr(ctx, err), nil - } - start = &artifact - } else { - return gen.RetrieveDependencies400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: "Neither a purl or a digest argument was provided", - }}, nil + unescapedPurl, err := url.QueryUnescape(purl) + if err != nil { + return nil, fmt.Errorf("failed to unescape package url: %w", err) + } + pkg, err := helpers.FindPackageWithPurl(ctx, gqlClient, unescapedPurl) + if err != nil { + return nil, fmt.Errorf("failed to find package with purl: %w", err) } - // Select the edgeGen. The default is byDigest - var edgeGenerator edgeGen - cond := request.Params.LinkCondition - if cond == nil { - edgeGenerator = newByDigest(s.gqlClient) - } else if *cond == gen.Name { - edgeGenerator = newByName(s.gqlClient) - } else if *cond == gen.Digest { - edgeGenerator = newByDigest(s.gqlClient) - } else { - err := fmt.Errorf("Unrecognized linkCondition: %s", *request.Params.LinkCondition) - return handleErr(ctx, err), nil + edgeGenerator := newByName(gqlClient) + // Perform traversal + deps, err := getTransitiveDependencies(ctx, gqlClient, &pkg, edgeGenerator) + if err != nil { + return nil, fmt.Errorf("failed to get dependencies: %w", err) } - // Compute the result and map to purls - deps, err := getTransitiveDependencies(ctx, s.gqlClient, start, edgeGenerator) + // Map nodes to PURLs + purls, err := mapPkgNodesToPurls(ctx, gqlClient, deps) if err != nil { - return handleErr(ctx, err), nil + return nil, fmt.Errorf("failed to map dependencies: %w", err) } - purls, err := mapPkgNodesToPurls(ctx, s.gqlClient, deps) + + return purls, nil +} + +// GetDepsForArtifact gets all direct and transitive dependencies for a digest +func GetDepsForArtifact( + ctx context.Context, + gqlClient graphql.Client, + digest string, +) (map[string]string, error) { + // Find the start node + unescapedDigest, err := url.QueryUnescape(digest) if err != nil { - return handleErr(ctx, err), nil + return nil, fmt.Errorf("failed to unescape digest: %w", err) + } + art, err := helpers.FindArtifactWithDigest(ctx, gqlClient, unescapedDigest) + if err != nil { + return nil, fmt.Errorf("failed to find digest: %w", err) + } + + edgeGenerator := newByDigest(gqlClient) + + // Perform traversal + deps, err := getTransitiveDependencies(ctx, gqlClient, &art, edgeGenerator) + if err != nil { + return nil, fmt.Errorf("failed to get dependencies: %w", err) } - page, pageInfo, err := pagination.Paginate(ctx, purls, request.Params.PaginationSpec) + // Map nodes to PURLs + purls, err := mapPkgNodesToPurls(ctx, gqlClient, deps) if err != nil { - return handleErr(ctx, err), nil + return nil, fmt.Errorf("failed to map dependencies: %w", err) } - return gen.RetrieveDependencies200JSONResponse{PurlListJSONResponse: gen.PurlListJSONResponse{ - PurlList: page, - PaginationInfo: pageInfo, - }}, nil + + return purls, nil } diff --git a/pkg/guacrest/server/retrieveDependencies_test.go b/pkg/guacrest/server/retrieveDependencies_test.go index 3fe0b71076..b1fad57053 100644 --- a/pkg/guacrest/server/retrieveDependencies_test.go +++ b/pkg/guacrest/server/retrieveDependencies_test.go @@ -1,34 +1,31 @@ -// // Copyright 2024 The GUAC Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - package server_test import ( stdcmp "cmp" "context" + "net/url" "testing" - cmp "github.com/google/go-cmp/cmp" - - "github.com/google/go-cmp/cmp/cmpopts" . "github.com/guacsec/guac/internal/testing/graphqlClients" - "github.com/guacsec/guac/internal/testing/ptrfrom" _ "github.com/guacsec/guac/pkg/assembler/backends/keyvalue" - api "github.com/guacsec/guac/pkg/guacrest/generated" "github.com/guacsec/guac/pkg/guacrest/server" "github.com/guacsec/guac/pkg/logging" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) // Tests the edges in ByName that are not in ByDigest @@ -38,8 +35,9 @@ func Test_RetrieveDependencies(t *testing.T) { name string data GuacData - // Only specify Purl or Digest. The test will set linkCondition, because both are tested - input api.RetrieveDependenciesParams + // Only specify a Purl or a Digest. But, both purls and digests are tested + purl string + digest string expectedByName []string expectedByDigest []string @@ -60,7 +58,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/foo", IncludedSoftware: []string{"pkg:guac/bar"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{}, }, @@ -72,7 +70,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/foo", IncludedSoftware: []string{"pkg:guac/bar", "pkg:guac/baz"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar", "pkg:guac/baz"}, expectedByDigest: []string{}, }, @@ -85,7 +83,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "sha-xyz", IncludedSoftware: []string{"pkg:guac/bar"}}, }, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-xyz")}, + digest: "sha-xyz", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/bar"}, }, @@ -98,12 +96,12 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/bar", IncludedSoftware: []string{"pkg:guac/baz"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar", "pkg:guac/baz"}, expectedByDigest: []string{}, }, { - name: "Artifact -> SBOM -> artifact -> SBOM -> package", + name: "Artifact -> SBOM -> digest -> SBOM -> package", data: GuacData{ Packages: []string{"pkg:guac/foo"}, Artifacts: []string{"sha-xyz", "sha-123"}, @@ -112,7 +110,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "sha-123", IncludedSoftware: []string{"pkg:guac/foo"}}, }, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-xyz")}, + digest: "sha-xyz", expectedByName: []string{"pkg:guac/foo"}, expectedByDigest: []string{"pkg:guac/foo"}, }, @@ -126,36 +124,36 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/bar", IncludedSoftware: []string{"pkg:guac/foo"}}, }, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-xyz")}, + digest: "sha-xyz", expectedByName: []string{"pkg:guac/foo", "pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/bar"}, }, { - name: "artifact -> occurrence -> package", + name: "digest -> occurrence -> package", data: GuacData{ Packages: []string{"pkg:guac/bar"}, Artifacts: []string{"sha-123", "sha-xyz"}, HasSboms: []HasSbom{{Subject: "sha-xyz", IncludedSoftware: []string{"sha-123"}}}, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/bar", Artifact: "sha-123"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-xyz")}, + digest: "sha-xyz", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/bar"}, }, { - name: "Package -> occurrence -> artifact", + name: "Package -> occurrence -> digest", data: GuacData{ Packages: []string{"pkg:guac/foo", "pkg:guac/bar"}, Artifacts: []string{"sha-xyz"}, HasSboms: []HasSbom{{Subject: "sha-xyz", IncludedSoftware: []string{"pkg:guac/bar"}}}, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-xyz"}}, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{}, }, { - name: "package -> occurrence -> artifact, artifact", + name: "package -> occurrence -> digest, digest", data: GuacData{ Packages: []string{"pkg:guac/foo", "pkg:guac/bar", "pkg:guac/baz"}, Artifacts: []string{"sha-xyz", "sha-123"}, @@ -168,24 +166,24 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/foo", Artifact: "sha-123"}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar", "pkg:guac/baz"}, expectedByDigest: []string{}, }, { - name: "Artifact -> hashEqual -> artifact", + name: "Artifact -> hashEqual -> digest", data: GuacData{ Packages: []string{"pkg:guac/foo"}, Artifacts: []string{"sha-123", "sha-456"}, HasSboms: []HasSbom{{Subject: "sha-456", IncludedSoftware: []string{"pkg:guac/foo"}}}, HashEquals: []HashEqual{{ArtifactA: "sha-123", ArtifactB: "sha-456"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo"}, expectedByDigest: []string{"pkg:guac/foo"}, }, { - name: "Artifact -> hashEqual -> artifact, artifact", + name: "Artifact -> hashEqual -> digest, digest", data: GuacData{ Packages: []string{"pkg:guac/foo", "pkg:guac/bar"}, Artifacts: []string{"sha-123", "sha-456", "sha-789"}, @@ -198,12 +196,12 @@ func Test_RetrieveDependencies(t *testing.T) { {ArtifactA: "sha-123", ArtifactB: "sha-789"}, }, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo", "pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/foo", "pkg:guac/bar"}, }, { - name: "Artifact -> hashEqual -> artifact -> hashEqual -> artifact", + name: "Artifact -> hashEqual -> digest -> hashEqual -> digest", data: GuacData{ Packages: []string{"pkg:guac/foo", "pkg:guac/bar"}, Artifacts: []string{"sha-123", "sha-456", "sha-789"}, @@ -216,12 +214,12 @@ func Test_RetrieveDependencies(t *testing.T) { {ArtifactA: "sha-456", ArtifactB: "sha-789"}, }, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo", "pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/foo", "pkg:guac/bar"}, }, { - name: "artifact -> SLSA -> artifact -> occurrence -> package", + name: "digest -> SLSA -> digest -> occurrence -> package", data: GuacData{ Packages: []string{"pkg:guac/foo"}, Artifacts: []string{"sha-123", "sha-xyz"}, @@ -229,12 +227,12 @@ func Test_RetrieveDependencies(t *testing.T) { IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-xyz"}}, HasSlsas: []HasSlsa{{Subject: "sha-123", BuiltBy: "GHA", BuiltFrom: []string{"sha-xyz"}}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo"}, expectedByDigest: []string{"pkg:guac/foo"}, }, { - name: "artifact -> SLSA -> artifact, artifact", + name: "digest -> SLSA -> digest, digest", data: GuacData{ Packages: []string{"pkg:guac/foo", "pkg:guac/bar"}, Artifacts: []string{"sha-123", "sha-xyz", "sha-abc"}, @@ -245,7 +243,7 @@ func Test_RetrieveDependencies(t *testing.T) { }, HasSlsas: []HasSlsa{{Subject: "sha-123", BuiltBy: "GHA", BuiltFrom: []string{"sha-xyz", "sha-abc"}}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo", "pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/foo", "pkg:guac/bar"}, }, @@ -253,7 +251,7 @@ func Test_RetrieveDependencies(t *testing.T) { * Test some edge cases *******/ { - name: "Both Package and occurrence artifact in SBOM does not lead to duplicate packages", + name: "Both Package and occurrence digest in SBOM does not lead to duplicate packages", data: GuacData{ Packages: []string{"pkg:guac/bar"}, Artifacts: []string{"sha-123", "sha-xyz"}, @@ -263,7 +261,7 @@ func Test_RetrieveDependencies(t *testing.T) { IncludedIsOccurrences: []IsOccurrence{{Subject: "pkg:guac/bar", Artifact: "sha-123"}}, }}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-xyz")}, + digest: "sha-xyz", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{"pkg:guac/bar"}, }, @@ -276,7 +274,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/baz", IncludedSoftware: []string{"pkg:guac/bar"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{}, }, @@ -289,7 +287,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/bar", IncludedSoftware: []string{"pkg:guac/baz"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/baz")}, + purl: "pkg:guac/baz", expectedByName: []string{}, expectedByDigest: []string{}, }, @@ -305,7 +303,7 @@ func Test_RetrieveDependencies(t *testing.T) { IncludedSoftware: []string{"pkg:guac/bar"}, IncludedIsOccurrences: []IsOccurrence{{Subject: "pkg:guac/bar", Artifact: "sha-123"}}, }, - // an artifact with digest sha-xyz depends on baz + // an digest with digest sha-xyz depends on baz { Subject: "sha-xyz", IncludedSoftware: []string{"pkg:guac/baz"}, @@ -314,7 +312,7 @@ func Test_RetrieveDependencies(t *testing.T) { // sha-xyz is an occurrence of bar IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/bar", Artifact: "sha-xyz"}}, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", // foo depends on baz expectedByName: []string{"pkg:guac/bar", "pkg:guac/baz"}, expectedByDigest: []string{}, @@ -329,7 +327,7 @@ func Test_RetrieveDependencies(t *testing.T) { Artifacts: []string{"sha-123"}, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-123"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{}, expectedByDigest: []string{}, }, @@ -343,7 +341,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/bar", Artifact: "sha-123"}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{}, expectedByDigest: []string{}, }, @@ -355,7 +353,7 @@ func Test_RetrieveDependencies(t *testing.T) { HashEquals: []HashEqual{{ArtifactA: "sha-123", ArtifactB: "sha-456"}}, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-456"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{}, expectedByDigest: []string{}, }, @@ -374,7 +372,7 @@ func Test_RetrieveDependencies(t *testing.T) { }, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-789"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{}, expectedByDigest: []string{}, }, @@ -390,7 +388,7 @@ func Test_RetrieveDependencies(t *testing.T) { }, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-789"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo"}, expectedByDigest: []string{"pkg:guac/foo"}, }, @@ -406,7 +404,7 @@ func Test_RetrieveDependencies(t *testing.T) { }, IsOccurrences: []IsOccurrence{{Subject: "pkg:guac/foo", Artifact: "sha-789"}}, }, - input: api.RetrieveDependenciesParams{Digest: ptrfrom.String("sha-123")}, + digest: "sha-123", expectedByName: []string{"pkg:guac/foo"}, expectedByDigest: []string{"pkg:guac/foo"}, }, @@ -422,7 +420,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/foo@v1", IncludedSoftware: []string{"pkg:guac/bar"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar@v1"}, expectedByDigest: []string{}, }, @@ -435,7 +433,7 @@ func Test_RetrieveDependencies(t *testing.T) { {Subject: "pkg:guac/foo@v1", IncludedSoftware: []string{"pkg:guac/bar@v1"}}, }, }, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:guac/bar"}, expectedByDigest: []string{}, }, @@ -453,7 +451,7 @@ func Test_RetrieveDependencies(t *testing.T) { Subject: "pkg:oci/debian@sha256%3A244fd47e07d10?repository_url=ghcr.io&tag=bullseye", IncludedSoftware: []string{"pkg:oci/static@sha256%3A244fd47e07d10?repository_url=gcr.io%2Fdistroless&tag=latest"}}, }}, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:oci/debian@sha256%3A244fd47e07d10?repository_url=ghcr.io&tag=bullseye")}, + purl: "pkg:oci/debian@sha256%3A244fd47e07d10?repository_url=ghcr.io&tag=bullseye", expectedByName: []string{"pkg:oci/static@sha256%3A244fd47e07d10?repository_url=gcr.io%2Fdistroless&tag=latest"}, expectedByDigest: []string{}, }, @@ -469,7 +467,7 @@ func Test_RetrieveDependencies(t *testing.T) { Subject: "pkg:guac/foo", IncludedSoftware: []string{"pkg:github/Package-url/purl-Spec"}}, }}, - input: api.RetrieveDependenciesParams{Purl: ptrfrom.String("pkg:guac/foo")}, + purl: "pkg:guac/foo", expectedByName: []string{"pkg:github/package-url/purl-spec"}, // lowercased expectedByDigest: []string{}, }, @@ -482,151 +480,41 @@ func Test_RetrieveDependencies(t *testing.T) { gqlClient := SetupTest(t) Ingest(ctx, t, gqlClient, tt.data) - restApi := server.NewDefaultServer(gqlClient) + encodedPurl := url.QueryEscape(tt.purl) + encodedArtifact := url.QueryEscape(tt.digest) - /******** call the endpoint with byName link condition ********/ - inputByName := tt.input - inputByName.LinkCondition = ptrfrom.Any(api.Name) - resByName, err := restApi.RetrieveDependencies(ctx, api.RetrieveDependenciesRequestObject{Params: inputByName}) - if err != nil { - t.Fatalf("Endpoint returned unexpected error: %v", err) - } - /******** check the output ********/ - switch v := resByName.(type) { - case api.RetrieveDependencies200JSONResponse: - if !cmp.Equal(v.PurlList, tt.expectedByName, cmpopts.EquateEmpty(), cmpopts.SortSlices(stdcmp.Less[string])) { - t.Errorf("RetrieveDependencies with byName returned %v, but wanted %v", v.PurlList, tt.expectedByName) + if tt.purl != "" { + /******** call the endpoint with byName link condition ********/ + resByName, err := server.GetDepsForPackage(ctx, gqlClient, encodedPurl) + if err != nil { + t.Fatalf("Endpoint returned unexpected error: %v", err) } - default: - t.Errorf("RetrieveDependencies with byName returned unexpected error: %v", v) - } - - /******** call the endpoint with byDigest link condition ********/ - inputByDigest := tt.input - inputByDigest.LinkCondition = ptrfrom.Any(api.Digest) - resByDigest, err := restApi.RetrieveDependencies(ctx, api.RetrieveDependenciesRequestObject{Params: inputByDigest}) - if err != nil { - t.Fatalf("Endpoint returned unexpected error: %v", err) - } - /******** check the output ********/ - switch v := resByDigest.(type) { - case api.RetrieveDependencies200JSONResponse: - if !cmp.Equal(v.PurlList, tt.expectedByDigest, cmpopts.EquateEmpty(), cmpopts.SortSlices(stdcmp.Less[string])) { - t.Errorf("RetrieveDependencies with byDigest returned %v, but wanted %v", v.PurlList, tt.expectedByDigest) + /******** check the output ********/ + var purls []string + for _, purl := range resByName { + purls = append(purls, purl) + } + if !cmp.Equal(purls, tt.expectedByName, cmpopts.EquateEmpty(), cmpopts.SortSlices(stdcmp.Less[string])) { + t.Errorf("RetrieveDependencies with byName returned %v, but wanted %v", purls, tt.expectedByName) } - default: - t.Errorf("RetrieveDependencies with byDigest returned unexpected error: %v", v) - } - }) - } -} - -func Test_ClientErrors(t *testing.T) { - ctx := logging.WithLogger(context.Background()) - tests := []struct { - name string - data GuacData - input api.RetrieveDependenciesParams - }{{ - name: "Package not found", - input: api.RetrieveDependenciesParams{ - Purl: ptrfrom.String("pkg:guac/foo"), - LinkCondition: ptrfrom.Any(api.Name), - }, - }, { - name: "Package not found because version was specified", - data: GuacData{Packages: []string{"pkg:guac/foo"}}, - input: api.RetrieveDependenciesParams{ - Purl: ptrfrom.String("pkg:guac/foo@v1"), - LinkCondition: ptrfrom.Any(api.Name), - }, - }, { - name: "Package not found because version was not specified", - data: GuacData{Packages: []string{"pkg:guac/foo@v1"}}, - input: api.RetrieveDependenciesParams{ - Purl: ptrfrom.String("pkg:guac/foo"), - LinkCondition: ptrfrom.Any(api.Name), - }, - }, { - name: "Package not found due to missing qualifiers", - data: GuacData{Packages: []string{"pkg:guac/foo?a=b"}}, - input: api.RetrieveDependenciesParams{ - Purl: ptrfrom.String("pkg:guac/foo"), - LinkCondition: ptrfrom.Any(api.Name), - }, - }, { - name: "Package not found due to providing qualifiers", - data: GuacData{Packages: []string{"pkg:guac/foo"}}, - input: api.RetrieveDependenciesParams{ - Purl: ptrfrom.String("pkg:guac/foo?a=b"), - LinkCondition: ptrfrom.Any(api.Name), - }, - }, { - name: "Artifact not found because version was not specified", - input: api.RetrieveDependenciesParams{ - Digest: ptrfrom.String("sha-abc"), - LinkCondition: ptrfrom.Any(api.Name), - }, - }, { - name: "Neither Purl nor Digest provided", - }, { - name: "Unrecognized link condition", - input: api.RetrieveDependenciesParams{ - Digest: ptrfrom.String("sha-abc"), - LinkCondition: ptrfrom.Any(api.RetrieveDependenciesParamsLinkCondition("foo")), - }, - }} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gqlClient := SetupTest(t) - Ingest(ctx, t, gqlClient, tt.data) - restApi := server.NewDefaultServer(gqlClient) - res, err := restApi.RetrieveDependencies(ctx, api.RetrieveDependenciesRequestObject{Params: tt.input}) - if err != nil { - t.Fatalf("RetrieveDependencies returned unexpected error: %v", err) - } - if _, ok := res.(api.RetrieveDependencies400JSONResponse); !ok { - t.Fatalf("Did not receive a 400 Response: recieved %v of type %T", res, res) } + if tt.digest != "" { + /******** call the endpoint with byDigest link condition ********/ + resByDigest, err := server.GetDepsForArtifact(ctx, gqlClient, encodedArtifact) + if err != nil { + t.Fatalf("Endpoint returned unexpected error: %v", err) + } + /******** check the output ********/ + var artifacts []string + for _, artifact := range resByDigest { + artifacts = append(artifacts, artifact) + } + if !cmp.Equal(artifacts, tt.expectedByDigest, cmpopts.EquateEmpty(), cmpopts.SortSlices(stdcmp.Less[string])) { + t.Errorf("RetrieveDependencies with byDigest returned %v, but wanted %v", artifacts, tt.expectedByDigest) + } + } }) } } - -func Test_DefaultLinkCondition(t *testing.T) { - /******** set up the test ********/ - ctx := logging.WithLogger(context.Background()) - gqlClient := SetupTest(t) - restApi := server.NewDefaultServer(gqlClient) - data := GuacData{ - Packages: []string{"pkg:guac/foo", "pkg:guac/bar"}, - HasSboms: []HasSbom{{ - Subject: "pkg:guac/foo", - IncludedSoftware: []string{"pkg:guac/bar"}}, - }} - Ingest(ctx, t, gqlClient, data) - - input := api.RetrieveDependenciesParams{ - Purl: ptrfrom.String("pkg:guac/foo"), - } - - /******** call the endpoint ********/ - res, err := restApi.RetrieveDependencies(ctx, api.RetrieveDependenciesRequestObject{Params: input}) - if err != nil { - t.Fatalf("RetrieveDependencies returned unexpected error: %v", err) - } - - /******** check the output ********/ - switch v := res.(type) { - case api.RetrieveDependencies200JSONResponse: - // that the default is byDigest is tested by asserting that no edges only in byName are used - if len(v.PurlList) != 0 { - t.Errorf("RetrieveDependencies returned %v, but no dependencies were expected", v) - } - default: - t.Errorf("RetrieveDependencies returned unexpected error: %v", v) - } - -} diff --git a/pkg/guacrest/server/searchViaArtifact.go b/pkg/guacrest/server/searchViaArtifact.go index b3e51301bd..78b5318e6b 100644 --- a/pkg/guacrest/server/searchViaArtifact.go +++ b/pkg/guacrest/server/searchViaArtifact.go @@ -17,189 +17,72 @@ package server import ( "context" - "fmt" "github.com/Khan/genqlient/graphql" model "github.com/guacsec/guac/pkg/assembler/clients/generated" gen "github.com/guacsec/guac/pkg/guacrest/generated" "github.com/guacsec/guac/pkg/guacrest/helpers" + "github.com/guacsec/guac/pkg/logging" ) -type dfsNode struct { - expanded bool - depth int - purl string -} - -// searchVulnerabilitiesViaArtifact searches for vulnerabilities associated with the given artifact. +// searchVulnerabilitiesViaArtifact searches for vulnerabilities associated with the given digest. // -// This function utilizes the searchDependenciesByArtifact function to traverse the dependencies -// of the specified artifact. It then fetches and returns vulnerabilities for each discovered package. +// This function utilizes the function GetDepsForArtifact to traverse the dependencies +// of the specified digest. It then fetches and returns vulnerabilities for each discovered package. // // Parameters: // - ctx: The context for the operation, used for cancellation and deadlines. // - gqlClient: The GraphQL client used for querying the database. -// - artifactSpec: The specification of the artifact from which to start the search. +// - artifactSpec: The specification of the digest from which to start the search. // - searchSoftware: A boolean flag indicating whether to include software components in the search. // // Returns: // - A slice of Vulnerability objects containing the found vulnerabilities. // - An error, if any occurs during the search or data retrieval. -func searchVulnerabilitiesViaArtifact(ctx context.Context, gqlClient graphql.Client, artifactSpec model.ArtifactSpec, searchSoftware bool, startSBOM model.AllHasSBOMTree) ([]gen.Vulnerability, error) { +func searchVulnerabilitiesViaArtifact(ctx context.Context, gqlClient graphql.Client, artifact string) ([]gen.Vulnerability, error) { + logger := logging.FromContext(ctx) var vulnerabilities []gen.Vulnerability - // Map to track processed vulnerabilities - processedVulns := make(map[string]bool) - - nodeMap, queue, err := searchDependenciesByArtifact(ctx, gqlClient, artifactSpec, searchSoftware, startSBOM) + pkgs, err := GetDepsForArtifact(ctx, gqlClient, artifact) if err != nil { return nil, err } - for len(queue) > 0 { - pkgID := queue[0] - queue = queue[1:] - nowNode := nodeMap[pkgID] - - // Avoid cycles. - if nowNode.expanded { - continue - } - - // Fetch vulnerabilities for the current package - certVulns, err := model.CertifyVuln(ctx, gqlClient, model.CertifyVulnSpec{ + for pkg := range pkgs { + vulns, err := model.CertifyVuln(ctx, gqlClient, model.CertifyVulnSpec{ Package: &model.PkgSpec{ - Id: &pkgID, + Id: &pkg, }, }) if err != nil { - continue + logger.Errorf("error fetching vulnerabilities from package spec: %v", err) + return nil, helpers.Err502 } - for _, certifyVuln := range certVulns.CertifyVuln { - if certifyVuln.Vulnerability.Type == "novuln" { - continue // Skip 'novuln' entries - } - if len(certifyVuln.Vulnerability.VulnerabilityIDs) == 0 { - continue // Skip if no vulnerability IDs + for _, vuln := range vulns.CertifyVuln { + vulnerability := gen.Vulnerability{ + Metadata: gen.ScanMetadata{ + Collector: &vuln.Metadata.Collector, + DbUri: &vuln.Metadata.DbUri, + DbVersion: &vuln.Metadata.DbVersion, + Origin: &vuln.Metadata.Origin, + ScannerUri: &vuln.Metadata.ScannerUri, + ScannerVersion: &vuln.Metadata.ScannerVersion, + TimeScanned: &vuln.Metadata.TimeScanned, + }, + Vulnerability: gen.VulnerabilityDetails{ + Type: &vuln.Vulnerability.Type, + }, } - purl := certifyVuln.Package.Namespaces[0].Names[0].Versions[0].Purl - - // Collect non-empty vulnerability IDs - for _, vID := range certifyVuln.Vulnerability.VulnerabilityIDs { - if vID.VulnerabilityID != "" { - // Create a unique key combining PURL and vulnerability ID - vulnKey := purl + "_" + vID.VulnerabilityID + vulnerability.Package = vuln.Package.Namespaces[0].Names[0].Versions[0].Purl - // Check if we've already processed this vulnerability for this package - if processedVulns[vulnKey] { - continue // Skip duplicates - } - - // Mark this vulnerability as processed - processedVulns[vulnKey] = true - - // Build the vulnerability object - v := gen.Vulnerability{ - Metadata: gen.ScanMetadata{ - Collector: &certifyVuln.Metadata.Collector, - DbUri: &certifyVuln.Metadata.DbUri, - DbVersion: &certifyVuln.Metadata.DbVersion, - Origin: &certifyVuln.Metadata.Origin, - ScannerUri: &certifyVuln.Metadata.ScannerUri, - ScannerVersion: &certifyVuln.Metadata.ScannerVersion, - TimeScanned: &certifyVuln.Metadata.TimeScanned, - }, - Packages: []string{purl}, - Vulnerability: gen.VulnerabilityDetails{ - Type: &certifyVuln.Vulnerability.Type, - VulnerabilityIDs: []string{vID.VulnerabilityID}, - }, - } - - vulnerabilities = append(vulnerabilities, v) - } + for _, vIDs := range vuln.Vulnerability.VulnerabilityIDs { + vulnerability.Vulnerability.VulnerabilityIDs = append(vulnerability.Vulnerability.VulnerabilityIDs, vIDs.VulnerabilityID) } - } - - // Mark node as expanded. - nowNode.expanded = true - nodeMap[pkgID] = nowNode - } - - return vulnerabilities, nil -} -// searchDependenciesByArtifact traverses the dependency graph starting from a given artifact. -// This function searches all dependencies or just dependencies in the given sbom. -// -// Parameters: -// - ctx: The context for the operation, used for cancellation and deadlines. -// - gqlClient: The GraphQL client used for querying the database. -// - artifactSpec: The specification of the artifact from which to start the dependency traversal. -// - searchSoftware: A boolean flag indicating whether to search via software components while traversing. -// -// Returns: -// - A map of package IDs to dfsNode structures representing the traversal state. -// - A queue of package IDs for further processing. -// - A map to track processed vulnerabilities to avoid duplicates. -// - An error, if any occurs during the traversal or data retrieval. -func searchDependenciesByArtifact(ctx context.Context, gqlClient graphql.Client, artifactSpec model.ArtifactSpec, searchSoftware bool, startSBOM model.AllHasSBOMTree) (map[string]dfsNode, []string, error) { - hasSBOMs, err := model.HasSBOMs(ctx, gqlClient, model.HasSBOMSpec{ - Subject: &model.PackageOrArtifactSpec{ - Artifact: &artifactSpec, - }, - }) - if err != nil { - return nil, nil, fmt.Errorf("error fetching hasSBOMs for artifact: %w", err) - } - - nodeMap := make(map[string]dfsNode) - queue := []string{} - - if searchSoftware { - hasSBOMs = &model.HasSBOMsResponse{ - HasSBOM: []model.HasSBOMsHasSBOM{ - { - AllHasSBOMTree: startSBOM, - }, - }, + vulnerabilities = append(vulnerabilities, vulnerability) } } - if !searchSoftware { - for _, hasSBOM := range hasSBOMs.HasSBOM { - for _, includedDep := range hasSBOM.IncludedDependencies { - pkg := includedDep.DependencyPackage - pkgID := pkg.Namespaces[0].Names[0].Versions[0].Id - purl := pkg.Namespaces[0].Names[0].Versions[0].Purl - nodeMap[pkgID] = dfsNode{depth: 1, purl: purl} - queue = append(queue, pkgID) - } - } - } else { - for _, hasSbom := range hasSBOMs.HasSBOM { - for _, software := range hasSbom.IncludedSoftware { - switch s := software.(type) { - case *model.AllHasSBOMTreeIncludedSoftwarePackage: - pkgID := s.Namespaces[0].Names[0].Versions[0].Id - purl := s.Namespaces[0].Names[0].Versions[0].Purl - nodeMap[pkgID] = dfsNode{depth: 1, purl: purl} - queue = append(queue, pkgID) - case *model.AllHasSBOMTreeIncludedSoftwareArtifact: - // convert artifact to pkg, then use the pkg id to get the vulnerability - pkg, err := helpers.GetPkgFromArtifact(gqlClient, s.Id) - if err != nil { - return nil, nil, fmt.Errorf("failed to get package attached to artifact %s: %w", s.Id, err) - } - pkgID := pkg.Namespaces[0].Names[0].Versions[0].Id - purl := pkg.Namespaces[0].Names[0].Versions[0].Purl - nodeMap[pkgID] = dfsNode{depth: 1, purl: purl} - queue = append(queue, pkgID) - } - } - } - } - - return nodeMap, queue, nil + return vulnerabilities, nil } diff --git a/pkg/guacrest/server/searchViaArtifact_test.go b/pkg/guacrest/server/searchViaArtifact_test.go index 399319f675..fa4169f231 100644 --- a/pkg/guacrest/server/searchViaArtifact_test.go +++ b/pkg/guacrest/server/searchViaArtifact_test.go @@ -23,327 +23,101 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" clients "github.com/guacsec/guac/internal/testing/graphqlClients" + "github.com/guacsec/guac/internal/testing/ptrfrom" model "github.com/guacsec/guac/pkg/assembler/clients/generated" gen "github.com/guacsec/guac/pkg/guacrest/generated" + "github.com/guacsec/guac/pkg/logging" ) -func TestSearchDependenciesByArtifact_BasicRetrieval(t *testing.T) { - ctx := context.Background() - gqlClient := clients.SetupTest(t) - - // Ingest a package - pkgNS := "github.com/hashicorp/consul" - pkgName := "sdk" - pkgVersion := "v1.0.0" - pkgType := "golang" - pkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: pkgType, - Namespace: &pkgNS, - Name: pkgName, - Version: &pkgVersion, - }, - } - - _, err := model.IngestPackage(ctx, gqlClient, pkgInput) - if err != nil { - t.Fatalf("Failed to ingest package: %v", err) - } - - // Ingest an artifact - artifactAlgorithm := "sha256" - artifactDigest := "6bbb0da1891646e58eb3e6a63af3a6fc3c8eb5a0d44824cba581d2e14a0450cf" - artifactInput := model.IDorArtifactInput{ - ArtifactInput: &model.ArtifactInputSpec{ - Algorithm: artifactAlgorithm, - Digest: artifactDigest, - }, - } - - _, err = model.IngestArtifact(ctx, gqlClient, artifactInput) - if err != nil { - t.Fatalf("Failed to ingest artifact: %v", err) - } - - // Create an IsOccurrence relationship between the package and the artifact - occurrenceInput := model.IsOccurrenceInputSpec{ - Justification: "test-justification", - Origin: "test-origin", - Collector: "test-collector", - } - - _, err = model.IngestIsOccurrencePkg(ctx, gqlClient, pkgInput, artifactInput, occurrenceInput) - if err != nil { - t.Fatalf("Failed to ingest IsOccurrence: %v", err) - } - - // Ingest a dependency package - depPkgNS := "github.com/hashicorp/consul-dep" - depPkgName := "dependency" - depPkgVersion := "v1.0.0-dep" - depPkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: pkgType, - Namespace: &depPkgNS, - Name: depPkgName, - Version: &depPkgVersion, - }, - } - - _, err = model.IngestPackage(ctx, gqlClient, depPkgInput) - if err != nil { - t.Fatalf("Failed to ingest dependency package: %v", err) - } - - // Create an IsDependency relationship - depPkgID, err := model.IngestIsDependency(ctx, gqlClient, pkgInput, depPkgInput, model.IsDependencyInputSpec{ - DependencyType: model.DependencyTypeDirect, - }) - if err != nil { - t.Fatalf("Failed to ingest IsDependency: %v", err) - } - - // Ingest a HasSBOM that includes the dependency - hasSBOMInput := model.HasSBOMInputSpec{ - KnownSince: time.Now(), - Origin: "test-origin", - Collector: "test-collector", - } - - _, err = model.IngestHasSBOMArtifact(ctx, gqlClient, model.IDorArtifactInput{ - ArtifactInput: &model.ArtifactInputSpec{ - Algorithm: artifactAlgorithm, - Digest: artifactDigest, - }, - }, hasSBOMInput, model.HasSBOMIncludesInputSpec{ - Dependencies: []string{depPkgID.IngestDependency}, - Packages: []string{}, - Artifacts: []string{}, - Occurrences: []string{}, - }) - if err != nil { - t.Fatalf("Failed to ingest HasSBOM: %v", err) - } - - // Prepare the artifact specification for searchDependenciesByArtifact - artifactSpec := model.ArtifactSpec{ - Algorithm: &artifactAlgorithm, - Digest: &artifactDigest, - } - - // Call searchDependenciesByArtifact - nodeMap, queue, err := searchDependenciesByArtifact(ctx, gqlClient, artifactSpec, false, model.AllHasSBOMTree{}) - if err != nil { - t.Fatalf("searchDependenciesByArtifact failed: %v", err) - } - - // Build expected results - expectedNodeMap := map[string]dfsNode{ - // Include the pkgID of the dependency package - // Assuming that the pkgID can be retrieved or is known - // For simplicity, the keys can be any placeholder strings - "pkg_dependency_id": { - depth: 1, - purl: "pkg:golang/github.com/hashicorp/consul-dep/dependency@v1.0.0-dep", - }, - } - - expectedQueue := []string{"pkg_dependency_id"} - - // Since we don't have access to the exact IDs, we'll compare the lengths and structure - if len(nodeMap) != len(expectedNodeMap) { - t.Errorf("Expected nodeMap length %d, got %d", len(expectedNodeMap), len(nodeMap)) - } - - if len(queue) != len(expectedQueue) { - t.Errorf("Expected queue length %d, got %d", len(expectedQueue), len(queue)) - } - - // Further validation can be done by comparing the PURLs in nodeMap - var actualPURLs []string - for _, node := range nodeMap { - actualPURLs = append(actualPURLs, node.purl) - } - - expectedPURLs := []string{ - "pkg:golang/github.com/hashicorp/consul-dep/dependency@v1.0.0-dep", - } - - if diff := cmp.Diff(expectedPURLs, actualPURLs); diff != "" { - t.Errorf("PURL mismatch (-expected +got):\n%s", diff) - } -} - -func TestSearchVulnerabilitiesViaArtifact_BasicRetrieval(t *testing.T) { - ctx := context.Background() - gqlClient := clients.SetupTest(t) - - // Ingest main package - pkgNS := "github.com/hashicorp/consul" - pkgName := "sdk" - pkgVersion := "v1.0.0" - pkgType := "golang" - pkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: pkgType, - Namespace: &pkgNS, - Name: pkgName, - Version: &pkgVersion, - }, - } - - _, err := model.IngestPackage(ctx, gqlClient, pkgInput) - if err != nil { - t.Fatalf("Failed to ingest package: %v", err) - } - - // Ingest artifact - artifactAlgorithm := "sha256" - artifactDigest := "6bbb0da1891646e58eb3e6a63af3a6fc3c8eb5a0d44824cba581d2e14a0450cf" - artifactInput := model.IDorArtifactInput{ - ArtifactInput: &model.ArtifactInputSpec{ - Algorithm: artifactAlgorithm, - Digest: artifactDigest, - }, - } - - _, err = model.IngestArtifact(ctx, gqlClient, artifactInput) - if err != nil { - t.Fatalf("Failed to ingest artifact: %v", err) - } - - // Create IsOccurrence relationship between package and artifact - isOccurrenceInput := model.IsOccurrenceInputSpec{ - Justification: "test-justification", - Origin: "test-origin", - Collector: "test-collector", - } - - _, err = model.IngestIsOccurrencePkg(ctx, gqlClient, pkgInput, artifactInput, isOccurrenceInput) - if err != nil { - t.Fatalf("Failed to ingest IsOccurrence: %v", err) - } - - // Ingest dependency package - depPkgNS := "github.com/hashicorp/consul-dep" - depPkgName := "dependency" - depPkgVersion := "v1.0.0-dep" - depPkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: pkgType, - Namespace: &depPkgNS, - Name: depPkgName, - Version: &depPkgVersion, - }, - } - - _, err = model.IngestPackage(ctx, gqlClient, depPkgInput) - if err != nil { - t.Fatalf("Failed to ingest dependency package: %v", err) - } - - // Create IsDependency relationship between main package and dependency package - depID, err := model.IngestIsDependency(ctx, gqlClient, pkgInput, depPkgInput, model.IsDependencyInputSpec{ - DependencyType: model.DependencyTypeDirect, - }) - if err != nil { - t.Fatalf("Failed to ingest IsDependency: %v", err) - } - - // Ingest vulnerability associated with the dependency package - vulnType := "osv" - vulnID := "osv-2022-0001" - vulnInput := model.VulnerabilityInputSpec{ - Type: vulnType, - VulnerabilityID: vulnID, - } - - vulnIDInput := model.IDorVulnerabilityInput{ - VulnerabilityInput: &vulnInput, - } - - _, err = model.IngestVulnerability(ctx, gqlClient, vulnIDInput) - if err != nil { - t.Fatalf("Failed to ingest vulnerability: %v", err) - } - - // Create CertifyVuln relationship between dependency package and vulnerability - scanner := "test-scanner" - dbURI := "https://vuln-db.example.com" - timeScanned := time.Now() - certifyVulnInput := model.ScanMetadataInput{ - TimeScanned: timeScanned, - DbUri: dbURI, - ScannerUri: scanner, - ScannerVersion: "1.0.0", - Collector: "test-collector", - Origin: "test-origin", - } - - _, err = model.IngestCertifyVulnPkg(ctx, gqlClient, depPkgInput, vulnIDInput, certifyVulnInput) - if err != nil { - t.Fatalf("Failed to ingest CertifyVuln: %v", err) - } - - // Ingest HasSBOM for the artifact including the dependency - hasSBOMInput := model.HasSBOMInputSpec{ - KnownSince: time.Now(), - Origin: "test-origin", - Collector: "test-collector", - } - - hasSBOMIncludesInput := model.HasSBOMIncludesInputSpec{ - Dependencies: []string{depID.IngestDependency}, - Packages: []string{}, - Artifacts: []string{}, - Occurrences: []string{}, - } - - // Ingest HasSBOMArtifact - _, err = model.IngestHasSBOMArtifact(ctx, gqlClient, artifactInput, hasSBOMInput, hasSBOMIncludesInput) - if err != nil { - t.Fatalf("Failed to ingest HasSBOM: %v", err) - } - - // Prepare artifact specification - artifactSpec := model.ArtifactSpec{ - Algorithm: &artifactAlgorithm, - Digest: &artifactDigest, - } - - // Call searchVulnerabilitiesViaArtifact - vulnerabilities, err := searchVulnerabilitiesViaArtifact(ctx, gqlClient, artifactSpec, false, model.AllHasSBOMTree{}) - if err != nil { - t.Fatalf("searchVulnerabilitiesViaArtifact failed: %v", err) - } - - // Define expected vulnerabilities - expectedVulnerabilities := []gen.Vulnerability{ +func TestSearchVulnerabilitiesViaArtifact(t *testing.T) { + ctx := logging.WithLogger(context.Background()) + tests := []struct { + name string + data clients.GuacData + artifact string + searchSoftware bool + startSBOM model.AllHasSBOMTree + expected []gen.Vulnerability + }{ { - Metadata: gen.ScanMetadata{ - Collector: &certifyVulnInput.Collector, - DbUri: &certifyVulnInput.DbUri, - DbVersion: &certifyVulnInput.DbVersion, - Origin: &certifyVulnInput.Origin, - ScannerUri: &certifyVulnInput.ScannerUri, - ScannerVersion: &certifyVulnInput.ScannerVersion, - TimeScanned: &certifyVulnInput.TimeScanned, - }, - Packages: []string{ - "pkg:golang/github.com/hashicorp/consul-dep/dependency@v1.0.0-dep", + name: "Basic vulnerability retrieval via digest", + data: clients.GuacData{ + Artifacts: []string{ + "sha256:abc123", + }, + Packages: []string{ + "pkg:golang/github.com/example/pkg@v1.0.0", + }, + Vulnerabilities: []string{ + "osv/osv-2023-0001", + }, + IsOccurrences: []clients.IsOccurrence{ + { + Subject: "pkg:golang/github.com/example/pkg@v1.0.0", + Artifact: "sha256:abc123", + }, + }, + CertifyVulns: []clients.CertifyVuln{ + { + Package: "pkg:golang/github.com/example/pkg@v1.0.0", + Vulnerability: "osv/osv-2023-0001", + Metadata: &model.ScanMetadataInput{ + TimeScanned: time.Now(), + DbUri: "https://example.com/vuln-db", + DbVersion: "1.0.0", + ScannerUri: "example-scanner", + ScannerVersion: "2.0.0", + Origin: "example-origin", + Collector: "example-collector", + }, + }, + }, }, - Vulnerability: gen.VulnerabilityDetails{ - Type: &vulnType, - VulnerabilityIDs: []string{ - vulnID, + artifact: "sha256%3Aabc123", // url encoded digest + searchSoftware: false, + startSBOM: model.AllHasSBOMTree{}, + expected: []gen.Vulnerability{ + { + Metadata: gen.ScanMetadata{ + TimeScanned: ptrfrom.Time(time.Now()), + DbUri: ptrfrom.String("https://example.com/vuln-db"), + DbVersion: ptrfrom.String("1.0.0"), + ScannerUri: ptrfrom.String("example-scanner"), + ScannerVersion: ptrfrom.String("2.0.0"), + Origin: ptrfrom.String("example-origin"), + Collector: ptrfrom.String("example-collector"), + }, + Package: "pkg:golang/github.com/example/pkg@v1.0.0", + Vulnerability: gen.VulnerabilityDetails{ + Type: ptrfrom.String("osv"), + VulnerabilityIDs: []string{"osv-2023-0001"}, + }, }, }, }, } - // Compare the results using cmp - if diff := cmp.Diff(expectedVulnerabilities, vulnerabilities); diff != "" { - t.Errorf("Vulnerabilities mismatch (-expected +got):\n%s", diff) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up the test + gqlClient := clients.SetupTest(t) + clients.Ingest(ctx, t, gqlClient, tt.data) + + // Call the function directly + vulnerabilities, err := searchVulnerabilitiesViaArtifact(ctx, gqlClient, tt.artifact, tt.searchSoftware) + if err != nil { + t.Fatalf("searchVulnerabilitiesViaArtifact returned unexpected error: %v", err) + } + + // Debug logging + t.Logf("Returned vulnerabilities: %+v", vulnerabilities) + + // Check the output + if diff := cmp.Diff(tt.expected, vulnerabilities, cmpopts.EquateApproxTime(time.Second)); diff != "" { + t.Errorf("searchVulnerabilitiesViaArtifact mismatch (-want +got):\n%s", diff) + } + }) } } diff --git a/pkg/guacrest/server/searchViaPkg.go b/pkg/guacrest/server/searchViaPkg.go index e9686d4c06..79255983cb 100644 --- a/pkg/guacrest/server/searchViaPkg.go +++ b/pkg/guacrest/server/searchViaPkg.go @@ -22,6 +22,8 @@ import ( model "github.com/guacsec/guac/pkg/assembler/clients/generated" gen "github.com/guacsec/guac/pkg/guacrest/generated" "github.com/guacsec/guac/pkg/guacrest/helpers" + "github.com/guacsec/guac/pkg/logging" + "net/url" ) // searchVulnerabilitiesViaPkg searches for vulnerabilities associated with the given package @@ -38,12 +40,35 @@ import ( // // The function performs a breadth-first search starting from the given package, // collecting vulnerabilities for each package and its dependencies. -func searchVulnerabilitiesViaPkg(ctx context.Context, gqlClient graphql.Client, pkgSpec model.PkgSpec, searchSoftware bool, startSBOM model.AllHasSBOMTree) ([]gen.Vulnerability, error) { +func searchVulnerabilitiesViaPkg(ctx context.Context, gqlClient graphql.Client, purl string, includeDependencies *bool) ([]gen.Vulnerability, error) { + logger := logging.FromContext(ctx) var vulnerabilities []gen.Vulnerability - pkgs, err := searchDependencies(ctx, gqlClient, pkgSpec, searchSoftware, startSBOM) + unescapedPurl, err := url.QueryUnescape(purl) if err != nil { - return nil, fmt.Errorf("error searching dependencies: %w", err) + return nil, fmt.Errorf("failed to unescape package url: %w", err) + } + pkg, err := helpers.FindPackageWithPurl(ctx, gqlClient, unescapedPurl) + if err != nil { + return nil, fmt.Errorf("failed to find package with purl: %w", err) + } + + var n node + n = &pkg + + pkgs, err := mapPkgNodesToPurls(ctx, gqlClient, []node{n}) + if err != nil { + return nil, fmt.Errorf("failed to map package nodes to purls: %w", err) + } + + if includeDependencies != nil && *includeDependencies { + dependencies, err := GetDepsForPackage(ctx, gqlClient, purl) + if err != nil { + return nil, fmt.Errorf("error searching dependencies: %w", err) + } + for k, v := range dependencies { + pkgs[k] = v + } } for pkg := range pkgs { @@ -53,7 +78,8 @@ func searchVulnerabilitiesViaPkg(ctx context.Context, gqlClient graphql.Client, }, }) if err != nil { - return nil, fmt.Errorf("error fetching vulnerabilities from package spec: %w", err) + logger.Errorf("error fetching vulnerabilities from package spec: %v", err) + return nil, helpers.Err502 } for _, vuln := range vulns.CertifyVuln { @@ -72,13 +98,7 @@ func searchVulnerabilitiesViaPkg(ctx context.Context, gqlClient graphql.Client, }, } - for _, namespace := range vuln.Package.Namespaces { - for _, name := range namespace.Names { - for _, version := range name.Versions { - vulnerability.Packages = append(vulnerability.Packages, version.Purl) - } - } - } + vulnerability.Package = vuln.Package.Namespaces[0].Names[0].Versions[0].Purl for _, vIDs := range vuln.Vulnerability.VulnerabilityIDs { vulnerability.Vulnerability.VulnerabilityIDs = append(vulnerability.Vulnerability.VulnerabilityIDs, vIDs.VulnerabilityID) @@ -90,96 +110,3 @@ func searchVulnerabilitiesViaPkg(ctx context.Context, gqlClient graphql.Client, return vulnerabilities, nil } - -func searchDependencies(ctx context.Context, gqlClient graphql.Client, pkgSpec model.PkgSpec, searchSoftware bool, startSBOM model.AllHasSBOMTree) (map[string]string, error) { - dependencies := make(map[string]string) - - pkgs, err := model.Packages(ctx, gqlClient, pkgSpec) - if err != nil { - return nil, fmt.Errorf("error searching packages: %w", err) - } - - for _, pkg := range pkgs.Packages { - for _, ns := range pkg.Namespaces { - for _, name := range ns.Names { - for _, version := range name.Versions { - dependencies[version.Id] = version.Purl - } - } - } - } - - doneFirst := false - - queue := []model.PkgSpec{pkgSpec} - depsFinished := make(map[string]bool) - - for len(queue) > 0 { - pop := queue[0] - queue = queue[1:] - - if pop.Id != nil { - if depsFinished[*pop.Id] { - continue - } - depsFinished[*pop.Id] = true - } - - hasSboms, err := model.HasSBOMs(ctx, gqlClient, model.HasSBOMSpec{ - Subject: &model.PackageOrArtifactSpec{ - Package: &pop, - }, - }) - - if err != nil { - return nil, fmt.Errorf("error fetching hasSboms from package spec %+v: %w", pop, err) - } - - if !doneFirst && searchSoftware { - hasSboms = &model.HasSBOMsResponse{ - HasSBOM: []model.HasSBOMsHasSBOM{ - { - AllHasSBOMTree: startSBOM, - }, - }, - } - } - - doneFirst = true - - if !searchSoftware { - for _, hasSbom := range hasSboms.HasSBOM { - for _, dep := range hasSbom.IncludedDependencies { - dependencies[dep.Package.Namespaces[0].Names[0].Versions[0].Id] = dep.Package.Namespaces[0].Names[0].Versions[0].Purl - queue = append(queue, model.PkgSpec{ - Id: &dep.Package.Namespaces[0].Names[0].Versions[0].Id, - }) - } - } - } else { - for _, hasSbom := range hasSboms.HasSBOM { - for _, software := range hasSbom.IncludedSoftware { - switch s := software.(type) { - case *model.AllHasSBOMTreeIncludedSoftwarePackage: - dependencies[s.Namespaces[0].Names[0].Versions[0].Id] = s.Namespaces[0].Names[0].Versions[0].Purl - queue = append(queue, model.PkgSpec{ - Id: &s.Namespaces[0].Names[0].Versions[0].Id, - }) - case *model.AllHasSBOMTreeIncludedSoftwareArtifact: - // convert artifact to pkg, then use the pkg id to get the vulnerability - pkg, err := helpers.GetPkgFromArtifact(gqlClient, s.Id) - if err != nil { - return nil, fmt.Errorf("failed to get package attached to artifact %s: %w", s.Id, err) - } - dependencies[pkg.Namespaces[0].Names[0].Versions[0].Id] = pkg.Namespaces[0].Names[0].Versions[0].Purl - queue = append(queue, model.PkgSpec{ - Id: &pkg.Namespaces[0].Names[0].Versions[0].Id, - }) - } - } - } - } - } - - return dependencies, nil -} diff --git a/pkg/guacrest/server/searchViaPkg_test.go b/pkg/guacrest/server/searchViaPkg_test.go index e97bb4044b..82299abe6e 100644 --- a/pkg/guacrest/server/searchViaPkg_test.go +++ b/pkg/guacrest/server/searchViaPkg_test.go @@ -19,208 +19,90 @@ package server import ( "context" - gen "github.com/guacsec/guac/pkg/guacrest/generated" - "log" "testing" "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" clients "github.com/guacsec/guac/internal/testing/graphqlClients" + "github.com/guacsec/guac/internal/testing/ptrfrom" model "github.com/guacsec/guac/pkg/assembler/clients/generated" + gen "github.com/guacsec/guac/pkg/guacrest/generated" + "github.com/guacsec/guac/pkg/logging" ) -// TestSearchVulnerabilitiesViaPkg_BasicRetrieval tests the searchVulnerabilitiesViaPkg function -func TestSearchVulnerabilitiesViaPkg_BasicRetrieval(t *testing.T) { - ctx := context.Background() - gqlClient := clients.SetupTest(t) - - // Ingest main package - pkgNS := "github.com/hashicorp/consul" - pkgVersion := "v1.0.0" - pkgName := "sdk" - pkgType := "golang" - pkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: pkgType, - Namespace: &pkgNS, - Name: pkgName, - Version: &pkgVersion, - }, - } - - _, err := model.IngestPackage(ctx, gqlClient, pkgInput) - if err != nil { - t.Fatalf("unable to ingest package: %v", err) - } - - // Ingest a vulnerability - vulnType := "osv" - vulnID := "osv-2022-0001" - vulnSpec := model.VulnerabilityInputSpec{ - Type: vulnType, - VulnerabilityID: vulnID, - } - - vulnInput := model.IDorVulnerabilityInput{ - VulnerabilityInput: &vulnSpec, - } - - _, err = model.IngestVulnerability(ctx, gqlClient, vulnInput) - if err != nil { - t.Fatalf("unable to ingest vulnerability: %v", err) - } - - // Ingest CertifyVuln relationship between the package and the vulnerability - scanner := "test-scanner" - dbURI := "https://vuln-db.example.com" - timeScanned := time.Now() - certifyVulnInput := model.ScanMetadataInput{ - TimeScanned: timeScanned, - DbUri: dbURI, - ScannerUri: scanner, - ScannerVersion: "1.0.0", - Collector: "test-collector", - Origin: "test-origin", - } - - _, err = model.IngestCertifyVulnPkg(ctx, gqlClient, pkgInput, vulnInput, certifyVulnInput) - if err != nil { - t.Fatalf("unable to ingest CertifyVuln: %v", err) - } - - // Prepare the package specification for searchVulnerabilitiesViaPkg - pkgSpec := model.PkgSpec{ - Type: &pkgType, - Namespace: &pkgNS, - Name: &pkgName, - Version: &pkgVersion, - } - - // Call searchVulnerabilitiesViaPkg - vulnerabilities, err := searchVulnerabilitiesViaPkg(ctx, gqlClient, pkgSpec, false, model.AllHasSBOMTree{}) - if err != nil { - t.Fatalf("searchVulnerabilitiesViaPkg failed: %v", err) - } - - // Define expected vulnerabilities - expectedVulnerabilities := []gen.Vulnerability{ +func TestSearchVulnerabilitiesViaPkg(t *testing.T) { + ctx := logging.WithLogger(context.Background()) + tests := []struct { + name string + data clients.GuacData + purl string + searchSoftware bool + startSBOM model.AllHasSBOMTree + expected []gen.Vulnerability + }{ { - Metadata: gen.ScanMetadata{ - Collector: &certifyVulnInput.Collector, - DbUri: &certifyVulnInput.DbUri, - DbVersion: &certifyVulnInput.DbVersion, - Origin: &certifyVulnInput.Origin, - ScannerUri: &certifyVulnInput.ScannerUri, - ScannerVersion: &certifyVulnInput.ScannerVersion, - TimeScanned: &certifyVulnInput.TimeScanned, - }, - Vulnerability: gen.VulnerabilityDetails{ - Type: &vulnInput.VulnerabilityInput.Type, - VulnerabilityIDs: []string{ - vulnID, + name: "Basic vulnerability retrieval", + data: clients.GuacData{ + Packages: []string{ + "pkg:golang/github.com/hashicorp/consul/sdk@v1.0.0", + }, + Vulnerabilities: []string{ + "osv/osv-2022-0001", + }, + CertifyVulns: []clients.CertifyVuln{ + { + Package: "pkg:golang/github.com/hashicorp/consul/sdk@v1.0.0", + Vulnerability: "osv/osv-2022-0001", + Metadata: &model.ScanMetadataInput{ + TimeScanned: time.Now(), + DbUri: "https://vuln-db.example.com", + DbVersion: "1.0.0", + ScannerUri: "test-scanner", + ScannerVersion: "1.0.0", + Origin: "test-origin", + Collector: "test-collector", + }, + }, }, }, - Packages: []string{ - "pkg:golang/github.com/hashicorp/consul/sdk@v1.0.0", + purl: "pkg%3Agolang%2Fgithub.com%2Fhashicorp%2Fconsul%2Fsdk%40v1.0.0", // url encoded purl + searchSoftware: false, + startSBOM: model.AllHasSBOMTree{}, + expected: []gen.Vulnerability{ + { + Metadata: gen.ScanMetadata{ + TimeScanned: ptrfrom.Time(time.Now()), + DbUri: ptrfrom.String("https://vuln-db.example.com"), + DbVersion: ptrfrom.String("1.0.0"), + ScannerUri: ptrfrom.String("test-scanner"), + ScannerVersion: ptrfrom.String("1.0.0"), + Origin: ptrfrom.String("test-origin"), + Collector: ptrfrom.String("test-collector"), + }, + Vulnerability: gen.VulnerabilityDetails{ + Type: ptrfrom.String("osv"), + VulnerabilityIDs: []string{"osv-2022-0001"}, + }, + Package: "pkg:golang/github.com/hashicorp/consul/sdk@v1.0.0", + }, }, }, } - // Compare the results using cmp - if diff := cmp.Diff(expectedVulnerabilities, vulnerabilities); diff != "" { - t.Errorf("Vulnerabilities mismatch (-expected +got):\n%s", diff) - } -} - -// Test for basic dependency retrieval -func TestSearchDependencies_BasicRetrieval(t *testing.T) { - ctx := context.Background() - gqlClient := clients.SetupTest(t) - - // Ingest main package - pkgNS := "github.com/hashicorp/consul" - pkgVersion := "v1.0.0" - pkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: "golang", - Namespace: &pkgNS, - Name: "sdk", - Version: &pkgVersion, - }, - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gqlClient := clients.SetupTest(t) + clients.Ingest(ctx, t, gqlClient, tt.data) - pkgID, err := model.IngestPackage(ctx, gqlClient, pkgInput) - if err != nil { - t.Fatalf("unable to ingest package: %v", err) - } - - // Ingest dependent package - depPkgNS := "github.com/hashicorp/consul-dep" - depPkgVersion := "v1.0.0-dep" - depPkgInput := model.IDorPkgInput{ - PackageInput: &model.PkgInputSpec{ - Type: "golang", - Namespace: &depPkgNS, - Name: "dependency", - Version: &depPkgVersion, - }, - } - - _, err = model.IngestPackage(ctx, gqlClient, depPkgInput) - if err != nil { - t.Fatalf("unable to ingest dependency package: %v", err) - } - - depID, err := model.IngestIsDependency(ctx, gqlClient, pkgInput, depPkgInput, model.IsDependencyInputSpec{ - DependencyType: model.DependencyTypeDirect, - }) - if err != nil { - t.Fatalf("unable to ingest dependency node: %v", err) - } - - // Ingest a SBOM that includes the dependency - hasSBOMInput := model.HasSBOMInputSpec{ - KnownSince: time.Now(), - } - - _, err = model.IngestHasSBOMPkg(ctx, gqlClient, model.IDorPkgInput{PackageInput: &model.PkgInputSpec{ - Type: "golang", - Namespace: &pkgNS, - Name: "sdk", - Version: &pkgVersion, - }}, hasSBOMInput, model.HasSBOMIncludesInputSpec{ - Dependencies: []string{depID.IngestDependency}, - Packages: []string{}, - Artifacts: []string{}, - Occurrences: []string{}, - }) - if err != nil { - log.Fatalf("Failed to ingest HasSBOM: %v", err) - } - - pkgType := "golang" - pkgName := "sdk" - // Prepare the package specification for searchDependencies - pkgSpec := model.PkgSpec{ - Type: &pkgType, - Namespace: &pkgNS, - Name: &pkgName, - Version: &pkgVersion, - } - - // Call searchDependencies - dependencies, err := searchDependencies(ctx, gqlClient, pkgSpec, false, model.AllHasSBOMTree{}) - if err != nil { - t.Fatalf("searchDependencies failed: %v", err) - } - - // Define expected dependencies - expectedDependencies := map[string]string{ - pkgID.IngestPackage.PackageVersionID: "pkg:golang/github.com/hashicorp/consul/sdk@v1.0.0", - } + vulnerabilities, err := searchVulnerabilitiesViaPkg(ctx, gqlClient, tt.purl, tt.searchSoftware) + if err != nil { + t.Fatalf("searchVulnerabilitiesViaPkg returned unexpected error: %v", err) + } - // Compare the results using cmp - if diff := cmp.Diff(expectedDependencies, dependencies); diff != "" { - t.Errorf("Dependencies mismatch (-expected +got):\n%s", diff) + if diff := cmp.Diff(tt.expected, vulnerabilities, cmpopts.EquateApproxTime(time.Second)); diff != "" { + t.Errorf("searchVulnerabilitiesViaPkg mismatch (-want +got):\n%s", diff) + } + }) } } diff --git a/pkg/guacrest/server/server.go b/pkg/guacrest/server/server.go index e025ffcc47..2be30fad31 100644 --- a/pkg/guacrest/server/server.go +++ b/pkg/guacrest/server/server.go @@ -20,18 +20,15 @@ import ( "fmt" "net/http" "net/url" - "strings" "time" - model "github.com/guacsec/guac/pkg/assembler/clients/generated" - - helpers2 "github.com/guacsec/guac/pkg/assembler/helpers" - "github.com/guacsec/guac/pkg/guacrest/helpers" - - "github.com/Khan/genqlient/graphql" + assemblerhelpers "github.com/guacsec/guac/pkg/assembler/helpers" "github.com/guacsec/guac/pkg/dependencies" gen "github.com/guacsec/guac/pkg/guacrest/generated" + "github.com/guacsec/guac/pkg/guacrest/helpers" "github.com/guacsec/guac/pkg/logging" + + "github.com/Khan/genqlient/graphql" ) // DefaultServer implements the API, backed by the GraphQL Server @@ -107,310 +104,90 @@ func (s *DefaultServer) AnalyzeDependencies(ctx context.Context, request gen.Ana } } -func (s *DefaultServer) GetPackagePurlsByPurl(ctx context.Context, request gen.GetPackagePurlsByPurlRequestObject) (gen.GetPackagePurlsByPurlResponseObject, error) { +func (s *DefaultServer) GetPackagePurls(ctx context.Context, request gen.GetPackagePurlsRequestObject) (gen.GetPackagePurlsResponseObject, error) { purl, err := url.QueryUnescape(request.Purl) if err != nil { - return gen.GetPackagePurlsByPurl400JSONResponse{ + return gen.GetPackagePurls400JSONResponse{ BadRequestJSONResponse: gen.BadRequestJSONResponse{ Message: fmt.Sprintf("Failed to unencode purl: %v", err), }, }, nil } - // Convert the PURL string to a PkgInputSpec - pkgInput, err := helpers2.PurlToPkg(purl) + pkgSpec, err := assemblerhelpers.PurlToPkgFilter(purl) if err != nil { - return gen.GetPackagePurlsByPurl400JSONResponse{ + return gen.GetPackagePurls400JSONResponse{ BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to parse PURL: %v", err), + Message: fmt.Sprintf("Failed to get package from purl: %v", err), }, }, nil } - pkgSpec := helpers.ConvertPkgInputSpecToPkgSpec(pkgInput) - // Retrieve package information using the helper function purls, _, err := helpers.GetPurlsForPkg(ctx, s.gqlClient, pkgSpec) if err != nil { - return gen.GetPackagePurlsByPurl500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving package info: %v", err), - }, - }, nil + return handleErr(ctx, err, GetPackagePurls).(gen.GetPackagePurlsResponseObject), nil } - result := gen.GetPackagePurlsByPurl200JSONResponse{} - result = append(result, purls...) + result := gen.GetPackagePurls200JSONResponse{} + for _, p := range purls { + result.PurlList = append(result.PurlList, p) + } - // Return the successful response with the retrieved package information return result, nil } -func (s *DefaultServer) GetPackageVulnerabilitiesByPurl(ctx context.Context, request gen.GetPackageVulnerabilitiesByPurlRequestObject) (gen.GetPackageVulnerabilitiesByPurlResponseObject, error) { - purl, err := url.QueryUnescape(request.Purl) - if err != nil { - return gen.GetPackageVulnerabilitiesByPurl400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to unencode purl: %v", err), - }, - }, nil - } - - // Convert the PURL string to a PkgInputSpec - pkgInput, err := helpers2.PurlToPkg(purl) - if err != nil { - return gen.GetPackageVulnerabilitiesByPurl400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to parse PURL: %v", err), - }, - }, nil - } - - pkgSpec := helpers.ConvertPkgInputSpecToPkgSpec(pkgInput) - - // Retrieve package information using the helper function - _, packageIDs, err := helpers.GetPurlsForPkg(ctx, s.gqlClient, pkgSpec) +func (s *DefaultServer) GetPackageVulns(ctx context.Context, request gen.GetPackageVulnsRequestObject) (gen.GetPackageVulnsResponseObject, error) { + vulns, err := searchVulnerabilitiesViaPkg(ctx, s.gqlClient, request.Purl, request.Params.IncludeDependencies) if err != nil { - return gen.GetPackageVulnerabilitiesByPurl500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving package info: %v", err), - }, - }, nil + return handleErr(ctx, err, GetPackageVulns).(gen.GetPackageVulnsResponseObject), nil } - latestSbom := &model.AllHasSBOMTree{} - shouldSearchSoftware := false - - // If the LatestSBOM query is specified then all other queries should be for the latest SBOM - if request.Params.LatestSBOM != nil && *request.Params.LatestSBOM { - latestSbom, err = helpers.LatestSBOMFromID(ctx, s.gqlClient, packageIDs) - if err != nil { - return nil, err - } - shouldSearchSoftware = true - } - - vulns, err := searchVulnerabilitiesViaPkg(ctx, s.gqlClient, pkgSpec, shouldSearchSoftware, *latestSbom) - if err != nil { - return gen.GetPackageVulnerabilitiesByPurl500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving vulnerabilities for package: %v", err), - }, - }, nil - } - - result := gen.GetPackageVulnerabilitiesByPurl200JSONResponse{ + result := gen.GetPackageVulns200JSONResponse{ VulnerabilityListJSONResponse: vulns, } - // Return the successful response with the retrieved package information return result, nil } -func (s *DefaultServer) GetPackageDependenciesByPurl(ctx context.Context, request gen.GetPackageDependenciesByPurlRequestObject) (gen.GetPackageDependenciesByPurlResponseObject, error) { - purl, err := url.QueryUnescape(request.Purl) - if err != nil { - return gen.GetPackageDependenciesByPurl400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to unencode purl: %v", err), - }, - }, nil - } - - // Convert the PURL string to a PkgInputSpec - pkgInput, err := helpers2.PurlToPkg(purl) - if err != nil { - return gen.GetPackageDependenciesByPurl400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to parse PURL: %v", err), - }, - }, nil - } - - pkgSpec := helpers.ConvertPkgInputSpecToPkgSpec(pkgInput) - - // Retrieve package IDs - _, packageIDs, err := helpers.GetPurlsForPkg(ctx, s.gqlClient, pkgSpec) - if err != nil { - return gen.GetPackageDependenciesByPurl500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving package info: %v", err), - }, - }, nil - } - - latestSbom := &model.AllHasSBOMTree{} - shouldSearchSoftware := false - - // If 'latestSBOM' is true, retrieve the latest SBOM - if request.Params.LatestSBOM != nil && *request.Params.LatestSBOM { - latestSbom, err = helpers.LatestSBOMFromID(ctx, s.gqlClient, packageIDs) - if err != nil { - return gen.GetPackageDependenciesByPurl500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving latest SBOM: %v", err), - }, - }, nil - } - shouldSearchSoftware = true - } - - // Use the searchDependencies function to retrieve deps - deps, err := searchDependencies(ctx, s.gqlClient, pkgSpec, shouldSearchSoftware, *latestSbom) +func (s *DefaultServer) GetPackageDeps(ctx context.Context, request gen.GetPackageDepsRequestObject) (gen.GetPackageDepsResponseObject, error) { + purls, err := GetDepsForPackage(ctx, s.gqlClient, request.Purl) if err != nil { - return gen.GetPackageDependenciesByPurl500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving deps: %v", err), - }, - }, nil + return handleErr(ctx, err, GetPackageDeps).(gen.GetPackageDepsResponseObject), nil } - result := gen.GetPackageDependenciesByPurl200JSONResponse{} + result := gen.GetPackageDeps200JSONResponse{} - for _, depPurl := range deps { + for _, depPurl := range purls { result.PurlList = append(result.PurlList, depPurl) } return result, nil } -func (s *DefaultServer) GetArtifactVulnerabilities(ctx context.Context, request gen.GetArtifactVulnerabilitiesRequestObject) (gen.GetArtifactVulnerabilitiesResponseObject, error) { - artifactStr, err := url.QueryUnescape(request.Artifact) - if err != nil { - return gen.GetArtifactVulnerabilities400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to unencode artifact: %v", err), - }, - }, nil - } - - // Parse the artifact string into an ArtifactSpec - parts := strings.SplitN(artifactStr, ":", 2) - if len(parts) != 2 { - return gen.GetArtifactVulnerabilities400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Invalid artifact format: %s", artifactStr), - }, - }, nil - } - algorithm := parts[0] - digest := parts[1] - - artifactSpec := model.ArtifactSpec{ - Algorithm: &algorithm, - Digest: &digest, - } - - art, err := model.Artifacts(ctx, s.gqlClient, artifactSpec) - if err != nil { - return gen.GetArtifactVulnerabilities500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving artifact: %s", artifactStr), - }, - }, nil - } - - latestSbom := &model.AllHasSBOMTree{} - shouldSearchSoftware := false - - // If 'latestSBOM' is true, retrieve the latest SBOM - if request.Params.LatestSBOM != nil && *request.Params.LatestSBOM { - latestSbom, err = helpers.LatestSBOMFromID(ctx, s.gqlClient, []string{art.Artifacts[0].Id}) - if err != nil { - return gen.GetArtifactVulnerabilities500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving latest SBOM: %v", err), - }, - }, nil - } - shouldSearchSoftware = true - } - +func (s *DefaultServer) GetArtifactVulns(ctx context.Context, request gen.GetArtifactVulnsRequestObject) (gen.GetArtifactVulnsResponseObject, error) { // Call the helper function to search for vulnerabilities - vulnerabilities, err := searchVulnerabilitiesViaArtifact(ctx, s.gqlClient, artifactSpec, shouldSearchSoftware, *latestSbom) + vulnerabilities, err := searchVulnerabilitiesViaArtifact(ctx, s.gqlClient, request.Digest) if err != nil { - logging.FromContext(ctx).Errorf("Error retrieving vulnerabilities: %v", err) - return gen.GetArtifactVulnerabilities500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving vulnerabilities: %v", err), - }, - }, nil + return handleErr(ctx, err, GetArtifactVulns).(gen.GetArtifactVulnsResponseObject), nil } - result := gen.GetArtifactVulnerabilities200JSONResponse{} + result := gen.GetArtifactVulns200JSONResponse{} result.VulnerabilityListJSONResponse = append(result.VulnerabilityListJSONResponse, vulnerabilities...) - // Return the list of vulnerabilities return result, nil } -func (s *DefaultServer) GetArtifactDependencies(ctx context.Context, request gen.GetArtifactDependenciesRequestObject) (gen.GetArtifactDependenciesResponseObject, error) { - artifactStr, err := url.QueryUnescape(request.Artifact) +func (s *DefaultServer) GetArtifactDeps(ctx context.Context, request gen.GetArtifactDepsRequestObject) (gen.GetArtifactDepsResponseObject, error) { + purls, err := GetDepsForArtifact(ctx, s.gqlClient, request.Digest) if err != nil { - return gen.GetArtifactDependencies400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Failed to unencode artifact: %v", err), - }, - }, nil - } - - // Parse the artifact string into an ArtifactSpec - parts := strings.SplitN(artifactStr, ":", 2) - if len(parts) != 2 { - return gen.GetArtifactDependencies400JSONResponse{ - BadRequestJSONResponse: gen.BadRequestJSONResponse{ - Message: fmt.Sprintf("Invalid artifact format: %s", artifactStr), - }, - }, nil - } - algorithm := parts[0] - digest := parts[1] - - artifactSpec := model.ArtifactSpec{ - Algorithm: &algorithm, - Digest: &digest, + return handleErr(ctx, err, GetArtifactDeps).(gen.GetArtifactDepsResponseObject), nil } - art, err := model.Artifacts(ctx, s.gqlClient, artifactSpec) - if err != nil { - return gen.GetArtifactDependencies500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving artifact: %s", artifactStr), - }, - }, nil - } + result := gen.GetArtifactDeps200JSONResponse{} - latestSbom := &model.AllHasSBOMTree{} - shouldSearchSoftware := false - - // If 'latestSBOM' is true, retrieve the latest SBOM - if request.Params.LatestSBOM != nil && *request.Params.LatestSBOM { - latestSbom, err = helpers.LatestSBOMFromID(ctx, s.gqlClient, []string{art.Artifacts[0].Id}) - if err != nil { - return gen.GetArtifactDependencies500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving latest SBOM: %v", err), - }, - }, nil - } - shouldSearchSoftware = true - } - - // Call the helper function to search for dependencies - deps, _, err := searchDependenciesByArtifact(ctx, s.gqlClient, artifactSpec, shouldSearchSoftware, *latestSbom) - if err != nil { - logging.FromContext(ctx).Errorf("Error retrieving vulnerabilities: %v", err) - return gen.GetArtifactDependencies500JSONResponse{ - InternalServerErrorJSONResponse: gen.InternalServerErrorJSONResponse{ - Message: fmt.Sprintf("Error retrieving vulnerabilities: %v", err), - }, - }, nil - } - - result := gen.GetArtifactDependencies200JSONResponse{} - - for _, dep := range deps { - result.PurlList = append(result.PurlList, dep.purl) + for _, depPurl := range purls { + result.PurlList = append(result.PurlList, depPurl) } return result, nil diff --git a/pkg/guacrest/server/server_test.go b/pkg/guacrest/server/server_test.go new file mode 100644 index 0000000000..d7bb3944b1 --- /dev/null +++ b/pkg/guacrest/server/server_test.go @@ -0,0 +1,130 @@ +package server + +import ( + "context" + gen "github.com/guacsec/guac/pkg/guacrest/generated" + "testing" + + . "github.com/guacsec/guac/internal/testing/graphqlClients" + _ "github.com/guacsec/guac/pkg/assembler/backends/keyvalue" + "github.com/guacsec/guac/pkg/logging" +) + +func Test_ClientErrors(t *testing.T) { + ctx := logging.WithLogger(context.Background()) + tests := []struct { + name string + data GuacData + purl string + digest string + }{{ + name: "Package not found", + purl: "pkg:guac/foo", + }, { + name: "Package not found because version was specified", + data: GuacData{Packages: []string{"pkg:guac/foo"}}, + purl: "pkg:guac/foo@v1", + }, { + name: "Package not found because version was not specified", + data: GuacData{Packages: []string{"pkg:guac/foo@v1"}}, + purl: "pkg:guac/foo", + }, { + name: "Package not found due to missing qualifiers", + data: GuacData{Packages: []string{"pkg:guac/foo?a=b"}}, + purl: "pkg:guac/foo", + }, { + name: "Package not found due to providing qualifiers", + data: GuacData{Packages: []string{"pkg:guac/foo"}}, + purl: "pkg:guac/foo?a=b", + }, { + name: "Artifact not found because version was not specified", + digest: "sha-abc", + }, { + name: "Neither Purl nor Digest provided", + }, { + name: "Unrecognized link condition", + digest: "sha-abc", + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gqlClient := SetupTest(t) + Ingest(ctx, t, gqlClient, tt.data) + + restApi := NewDefaultServer(gqlClient) + + if tt.purl != "" { + res, err := restApi.GetPackageDeps(ctx, gen.GetPackageDepsRequestObject{ + Purl: tt.purl, + }) + if err != nil { + t.Fatalf("GetPackageDeps returned unexpected error: %v", err) + } + if !isBadRequestResponse(res) { + t.Fatalf("Did not receive a 400 Response: received %v of type %T", res, res) + } + } + + if tt.digest != "" { + res, err := restApi.GetArtifactDeps(ctx, gen.GetArtifactDepsRequestObject{ + Digest: tt.digest, + }) + if err != nil { + t.Fatalf("GetArtifactDeps returned unexpected error: %v", err) + } + if !isBadRequestResponse(res) { + t.Fatalf("Did not receive a 400 Response: received %v of type %T", res, res) + } + } + }) + } +} + +func Test_DefaultLinkCondition(t *testing.T) { + /******** set up the test ********/ + ctx := logging.WithLogger(context.Background()) + gqlClient := SetupTest(t) + restApi := NewDefaultServer(gqlClient) + data := GuacData{ + Packages: []string{"pkg:guac/foo", "pkg:guac/bar"}, + HasSboms: []HasSbom{{ + Subject: "pkg:guac/foo", + IncludedSoftware: []string{"pkg:guac/bar"}}, + }} + Ingest(ctx, t, gqlClient, data) + + /******** call the endpoint ********/ + res, err := restApi.GetPackageDeps(ctx, gen.GetPackageDepsRequestObject{ + Purl: "pkg:guac/foo", + }) + if err != nil { + t.Fatalf("RetrieveDependencies returned unexpected error: %v", err) + } + + /******** check the output ********/ + if res, ok := res.(gen.GetPackageDepsResponseObject); ok { + switch res.(type) { + case gen.GetPackageDeps200JSONResponse: + case gen.GetPackageDeps400JSONResponse, gen.GetPackageDeps500JSONResponse, gen.GetPackageDeps502JSONResponse: + t.Fatalf("Did not receive a 200 Response: received %v of type %T", res, res) + default: + t.Fatalf("Unexpected response type: %T", res) + } + } else { + t.Fatalf("Response is not of type GetPackageDepsResponseObject: %T", res) + } +} + +// Helper function to check if the response is a 400 error +func isBadRequestResponse(res interface{}) bool { + switch res.(type) { + case gen.GetPackagePurls400JSONResponse, + gen.GetPackageDeps400JSONResponse, + gen.GetPackageVulns400JSONResponse, + gen.GetArtifactDeps400JSONResponse, + gen.GetArtifactVulns400JSONResponse: + return true + default: + return false + } +}