Compare commits

...
27 Commits
Author SHA1 Message Date
Alex Goodman 6d5ff0fd8e Mark package relations by file ownership (#329)
* add marking package relations by file ownership

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* correct json schema version; ensure fileOwners dont return dups; pin test pkg versions

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* extract package relationships into separate section

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* pull in client-go features for import of PackageRelationships

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* move unit test for ownership by files relationship further down

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* rename relationship to "ownership-by-file-overlap"

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-02-25 13:47:13 -05:00
Alfredo Deza c5e3b631ac Merge pull request #328 from anchore/issue-327
Be lenient on invalid fields in PKG-INFO
2021-02-17 08:14:18 -05:00
Alfredo Deza 05b9d35ee6 python: log a warning when invalid fields in PKG-INFO are found
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2021-02-16 15:27:08 -05:00
Alfredo Deza 555376e250 python: test leniency with invalid fields in PKG-INFO
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2021-02-16 14:20:07 -05:00
Dan Luhring fee878028a Merge pull request #324 from anchore/etui-handle-signals
Add handling of interrupting signals to the UI
2021-02-12 09:21:16 -05:00
Dan Luhring 5370daf027 Add handling of interrupting signals to ETUI
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-11 22:29:43 -05:00
Alfredo Deza 8d838b18a8 Merge pull request #320 from anchore/pin-cache-action
Pin actions/cache to v2.1.3
2021-02-05 11:26:40 -05:00
Dan Luhring bf2d5ed87e Pin actions/cache to v2.1.3
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-05 11:15:53 -05:00
Alfredo Deza a350e5889b Merge pull request #319 from anchore/bust-ci-cache
make: add a bootstrap cache buster
2021-02-05 10:47:54 -05:00
Alfredo Deza e4393e7a3d make: add a bootstrap cache buster
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2021-02-05 10:17:09 -05:00
Dan Luhring 26d1975870 Merge pull request #318 from anchore/allow-path-prefixes
Allow path prefixes
2021-02-02 10:30:34 -05:00
Dan Luhring babb09b3a4 Refactor and improve base URL prep for client
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-02 09:54:00 -05:00
Dan Luhring b207bc8ee2 Ensure upload base path ends in /v1
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-01 16:59:23 -05:00
Dan Luhring 5b5fa7ec90 Add tests for Anchore client URL intake
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-01 13:57:40 -05:00
Dan Luhring 6d730d24dd Lean on built-in URL parsing to enable path prefix
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-01 13:48:25 -05:00
Dan Luhring b22fd987db Merge pull request #316 from anchore/fix-zero-layers-panic
Bring in fix from stereoscope for zero-layers panic
2021-02-01 12:03:28 -05:00
Dan Luhring 3b4d4b186f Bring in fix to zero layers panic from stereoscope
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-02-01 11:54:30 -05:00
Dan Luhring 407769e88c Merge pull request #314 from anchore/issue-291-java-parent-ref
Ensure java parent pkg ref isn't nil when looking for parent matches
2021-01-27 11:51:40 -05:00
Dan Luhring 1416e3cb7a Invert if statement to reduce nesting in archive parser
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-27 10:34:52 -05:00
Dan Luhring 0ccfee03f1 Fix dependency resolution issue in test image
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-27 10:30:49 -05:00
Alex Goodman bb1facbf81 ensure java parent pkg ref isnt nil when looking for parent matches
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-27 10:12:41 -05:00
Dan Luhring ae7cd6bbb7 Add test for Java cataloging with no main package
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-27 10:12:41 -05:00
Dan Luhring 7bcdafe745 Merge pull request #312 from anchore/omit-empty-packages
Omit empty packages
2021-01-22 16:10:06 -05:00
Dan Luhring 4576c081b9 Improve output for test case
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 15:20:42 -05:00
Dan Luhring d5779a9822 Clarify debug message for package.json omissions
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 15:20:24 -05:00
Dan Luhring 9ec3ad58c8 Update regression test expected value and pin deps
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 14:03:50 -05:00
Dan Luhring 176dfdd9c1 Don't create packages unless package.json has name and version
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 13:56:54 -05:00
54 changed files with 2195 additions and 156 deletions
+6 -10
View File
@@ -1,4 +1,4 @@
name: 'Acceptance'
name: "Acceptance"
on:
workflow_dispatch:
push:
@@ -16,7 +16,6 @@ jobs:
Build-Snapshot-Artifacts:
runs-on: macos-latest # We're creating these snapshot builds on macOS to be consistent with our release workflow's build process, which also takes place on macOS (due to code signing requirements).
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
@@ -25,7 +24,7 @@ jobs:
- name: Restore bootstrap cache
id: cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: |
~/go/pkg/mod
@@ -75,11 +74,10 @@ jobs:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Acceptance-Linux:
needs: [ Build-Snapshot-Artifacts ]
needs: [Build-Snapshot-Artifacts]
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
@@ -101,10 +99,9 @@ jobs:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Acceptance-Mac:
needs: [ Build-Snapshot-Artifacts ]
needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
@@ -126,11 +123,10 @@ jobs:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Inline-Compare:
needs: [ Build-Snapshot-Artifacts ]
needs: [Build-Snapshot-Artifacts]
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Fingerprint inline-compare sources
@@ -138,7 +134,7 @@ jobs:
- name: Restore inline reports cache
id: cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/test/inline-compare/inline-reports
key: inline-reports-${{ hashFiles('**/inline-compare.fingerprint') }}
+5 -7
View File
@@ -1,12 +1,12 @@
name: 'Release'
name: "Release"
on:
push:
# take no actions on push to any branch...
branches-ignore:
- '**'
- "**"
# ... only act on release tags
tags:
- 'v*'
- "v*"
env:
GO_VERSION: "1.14.x"
@@ -15,7 +15,6 @@ jobs:
wait-for-checks:
runs-on: ubuntu-latest # This OS choice is arbitrary. None of the steps in this job are specific to either Linux or macOS.
steps:
- uses: actions/checkout@v2
# we don't want to release commits that have been pushed and tagged, but not necessarily merged onto main
@@ -81,10 +80,9 @@ jobs:
false
release:
needs: [ wait-for-checks ]
needs: [wait-for-checks]
runs-on: macos-latest # Due to our code signing process, it's vital that we run our release steps on macOS.
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
@@ -96,7 +94,7 @@ jobs:
# We are expecting this cache to have been created during the "Build-Snapshot-Artifacts" job in the "Acceptance" workflow.
- name: Restore bootstrap cache
id: cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: |
~/go/pkg/mod
@@ -1,10 +1,9 @@
name: 'Static Analysis + Unit + Integration'
name: "Static Analysis + Unit + Integration"
on:
workflow_dispatch:
push:
pull_request:
jobs:
Static-Analysis:
strategy:
matrix:
@@ -12,7 +11,6 @@ jobs:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
@@ -21,7 +19,7 @@ jobs:
- name: Restore bootstrap cache
id: bootstrap-cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: |
~/go/pkg/mod
@@ -49,7 +47,6 @@ jobs:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
@@ -58,7 +55,7 @@ jobs:
- name: Restore bootstrap cache
id: bootstrap-cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: |
~/go/pkg/mod
@@ -80,7 +77,7 @@ jobs:
- name: Restore Java test-fixture cache
id: unit-java-cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: syft/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
@@ -95,7 +92,7 @@ jobs:
run: make integration-fingerprint
- name: Restore integration test cache
uses: actions/cache@v2
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/integration/test-fixtures/cache
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('integration/test-fixtures/cache.fingerprint') }}
+1
View File
@@ -19,6 +19,7 @@ COVERAGE_THRESHOLD := 68
# CI cache busting values; change these if you want CI to not use previous stored cache
COMPARE_CACHE_BUSTER="f7e689d76a9"
INTEGRATION_CACHE_BUSTER="789bacdf"
BOOTSTRAP_CACHE="789bacdf"
## Build variables
DISTDIR=./dist
+2 -11
View File
@@ -158,22 +158,13 @@ func doImport(src source.Source, s source.Metadata, catalog *pkg.Catalog, d *dis
}
}
var scheme string
var hostname = appConfig.Anchore.Host
urlFields := strings.Split(hostname, "://")
if len(urlFields) > 1 {
scheme = urlFields[0]
hostname = urlFields[1]
}
c, err := anchore.NewClient(anchore.Configuration{
Hostname: hostname,
BaseURL: appConfig.Anchore.Host,
Username: appConfig.Anchore.Username,
Password: appConfig.Anchore.Password,
Scheme: scheme,
})
if err != nil {
return fmt.Errorf("failed to create anchore client: %+v", err)
return fmt.Errorf("unable to upload results: %w", err)
}
importCfg := anchore.ImportConfig{
+2 -2
View File
@@ -5,11 +5,11 @@ go 1.14
require (
github.com/adrg/xdg v0.2.1
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921
github.com/anchore/client-go v0.0.0-20201216213038-a486b838e238
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/stereoscope v0.0.0-20210105001222-7beea73cb7e5
github.com/anchore/stereoscope v0.0.0-20210201165248-e94c52b4052d
github.com/antihax/optional v1.0.0
github.com/bmatcuk/doublestar/v2 v2.0.4
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
+5 -8
View File
@@ -126,20 +126,16 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anchore/client-go v0.0.0-20201216213038-a486b838e238 h1:/iI+1cj1a27ow0wj378pPJIm8sCSy6I21Tz6oLbLDQY=
github.com/anchore/client-go v0.0.0-20201216213038-a486b838e238/go.mod h1:FaODhIA06mxO1E6R32JE0TL1JWZZkmjRIAd4ULvHUKk=
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf/go.mod h1:FaODhIA06mxO1E6R32JE0TL1JWZZkmjRIAd4ULvHUKk=
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12 h1:xbeIbn5F52JVx3RUIajxCj8b0y+9lywspql4sFhcxWQ=
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12/go.mod h1:juoyWXIj7sJ1IDl4E/KIfyLtovbs5XQVSIdaQifFQT8=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/stereoscope v0.0.0-20210104203718-4c1d1bd9a255 h1:Ng7BDr9PQTCztANogjfEdEjjWUylhlPyZPhtarIGo00=
github.com/anchore/stereoscope v0.0.0-20210104203718-4c1d1bd9a255/go.mod h1:BMdPL0QEIYfpjQ3M7sHYZvuh6+vcomqF3TMHL8gr6Vw=
github.com/anchore/stereoscope v0.0.0-20210105000809-428eda0b2ec6 h1:JWpsV/8x1fuCYjJmNjT43cVFblLTpO/ISDnePukiTNw=
github.com/anchore/stereoscope v0.0.0-20210105000809-428eda0b2ec6/go.mod h1:BMdPL0QEIYfpjQ3M7sHYZvuh6+vcomqF3TMHL8gr6Vw=
github.com/anchore/stereoscope v0.0.0-20210105001222-7beea73cb7e5 h1:NGRfS6BZKElgiMbqdoH9iQn+6oxT7CJdZYrqgwvGkWY=
github.com/anchore/stereoscope v0.0.0-20210105001222-7beea73cb7e5/go.mod h1:BMdPL0QEIYfpjQ3M7sHYZvuh6+vcomqF3TMHL8gr6Vw=
github.com/anchore/stereoscope v0.0.0-20210201165248-e94c52b4052d h1:2hv5NOZ0fD8tPk1UdGiW9PHxmjBmBLL+sFlhLXjjKgo=
github.com/anchore/stereoscope v0.0.0-20210201165248-e94c52b4052d/go.mod h1:lhSEYyGLXTXMIFHAz7Ls/MNQ5EjYd5ziLxovKZp1xOs=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -849,6 +845,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d h1:KOxOL6qpmqwoPloNwi+CEgc1ayjHNOFNrvoOmeDOjDg=
+63 -6
View File
@@ -2,7 +2,11 @@ package anchore
import (
"context"
"errors"
"fmt"
"path"
"strings"
"unicode"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/internal"
@@ -10,11 +14,10 @@ import (
)
type Configuration struct {
Hostname string
BaseURL string
Username string
Password string
UserAgent string
Scheme string
}
type Client struct {
@@ -29,16 +32,16 @@ func NewClient(cfg Configuration) (*Client, error) {
cfg.UserAgent = fmt.Sprintf("%s / %s %s", internal.ApplicationName, versionInfo.Version, versionInfo.Platform)
}
if cfg.Scheme == "" {
cfg.Scheme = "https"
baseURL, err := prepareBaseURLForClient(cfg.BaseURL)
if err != nil {
return nil, fmt.Errorf("unable to create client: %w", err)
}
return &Client{
config: cfg,
client: external.NewAPIClient(&external.Configuration{
Host: cfg.Hostname,
BasePath: baseURL,
UserAgent: cfg.UserAgent,
Scheme: cfg.Scheme,
}),
}, nil
}
@@ -56,3 +59,57 @@ func (c *Client) newRequestContext(parentContext context.Context) context.Contex
},
)
}
var ErrInvalidBaseURLInput = errors.New("invalid base URL input")
func prepareBaseURLForClient(baseURL string) (string, error) {
if err := checkBaseURLInput(baseURL); err != nil {
return "", err
}
scheme, urlWithoutScheme := splitSchemeFromURL(baseURL)
if scheme == "" {
scheme = "http"
}
urlWithoutScheme = path.Clean(urlWithoutScheme)
const requiredSuffix = "v1"
if path.Base(urlWithoutScheme) != requiredSuffix {
urlWithoutScheme = path.Join(urlWithoutScheme, requiredSuffix)
}
preparedBaseURL := scheme + "://" + urlWithoutScheme
return preparedBaseURL, nil
}
func checkBaseURLInput(url string) error {
if url == "" {
return ErrInvalidBaseURLInput
}
firstCharacter := rune(url[0])
if !(unicode.IsLetter(firstCharacter)) {
return ErrInvalidBaseURLInput
}
return nil
}
func splitSchemeFromURL(url string) (scheme, urlWithoutScheme string) {
if hasScheme(url) {
urlParts := strings.SplitN(url, "://", 2)
scheme = urlParts[0]
urlWithoutScheme = urlParts[1]
return
}
return "", url
}
func hasScheme(url string) bool {
parts := strings.Split(url, "://")
return len(parts) > 1
}
+210
View File
@@ -0,0 +1,210 @@
package anchore
import "testing"
func TestHasScheme(t *testing.T) {
cases := []struct {
url string
expected bool
}{
{
url: "http://localhost",
expected: true,
},
{
url: "https://anchore.com:8443",
expected: true,
},
{
url: "google.com",
expected: false,
},
{
url: "",
expected: false,
},
}
for _, testCase := range cases {
t.Run(testCase.url, func(t *testing.T) {
result := hasScheme(testCase.url)
if testCase.expected != result {
t.Errorf("expected %t but got %t", testCase.expected, result)
}
})
}
}
func TestPrepareBaseURLForClient(t *testing.T) {
cases := []struct {
inputURL string
expectedURL string
expectedErr error
}{
{
inputURL: "",
expectedURL: "",
expectedErr: ErrInvalidBaseURLInput,
},
{
inputURL: "localhost",
expectedURL: "http://localhost/v1",
expectedErr: nil,
},
{
inputURL: "https://localhost",
expectedURL: "https://localhost/v1",
expectedErr: nil,
},
{
inputURL: "https://localhost/",
expectedURL: "https://localhost/v1",
expectedErr: nil,
},
{
inputURL: "https://localhost/v1/",
expectedURL: "https://localhost/v1",
expectedErr: nil,
},
{
inputURL: "https://localhost/v1//",
expectedURL: "https://localhost/v1",
expectedErr: nil,
},
{
inputURL: "http://something.com/platform/v1/services/anchore",
expectedURL: "http://something.com/platform/v1/services/anchore/v1",
expectedErr: nil,
},
{
inputURL: "my-host:8228",
expectedURL: "http://my-host:8228/v1",
expectedErr: nil,
},
{
inputURL: "v1/v1",
expectedURL: "http://v1/v1",
expectedErr: nil,
},
{
inputURL: "/v1",
expectedURL: "",
expectedErr: ErrInvalidBaseURLInput,
},
{
inputURL: "/imports/images",
expectedURL: "",
expectedErr: ErrInvalidBaseURLInput,
},
}
for _, testCase := range cases {
t.Run(testCase.inputURL, func(t *testing.T) {
resultURL, err := prepareBaseURLForClient(testCase.inputURL)
if err != testCase.expectedErr {
t.Errorf("expected err to be '%v' but got '%v'", testCase.expectedErr, err)
}
if resultURL != testCase.expectedURL {
t.Errorf("expected URL to be '%v' but got '%v'", testCase.expectedURL, resultURL)
}
})
}
}
func TestCheckBaseURLInput(t *testing.T) {
cases := []struct {
input string
expected error
}{
{
input: "",
expected: ErrInvalidBaseURLInput,
},
{
input: "x",
expected: nil,
},
{
input: "localhost:8000",
expected: nil,
},
{
input: ":80",
expected: ErrInvalidBaseURLInput,
},
{
input: "/v1",
expected: ErrInvalidBaseURLInput,
},
}
for _, testCase := range cases {
t.Run(testCase.input, func(t *testing.T) {
resultErr := checkBaseURLInput(testCase.input)
if testCase.expected != resultErr {
t.Errorf("expected err to be '%v' but got '%v'", testCase.expected, resultErr)
}
})
}
}
func TestSplitSchemeFromURL(t *testing.T) {
cases := []struct {
input string
expectedScheme string
expectedURLWithoutScheme string
}{
{
input: "",
expectedScheme: "",
expectedURLWithoutScheme: "",
},
{
input: "localhost",
expectedScheme: "",
expectedURLWithoutScheme: "localhost",
},
{
input: "https://anchore.com/path",
expectedScheme: "https",
expectedURLWithoutScheme: "anchore.com/path",
},
{
input: "tcp://host:1234",
expectedScheme: "tcp",
expectedURLWithoutScheme: "host:1234",
},
{
input: "/hello",
expectedScheme: "",
expectedURLWithoutScheme: "/hello",
},
{
input: "://host",
expectedScheme: "",
expectedURLWithoutScheme: "host",
},
{
input: "http//localhost",
expectedScheme: "",
expectedURLWithoutScheme: "http//localhost",
},
}
for _, testCase := range cases {
t.Run(testCase.input, func(t *testing.T) {
resultScheme, resultURLWithoutScheme := splitSchemeFromURL(testCase.input)
if testCase.expectedScheme != resultScheme {
t.Errorf("expected scheme to be '%s' but got '%s'", testCase.expectedScheme, resultScheme)
}
if testCase.expectedURLWithoutScheme != resultURLWithoutScheme {
t.Errorf("expected urlWithoutScheme to be '%s' but got '%s'", testCase.expectedURLWithoutScheme, resultURLWithoutScheme)
}
})
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ func importProgress(source string) (*progress.Stage, *progress.Manual) {
// nolint:funlen
func (c *Client) Import(ctx context.Context, cfg ImportConfig) error {
stage, prog := importProgress(c.config.Hostname)
stage, prog := importProgress(c.config.BaseURL)
ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
@@ -107,6 +107,10 @@ func TestPackageSbomToModel(t *testing.T) {
}
for _, d := range deep.Equal(actualDoc, expectedDoc) {
if strings.HasSuffix(d, "<nil slice> != []") {
// do not consider nil vs empty collection semantics as a "difference"
continue
}
t.Errorf("diff: %+v", d)
}
}
+1 -1
View File
@@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON presenter
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "1.0.1"
JSONSchemaVersion = "1.0.2"
)
+19
View File
@@ -23,7 +23,9 @@ import (
"context"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/anchore/syft/internal/logger"
@@ -102,6 +104,8 @@ func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscr
ctx := context.Background()
syftUIHandler := ui.NewHandler()
signals := interruptingSignals()
eventLoop:
for {
select {
@@ -154,8 +158,23 @@ eventLoop:
log.Errorf("cancelled (%+v)", err)
}
break eventLoop
case <-signals:
break eventLoop
}
}
return nil
}
func interruptingSignals() chan os.Signal {
c := make(chan os.Signal, 1) // Note: A buffered channel is recommended for this; see https://golang.org/pkg/os/signal/#Notify
interruptions := []os.Signal{
syscall.SIGINT,
syscall.SIGTERM,
}
signal.Notify(c, interruptions...)
return c
}
+718
View File
@@ -0,0 +1,718 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Document",
"definitions": {
"ApkFileRecord": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"ownerUid": {
"type": "string"
},
"ownerGid": {
"type": "string"
},
"permissions": {
"type": "string"
},
"checksum": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"ApkMetadata": {
"required": [
"package",
"originPackage",
"maintainer",
"version",
"license",
"architecture",
"url",
"description",
"size",
"installedSize",
"pullDependencies",
"pullChecksum",
"gitCommitOfApkPort",
"files"
],
"properties": {
"package": {
"type": "string"
},
"originPackage": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"version": {
"type": "string"
},
"license": {
"type": "string"
},
"architecture": {
"type": "string"
},
"url": {
"type": "string"
},
"description": {
"type": "string"
},
"size": {
"type": "integer"
},
"installedSize": {
"type": "integer"
},
"pullDependencies": {
"type": "string"
},
"pullChecksum": {
"type": "string"
},
"gitCommitOfApkPort": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/ApkFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Descriptor": {
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Distribution": {
"required": [
"name",
"version",
"idLike"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"idLike": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Document": {
"required": [
"artifacts",
"source",
"distro",
"descriptor",
"schema",
"artifactRelationships"
],
"properties": {
"artifacts": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Package"
},
"type": "array"
},
"source": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Source"
},
"distro": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Distribution"
},
"descriptor": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Descriptor"
},
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Schema"
},
"artifactRelationships": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Relationship"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"DpkgFileRecord": {
"required": [
"path",
"md5"
],
"properties": {
"path": {
"type": "string"
},
"md5": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"DpkgMetadata": {
"required": [
"package",
"source",
"version",
"sourceVersion",
"architecture",
"maintainer",
"installedSize",
"files"
],
"properties": {
"package": {
"type": "string"
},
"source": {
"type": "string"
},
"version": {
"type": "string"
},
"sourceVersion": {
"type": "string"
},
"architecture": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"installedSize": {
"type": "integer"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/DpkgFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"GemMetadata": {
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"authors": {
"items": {
"type": "string"
},
"type": "array"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"homepage": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"JavaManifest": {
"properties": {
"main": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
},
"namedSections": {
"patternProperties": {
".*": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"JavaMetadata": {
"required": [
"virtualPath"
],
"properties": {
"virtualPath": {
"type": "string"
},
"manifest": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/JavaManifest"
},
"pomProperties": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PomProperties"
}
},
"additionalProperties": false,
"type": "object"
},
"Location": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"layerID": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"NpmPackageJSONMetadata": {
"required": [
"author",
"licenses",
"homepage",
"description",
"url"
],
"properties": {
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"author": {
"type": "string"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"homepage": {
"type": "string"
},
"description": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Package": {
"required": [
"id",
"name",
"version",
"type",
"foundBy",
"locations",
"licenses",
"language",
"cpes",
"purl",
"metadataType",
"metadata"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"type": {
"type": "string"
},
"foundBy": {
"type": "string"
},
"locations": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Location"
},
"type": "array"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"language": {
"type": "string"
},
"cpes": {
"items": {
"type": "string"
},
"type": "array"
},
"purl": {
"type": "string"
},
"metadataType": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"$ref": "#/definitions/ApkMetadata"
},
{
"$ref": "#/definitions/DpkgMetadata"
},
{
"$ref": "#/definitions/GemMetadata"
},
{
"$ref": "#/definitions/JavaMetadata"
},
{
"$ref": "#/definitions/NpmPackageJSONMetadata"
},
{
"$ref": "#/definitions/PythonPackageMetadata"
},
{
"$ref": "#/definitions/RpmdbMetadata"
}
]
}
},
"additionalProperties": false,
"type": "object"
},
"PomProperties": {
"required": [
"path",
"name",
"groupId",
"artifactId",
"version",
"extraFields"
],
"properties": {
"path": {
"type": "string"
},
"name": {
"type": "string"
},
"groupId": {
"type": "string"
},
"artifactId": {
"type": "string"
},
"version": {
"type": "string"
},
"extraFields": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonFileDigest": {
"required": [
"algorithm",
"value"
],
"properties": {
"algorithm": {
"type": "string"
},
"value": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonFileRecord": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"digest": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PythonFileDigest"
},
"size": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonPackageMetadata": {
"required": [
"name",
"version",
"license",
"author",
"authorEmail",
"platform",
"sitePackagesRootPath"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"license": {
"type": "string"
},
"author": {
"type": "string"
},
"authorEmail": {
"type": "string"
},
"platform": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PythonFileRecord"
},
"type": "array"
},
"sitePackagesRootPath": {
"type": "string"
},
"topLevelPackages": {
"items": {
"type": "string"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Relationship": {
"required": [
"parent",
"child",
"type",
"metadata"
],
"properties": {
"parent": {
"type": "string"
},
"child": {
"type": "string"
},
"type": {
"type": "string"
},
"metadata": {
"additionalProperties": true
}
},
"additionalProperties": false,
"type": "object"
},
"RpmdbFileRecord": {
"required": [
"path",
"mode",
"size",
"sha256"
],
"properties": {
"path": {
"type": "string"
},
"mode": {
"type": "integer"
},
"size": {
"type": "integer"
},
"sha256": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"RpmdbMetadata": {
"required": [
"name",
"version",
"epoch",
"architecture",
"release",
"sourceRpm",
"size",
"license",
"vendor",
"files"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"epoch": {
"type": "integer"
},
"architecture": {
"type": "string"
},
"release": {
"type": "string"
},
"sourceRpm": {
"type": "string"
},
"size": {
"type": "integer"
},
"license": {
"type": "string"
},
"vendor": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/RpmdbFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Schema": {
"required": [
"version",
"url"
],
"properties": {
"version": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Source": {
"required": [
"type",
"target"
],
"properties": {
"type": {
"type": "string"
},
"target": {
"additionalProperties": true
}
},
"additionalProperties": false,
"type": "object"
}
}
}
+2 -1
View File
@@ -5,12 +5,13 @@ package apkdb
import (
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
)
// NewApkdbCataloger returns a new Alpine DB cataloger object.
func NewApkdbCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/lib/apk/db/installed": parseApkDB,
pkg.ApkDbGlob: parseApkDB,
}
return common.NewGenericCataloger(nil, globParsers, "apkdb-cataloger")
+1
View File
@@ -39,6 +39,7 @@ func newMonitor() (*progress.Manual, *progress.Manual) {
// request.
func Catalog(resolver source.Resolver, theDistro *distro.Distro, catalogers ...Cataloger) (*pkg.Catalog, error) {
catalog := pkg.NewCatalog()
filesProcessed, packagesDiscovered := newMonitor()
// perform analysis, accumulating errors for each failed analysis
+3 -4
View File
@@ -14,9 +14,8 @@ import (
)
const (
dpkgStatusGlob = "**/var/lib/dpkg/status"
md5sumsExt = ".md5sums"
docsPath = "/usr/share/doc"
md5sumsExt = ".md5sums"
docsPath = "/usr/share/doc"
)
type Cataloger struct{}
@@ -34,7 +33,7 @@ func (c *Cataloger) Name() string {
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing dpkg support files.
// nolint:funlen
func (c *Cataloger) Catalog(resolver source.Resolver) ([]pkg.Package, error) {
dbFileMatches, err := resolver.FilesByGlob(dpkgStatusGlob)
dbFileMatches, err := resolver.FilesByGlob(pkg.DpkgDbGlob)
if err != nil {
return nil, fmt.Errorf("failed to find dpkg status files's by glob: %w", err)
}
+34 -28
View File
@@ -178,36 +178,40 @@ func (j *archiveParser) discoverPkgsFromPomProperties(parentPkg *pkg.Package) ([
return nil, fmt.Errorf("failed to parse pom.properties (%s): %w", j.virtualPath, err)
}
if propsObj != nil {
if propsObj.Version != "" && propsObj.ArtifactID != "" {
// TODO: if there is no parentPkg (no java manifest) one of these poms could be the parent. We should discover the right parent and attach the correct info accordingly to each discovered package
if propsObj == nil {
continue
}
// keep the artifact name within the virtual path if this package does not match the parent package
vPathSuffix := ""
if !strings.HasPrefix(propsObj.ArtifactID, parentPkg.Name) {
vPathSuffix += ":" + propsObj.ArtifactID
}
virtualPath := j.virtualPath + vPathSuffix
if propsObj.Version != "" && propsObj.ArtifactID != "" {
// TODO: if there is no parentPkg (no java manifest) one of these poms could be the parent. We should discover the right parent and attach the correct info accordingly to each discovered package
// discovered props = new package
p := pkg.Package{
Name: propsObj.ArtifactID,
Version: propsObj.Version,
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath,
PomProperties: propsObj,
Parent: parentPkg,
},
}
// keep the artifact name within the virtual path if this package does not match the parent package
vPathSuffix := ""
if parentPkg != nil && !strings.HasPrefix(propsObj.ArtifactID, parentPkg.Name) {
vPathSuffix += ":" + propsObj.ArtifactID
}
virtualPath := j.virtualPath + vPathSuffix
pkgKey := uniquePkgKey(&p)
// discovered props = new package
p := pkg.Package{
Name: propsObj.ArtifactID,
Version: propsObj.Version,
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath,
PomProperties: propsObj,
Parent: parentPkg,
},
}
// the name/version pair matches...
matchesParentPkg := pkgKey == parentKey
pkgKey := uniquePkgKey(&p)
// the name/version pair matches...
matchesParentPkg := pkgKey == parentKey
if parentPkg != nil {
// the virtual path matches...
matchesParentPkg = matchesParentPkg || parentPkg.Metadata.(pkg.JavaMetadata).VirtualPath == virtualPath
@@ -231,11 +235,13 @@ func (j *archiveParser) discoverPkgsFromPomProperties(parentPkg *pkg.Package) ([
parentMetadata.PomProperties = propsObj
parentPkg.Metadata = parentMetadata
}
} else if !j.discoveredPkgs.Contains(pkgKey) {
// only keep packages we haven't seen yet (and are not related to the parent package)
pkgs = append(pkgs, p)
}
}
if !matchesParentPkg && !j.discoveredPkgs.Contains(pkgKey) {
// only keep packages we haven't seen yet (and are not related to the parent package)
pkgs = append(pkgs, p)
}
}
}
return pkgs, nil
@@ -7,6 +7,8 @@ import (
"io"
"regexp"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal"
"github.com/mitchellh/mapstructure"
@@ -172,6 +174,11 @@ func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, error) {
return nil, fmt.Errorf("failed to parse package.json file: %w", err)
}
if !p.hasNameAndVersionValues() {
log.Debug("encountered package.json file without a name and/or version field, ignoring this file")
return nil, nil
}
licenses, err := licensesFromJSON(p)
if err != nil {
return nil, fmt.Errorf("failed to parse package.json file: %w", err)
@@ -195,3 +202,7 @@ func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, error) {
return packages, nil
}
func (p PackageJSON) hasNameAndVersionValues() bool {
return p.Name != "" && p.Version != ""
}
@@ -142,3 +142,20 @@ func TestParsePackageJSON(t *testing.T) {
})
}
}
func TestParsePackageJSON_Partial(t *testing.T) { // see https://github.com/anchore/syft/issues/311
const fixtureFile = "test-fixtures/pkg-json/package-partial.json"
fixture, err := os.Open(fixtureFile)
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parsePackageJSON("", fixture)
if err != nil {
t.Fatalf("failed to parse package-lock.json: %+v", err)
}
if actualCount := len(actual); actualCount != 0 {
t.Errorf("no packages should've been returned (but got %d packages)", actualCount)
}
}
@@ -0,0 +1,5 @@
{
"sideEffects": false,
"module": "../../esm/fp/isSaturday/index.js",
"typings": "../../typings.d.ts"
}
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log"
"github.com/mitchellh/mapstructure"
@@ -56,7 +57,7 @@ func parseWheelOrEggMetadata(path string, reader io.Reader) (pkg.PythonPackageMe
fields[key] = val
} else {
return pkg.PythonPackageMetadata{}, fmt.Errorf("cannot parse field from line: '%s'", line)
log.Warnf("cannot parse field from path: %q from line: %q", path, line)
}
}
}
@@ -117,3 +117,37 @@ func TestDetermineSitePackagesRootPath(t *testing.T) {
})
}
}
func TestParseWheelEggMetadataInvalid(t *testing.T) {
tests := []struct {
Fixture string
ExpectedMetadata pkg.PythonPackageMetadata
}{
{
Fixture: "test-fixtures/egg-info/PKG-INFO-INVALID",
ExpectedMetadata: pkg.PythonPackageMetadata{
Name: "mxnet",
Version: "1.8.0",
SitePackagesRootPath: "test-fixtures",
},
},
}
for _, test := range tests {
t.Run(test.Fixture, func(t *testing.T) {
fixture, err := os.Open(test.Fixture)
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parseWheelOrEggMetadata(test.Fixture, fixture)
if err != nil {
t.Fatalf("failed to parse: %+v", err)
}
for _, d := range deep.Equal(actual, test.ExpectedMetadata) {
t.Errorf("diff: %+v", d)
}
})
}
}
@@ -0,0 +1,69 @@
Metadata-Version: 1.1
Name: mxnet
Version: 1.8.0
Summary: <!--- Licensed to the Apache Software Foundation (ASF) under one -->
<!--- or more contributor license agreements. See the NOTICE file -->
<!--- distributed with this work for additional information -->
<!--- regarding copyright ownership. The ASF licenses this file -->
<!--- to you 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. -->
MXNet Python Package
====================
This directory and nested files contain MXNet Python package and language binding.
## Installation
To install MXNet Python package, visit MXNet [Install Instruction](https://mxnet.apache.org/get_started)
## Running the unit tests
For running unit tests, you will need the [nose PyPi package](https://pypi.python.org/pypi/nose). To install:
```bash
pip install --upgrade nose
```
Once ```nose``` is installed, run the following from MXNet root directory (please make sure the installation path of ```nosetests``` is included in your ```$PATH``` environment variable):
```
nosetests tests/python/unittest
nosetests tests/python/train
```
Home-page: https://github.com/apache/incubator-mxnet
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: C++
Classifier: Programming Language :: Cython
Classifier: Programming Language :: Other
Classifier: Programming Language :: Perl
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
+2 -5
View File
@@ -10,10 +10,7 @@ import (
"github.com/anchore/syft/syft/source"
)
const (
packagesGlob = "**/var/lib/rpm/Packages"
catalogerName = "rpmdb-cataloger"
)
const catalogerName = "rpmdb-cataloger"
type Cataloger struct{}
@@ -29,7 +26,7 @@ func (c *Cataloger) Name() string {
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
func (c *Cataloger) Catalog(resolver source.Resolver) ([]pkg.Package, error) {
fileMatches, err := resolver.FilesByGlob(packagesGlob)
fileMatches, err := resolver.FilesByGlob(pkg.RpmDbGlob)
if err != nil {
return nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
}
+19
View File
@@ -1,9 +1,16 @@
package pkg
import (
"sort"
"github.com/package-url/packageurl-go"
"github.com/scylladb/go-set/strset"
)
const ApkDbGlob = "**/lib/apk/db/installed"
var _ fileOwner = (*ApkMetadata)(nil)
// ApkMetadata represents all captured data for a Alpine DB package entry.
// See the following sources for more information:
// - https://wiki.alpinelinux.org/wiki/Apk_spec
@@ -53,3 +60,15 @@ func (m ApkMetadata) PackageURL() string {
"")
return pURL.ToString()
}
func (m ApkMetadata) ownedFiles() (result []string) {
s := strset.New()
for _, f := range m.Files {
if f.Path != "" {
s.Add(f.Path)
}
}
result = s.List()
sort.Strings(result)
return result
}
+46 -1
View File
@@ -1,8 +1,11 @@
package pkg
import (
"github.com/sergi/go-diff/diffmatchpatch"
"strings"
"testing"
"github.com/go-test/deep"
"github.com/sergi/go-diff/diffmatchpatch"
)
func TestApkMetadata_pURL(t *testing.T) {
@@ -31,3 +34,45 @@ func TestApkMetadata_pURL(t *testing.T) {
})
}
}
func TestApkMetadata_fileOwner(t *testing.T) {
tests := []struct {
metadata ApkMetadata
expected []string
}{
{
metadata: ApkMetadata{
Files: []ApkFileRecord{
{Path: "/somewhere"},
{Path: "/else"},
},
},
expected: []string{
"/else",
"/somewhere",
},
},
{
metadata: ApkMetadata{
Files: []ApkFileRecord{
{Path: "/somewhere"},
{Path: ""},
},
},
expected: []string{
"/somewhere",
},
},
}
for _, test := range tests {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{}
i = test.metadata
actual := i.(fileOwner).ownedFiles()
for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d)
}
})
}
}
+90 -35
View File
@@ -4,27 +4,29 @@ import (
"sort"
"sync"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/internal/log"
)
var nextPackageID int64
var globsForbiddenFromBeingOwned = []string{
ApkDbGlob,
DpkgDbGlob,
RpmDbGlob,
}
// Catalog represents a collection of Packages.
type Catalog struct {
byID map[ID]*Package
byType map[Type][]*Package
byFile map[source.Location][]*Package
lock sync.RWMutex
byID map[ID]*Package
idsByType map[Type][]ID
idsByPath map[string][]ID // note: this is real path or virtual path
lock sync.RWMutex
}
// NewCatalog returns a new empty Catalog
func NewCatalog(pkgs ...Package) *Catalog {
catalog := Catalog{
byID: make(map[ID]*Package),
byType: make(map[Type][]*Package),
byFile: make(map[source.Location][]*Package),
byID: make(map[ID]*Package),
idsByType: make(map[Type][]ID),
idsByPath: make(map[string][]ID),
}
for _, p := range pkgs {
@@ -41,52 +43,96 @@ func (c *Catalog) PackageCount() int {
// Package returns the package with the given ID.
func (c *Catalog) Package(id ID) *Package {
return c.byID[id]
v, exists := c.byID[id]
if !exists {
return nil
}
return v
}
// PackagesByFile returns all packages that were discovered from the given source file reference.
func (c *Catalog) PackagesByFile(location source.Location) []*Package {
return c.byFile[location]
// PackagesByPath returns all packages that were discovered from the given path.
func (c *Catalog) PackagesByPath(path string) []*Package {
return c.Packages(c.idsByPath[path])
}
// Packages returns all packages for the given ID.
func (c *Catalog) Packages(ids []ID) (result []*Package) {
for _, i := range ids {
p, exists := c.byID[i]
if exists {
result = append(result, p)
}
}
return result
}
// Add a package to the Catalog.
func (c *Catalog) Add(p Package) {
if p.id != 0 {
log.Errorf("package already added to catalog: %s", p)
return
}
c.lock.Lock()
defer c.lock.Unlock()
p.id = ID(nextPackageID)
nextPackageID++
_, exists := c.byID[p.ID]
if exists {
log.Errorf("package ID already exists in the catalog : id=%+v %+v", p.ID, p)
return
}
if p.ID == "" {
p.ID = newID()
}
// store by package ID
c.byID[p.id] = &p
c.byID[p.ID] = &p
// store by package type
_, ok := c.byType[p.Type]
if !ok {
c.byType[p.Type] = make([]*Package, 0)
}
c.byType[p.Type] = append(c.byType[p.Type], &p)
c.idsByType[p.Type] = append(c.idsByType[p.Type], p.ID)
// store by file references
for _, s := range p.Locations {
_, ok := c.byFile[s]
if !ok {
c.byFile[s] = make([]*Package, 0)
// store by file location paths
for _, l := range p.Locations {
if l.RealPath != "" {
c.idsByPath[l.RealPath] = append(c.idsByPath[l.RealPath], p.ID)
}
if l.VirtualPath != "" {
c.idsByPath[l.VirtualPath] = append(c.idsByPath[l.VirtualPath], p.ID)
}
c.byFile[s] = append(c.byFile[s], &p)
}
}
func (c *Catalog) Remove(id ID) {
c.lock.Lock()
defer c.lock.Unlock()
_, exists := c.byID[id]
if !exists {
log.Errorf("package ID does not exist in the catalog : id=%+v", id)
return
}
// Remove all index references to this package ID
for t, ids := range c.idsByType {
c.idsByType[t] = removeID(id, ids)
if len(c.idsByType[t]) == 0 {
delete(c.idsByType, t)
}
}
for p, ids := range c.idsByPath {
c.idsByPath[p] = removeID(id, ids)
if len(c.idsByPath[p]) == 0 {
delete(c.idsByPath, p)
}
}
// Remove package
delete(c.byID, id)
}
// Enumerate all packages for the given type(s), enumerating all packages if no type is specified.
func (c *Catalog) Enumerate(types ...Type) <-chan *Package {
channel := make(chan *Package)
go func() {
defer close(channel)
for ty, packages := range c.byType {
for ty, ids := range c.idsByType {
if len(types) != 0 {
found := false
typeCheck:
@@ -100,8 +146,8 @@ func (c *Catalog) Enumerate(types ...Type) <-chan *Package {
continue
}
}
for _, p := range packages {
channel <- p
for _, id := range ids {
channel <- c.Package(id)
}
}
}()
@@ -128,3 +174,12 @@ func (c *Catalog) Sorted(types ...Type) []*Package {
return pkgs
}
func removeID(id ID, target []ID) (result []ID) {
for _, value := range target {
if value != id {
result = append(result, value)
}
}
return result
}
+157
View File
@@ -0,0 +1,157 @@
package pkg
import (
"testing"
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/source"
)
var catalogAddAndRemoveTestPkgs = []Package{
{
ID: "my-id",
Locations: []source.Location{
{
RealPath: "/a/path",
VirtualPath: "/another/path",
},
{
RealPath: "/b/path",
VirtualPath: "/bee/path",
},
},
Type: RpmPkg,
},
{
ID: "my-other-id",
Locations: []source.Location{
{
RealPath: "/c/path",
VirtualPath: "/another/path",
},
{
RealPath: "/d/path",
VirtualPath: "/another/path",
},
},
Type: NpmPkg,
},
}
type expectedIndexes struct {
byType map[Type]*strset.Set
byPath map[string]*strset.Set
}
func TestCatalogAddPopulatesIndex(t *testing.T) {
tests := []struct {
name string
pkgs []Package
expectedIndexes expectedIndexes
}{
{
name: "vanilla-add",
pkgs: catalogAddAndRemoveTestPkgs,
expectedIndexes: expectedIndexes{
byType: map[Type]*strset.Set{
RpmPkg: strset.New("my-id"),
NpmPkg: strset.New("my-other-id"),
},
byPath: map[string]*strset.Set{
"/another/path": strset.New("my-id", "my-other-id"),
"/a/path": strset.New("my-id"),
"/b/path": strset.New("my-id"),
"/bee/path": strset.New("my-id"),
"/c/path": strset.New("my-other-id"),
"/d/path": strset.New("my-other-id"),
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := NewCatalog(test.pkgs...)
assertIndexes(t, c, test.expectedIndexes)
})
}
}
func TestCatalogRemove(t *testing.T) {
tests := []struct {
name string
pkgs []Package
removeId ID
expectedIndexes expectedIndexes
}{
{
name: "vanilla-add",
removeId: "my-other-id",
pkgs: catalogAddAndRemoveTestPkgs,
expectedIndexes: expectedIndexes{
byType: map[Type]*strset.Set{
RpmPkg: strset.New("my-id"),
},
byPath: map[string]*strset.Set{
"/another/path": strset.New("my-id"),
"/a/path": strset.New("my-id"),
"/b/path": strset.New("my-id"),
"/bee/path": strset.New("my-id"),
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := NewCatalog(test.pkgs...)
c.Remove(test.removeId)
assertIndexes(t, c, test.expectedIndexes)
if c.Package(test.removeId) != nil {
t.Errorf("expected package to be removed, but was found!")
}
if c.PackageCount() != len(test.pkgs)-1 {
t.Errorf("expected count to be affected but was not")
}
})
}
}
func assertIndexes(t *testing.T, c *Catalog, expectedIndexes expectedIndexes) {
// assert path index
if len(c.idsByPath) != len(expectedIndexes.byPath) {
t.Errorf("unexpected path index length: %d != %d", len(c.idsByPath), len(expectedIndexes.byPath))
}
for path, expectedIds := range expectedIndexes.byPath {
actualIds := strset.New()
for _, p := range c.PackagesByPath(path) {
actualIds.Add(string(p.ID))
}
if !expectedIds.IsEqual(actualIds) {
t.Errorf("mismatched IDs for path=%q : %+v", path, strset.SymmetricDifference(actualIds, expectedIds))
}
}
// assert type index
if len(c.idsByType) != len(expectedIndexes.byType) {
t.Errorf("unexpected type index length: %d != %d", len(c.idsByType), len(expectedIndexes.byType))
}
for ty, expectedIds := range expectedIndexes.byType {
actualIds := strset.New()
for p := range c.Enumerate(ty) {
actualIds.Add(string(p.ID))
}
if !expectedIds.IsEqual(actualIds) {
t.Errorf("mismatched IDs for type=%q : %+v", ty, strset.SymmetricDifference(actualIds, expectedIds))
}
}
}
+19
View File
@@ -1,10 +1,17 @@
package pkg
import (
"sort"
"github.com/anchore/syft/syft/distro"
"github.com/package-url/packageurl-go"
"github.com/scylladb/go-set/strset"
)
const DpkgDbGlob = "**/var/lib/dpkg/status"
var _ fileOwner = (*DpkgMetadata)(nil)
// DpkgMetadata represents all captured data for a Debian package DB entry; available fields are described
// at http://manpages.ubuntu.com/manpages/xenial/man1/dpkg-query.1.html in the --showformat section.
type DpkgMetadata struct {
@@ -44,3 +51,15 @@ func (m DpkgMetadata) PackageURL(d *distro.Distro) string {
"")
return pURL.ToString()
}
func (m DpkgMetadata) ownedFiles() (result []string) {
s := strset.New()
for _, f := range m.Files {
if f.Path != "" {
s.Add(f.Path)
}
}
result = s.List()
sort.Strings(result)
return
}
+45
View File
@@ -1,8 +1,11 @@
package pkg
import (
"strings"
"testing"
"github.com/go-test/deep"
"github.com/anchore/syft/syft/distro"
"github.com/sergi/go-diff/diffmatchpatch"
)
@@ -50,3 +53,45 @@ func TestDpkgMetadata_pURL(t *testing.T) {
})
}
}
func TestDpkgMetadata_fileOwner(t *testing.T) {
tests := []struct {
metadata DpkgMetadata
expected []string
}{
{
metadata: DpkgMetadata{
Files: []DpkgFileRecord{
{Path: "/somewhere"},
{Path: "/else"},
},
},
expected: []string{
"/else",
"/somewhere",
},
},
{
metadata: DpkgMetadata{
Files: []DpkgFileRecord{
{Path: "/somewhere"},
{Path: ""},
},
},
expected: []string{
"/somewhere",
},
},
}
for _, test := range tests {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{}
i = test.metadata
actual := i.(fileOwner).ownedFiles()
for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d)
}
})
}
}
+5
View File
@@ -0,0 +1,5 @@
package pkg
type fileOwner interface {
ownedFiles() []string
}
+12
View File
@@ -0,0 +1,12 @@
package pkg
import (
"github.com/google/uuid"
)
// ID represents a unique value for each package added to a package catalog.
type ID string
func newID() ID {
return ID(uuid.New().String())
}
@@ -0,0 +1,86 @@
package pkg
import (
"github.com/anchore/syft/internal/log"
"github.com/bmatcuk/doublestar/v2"
"github.com/scylladb/go-set/strset"
)
type ownershipByFilesMetadata struct {
Files []string `json:"files"`
}
func ownershipByFilesRelationships(catalog *Catalog) []Relationship {
var relationships = findOwnershipByFilesRelationships(catalog)
var edges []Relationship
for parent, children := range relationships {
for child, files := range children {
edges = append(edges, Relationship{
Parent: parent,
Child: child,
Type: OwnershipByFileOverlapRelationship,
Metadata: ownershipByFilesMetadata{
Files: files.List(),
},
})
}
}
return edges
}
// findOwnershipByFilesRelationships find overlaps in file ownership with a file that defines another package. Specifically, a .Location.Path of
// a package is found to be owned by another (from the owner's .Metadata.Files[]).
func findOwnershipByFilesRelationships(catalog *Catalog) map[ID]map[ID]*strset.Set {
var relationships = make(map[ID]map[ID]*strset.Set)
for _, candidateOwnerPkg := range catalog.Sorted() {
if candidateOwnerPkg.Metadata == nil {
continue
}
// check to see if this is a file owner
pkgFileOwner, ok := candidateOwnerPkg.Metadata.(fileOwner)
if !ok {
continue
}
for _, ownedFilePath := range pkgFileOwner.ownedFiles() {
if matchesAny(ownedFilePath, globsForbiddenFromBeingOwned) {
// we skip over known exceptions to file ownership, such as the RPM package owning
// the RPM DB path, otherwise the RPM package would "own" all RPMs, which is not intended
continue
}
// look for package(s) in the catalog that may be owned by this package and mark the relationship
for _, subPackage := range catalog.PackagesByPath(ownedFilePath) {
if subPackage.ID == candidateOwnerPkg.ID {
continue
}
if _, exists := relationships[candidateOwnerPkg.ID]; !exists {
relationships[candidateOwnerPkg.ID] = make(map[ID]*strset.Set)
}
if _, exists := relationships[candidateOwnerPkg.ID][subPackage.ID]; !exists {
relationships[candidateOwnerPkg.ID][subPackage.ID] = strset.New()
}
relationships[candidateOwnerPkg.ID][subPackage.ID].Add(ownedFilePath)
}
}
}
return relationships
}
func matchesAny(s string, globs []string) bool {
for _, g := range globs {
matches, err := doublestar.Match(g, s)
if err != nil {
log.Errorf("failed to match glob=%q : %+v", g, err)
}
if matches {
return true
}
}
return false
}
@@ -0,0 +1,175 @@
package pkg
import (
"testing"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
func TestOwnershipByFilesRelationship(t *testing.T) {
tests := []struct {
name string
pkgs []Package
expectedRelations []Relationship
}{
{
name: "owns-by-real-path",
pkgs: []Package{
{
ID: "parent",
Locations: []source.Location{
{
RealPath: "/a/path",
VirtualPath: "/another/path",
},
{
RealPath: "/b/path",
VirtualPath: "/bee/path",
},
},
Type: RpmPkg,
MetadataType: RpmdbMetadataType,
Metadata: RpmdbMetadata{
Files: []RpmdbFileRecord{
{Path: "/owning/path/1"},
{Path: "/owning/path/2"},
{Path: "/d/path"},
},
},
},
{
ID: "child",
Locations: []source.Location{
{
RealPath: "/c/path",
VirtualPath: "/another/path",
},
{
RealPath: "/d/path",
VirtualPath: "/another/path",
},
},
Type: NpmPkg,
},
},
expectedRelations: []Relationship{
{
Parent: "parent",
Child: "child",
Type: OwnershipByFileOverlapRelationship,
Metadata: ownershipByFilesMetadata{
Files: []string{
"/d/path",
},
},
},
},
},
{
name: "owns-by-virtual-path",
pkgs: []Package{
{
ID: "parent",
Locations: []source.Location{
{
RealPath: "/a/path",
VirtualPath: "/some/other/path",
},
{
RealPath: "/b/path",
VirtualPath: "/bee/path",
},
},
Type: RpmPkg,
MetadataType: RpmdbMetadataType,
Metadata: RpmdbMetadata{
Files: []RpmdbFileRecord{
{Path: "/owning/path/1"},
{Path: "/owning/path/2"},
{Path: "/another/path"},
},
},
},
{
ID: "child",
Locations: []source.Location{
{
RealPath: "/c/path",
VirtualPath: "/another/path",
},
{
RealPath: "/d/path",
VirtualPath: "",
},
},
Type: NpmPkg,
},
},
expectedRelations: []Relationship{
{
Parent: "parent",
Child: "child",
Type: OwnershipByFileOverlapRelationship,
Metadata: ownershipByFilesMetadata{
Files: []string{
"/another/path",
},
},
},
},
},
{
name: "ignore-empty-path",
pkgs: []Package{
{
ID: "parent",
Locations: []source.Location{
{
RealPath: "/a/path",
VirtualPath: "/some/other/path",
},
{
RealPath: "/b/path",
VirtualPath: "/bee/path",
},
},
Type: RpmPkg,
MetadataType: RpmdbMetadataType,
Metadata: RpmdbMetadata{
Files: []RpmdbFileRecord{
{Path: "/owning/path/1"},
{Path: "/owning/path/2"},
{Path: ""},
},
},
},
{
ID: "child",
Locations: []source.Location{
{
RealPath: "/c/path",
VirtualPath: "/another/path",
},
{
RealPath: "/d/path",
VirtualPath: "",
},
},
Type: NpmPkg,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := NewCatalog(test.pkgs...)
relationships := ownershipByFilesRelationships(c)
for _, d := range deep.Equal(test.expectedRelations, relationships) {
t.Errorf("diff: %+v", d)
}
})
}
}
+1 -9
View File
@@ -9,12 +9,9 @@ import (
"github.com/anchore/syft/syft/source"
)
// ID represents a unique value for each package added to a package catalog.
type ID int64
// Package represents an application or library that has been bundled into a distributable format.
type Package struct {
id ID // uniquely identifies a package, set by the cataloger
ID ID // uniquely identifies a package, set by the cataloger
Name string // the package name
Version string // the version of the package
FoundBy string // the specific cataloger that discovered this package
@@ -29,11 +26,6 @@ type Package struct {
Metadata interface{} // additional data found while parsing the package source
}
// ID returns the package ID, which is unique relative to a package catalog.
func (p Package) ID() ID {
return p.id
}
// Stringer to represent a package.
func (p Package) String() string {
return fmt.Sprintf("Pkg(type=%s, name=%s, version=%s)", p.Type, p.Name, p.Version)
+20
View File
@@ -1,5 +1,13 @@
package pkg
import (
"sort"
"github.com/scylladb/go-set/strset"
)
var _ fileOwner = (*PythonPackageMetadata)(nil)
// PythonFileDigest represents the file metadata for a single file attributed to a python package.
type PythonFileDigest struct {
Algorithm string `json:"algorithm"`
@@ -25,3 +33,15 @@ type PythonPackageMetadata struct {
SitePackagesRootPath string `json:"sitePackagesRootPath"`
TopLevelPackages []string `json:"topLevelPackages,omitempty"`
}
func (m PythonPackageMetadata) ownedFiles() (result []string) {
s := strset.New()
for _, f := range m.Files {
if f.Path != "" {
s.Add(f.Path)
}
}
result = s.List()
sort.Strings(result)
return result
}
+50
View File
@@ -0,0 +1,50 @@
package pkg
import (
"strings"
"testing"
"github.com/go-test/deep"
)
func TestPythonMetadata_fileOwner(t *testing.T) {
tests := []struct {
metadata PythonPackageMetadata
expected []string
}{
{
metadata: PythonPackageMetadata{
Files: []PythonFileRecord{
{Path: "/somewhere"},
{Path: "/else"},
},
},
expected: []string{
"/else",
"/somewhere",
},
},
{
metadata: PythonPackageMetadata{
Files: []PythonFileRecord{
{Path: "/somewhere"},
{Path: ""},
},
},
expected: []string{
"/somewhere",
},
},
}
for _, test := range tests {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{}
i = test.metadata
actual := i.(fileOwner).ownedFiles()
for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d)
}
})
}
}
+20
View File
@@ -0,0 +1,20 @@
package pkg
const (
// OwnershipByFileOverlapRelationship indicates that the parent package owns the child package made evident by the set of provided files
OwnershipByFileOverlapRelationship RelationshipType = "ownership-by-file-overlap"
)
type RelationshipType string
type Relationship struct {
Parent ID
Child ID
Type RelationshipType
Metadata interface{}
}
// TODO: as more relationships are added, this function signature will probably accommodate selection
func NewRelationships(catalog *Catalog) []Relationship {
return ownershipByFilesRelationships(catalog)
}
+19
View File
@@ -2,11 +2,18 @@ package pkg
import (
"fmt"
"sort"
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/distro"
"github.com/package-url/packageurl-go"
)
const RpmDbGlob = "**/var/lib/rpm/Packages"
var _ fileOwner = (*RpmdbMetadata)(nil)
// RpmdbMetadata represents all captured data for a RPM DB package entry.
type RpmdbMetadata struct {
Name string `json:"name"`
@@ -52,3 +59,15 @@ func (m RpmdbMetadata) PackageURL(d *distro.Distro) string {
"")
return pURL.ToString()
}
func (m RpmdbMetadata) ownedFiles() (result []string) {
s := strset.New()
for _, f := range m.Files {
if f.Path != "" {
s.Add(f.Path)
}
}
result = s.List()
sort.Strings(result)
return result
}
+45
View File
@@ -1,8 +1,11 @@
package pkg
import (
"strings"
"testing"
"github.com/go-test/deep"
"github.com/anchore/syft/syft/distro"
"github.com/sergi/go-diff/diffmatchpatch"
)
@@ -52,3 +55,45 @@ func TestRpmMetadata_pURL(t *testing.T) {
})
}
}
func TestRpmMetadata_fileOwner(t *testing.T) {
tests := []struct {
metadata RpmdbMetadata
expected []string
}{
{
metadata: RpmdbMetadata{
Files: []RpmdbFileRecord{
{Path: "/somewhere"},
{Path: "/else"},
},
},
expected: []string{
"/else",
"/somewhere",
},
},
{
metadata: RpmdbMetadata{
Files: []RpmdbFileRecord{
{Path: "/somewhere"},
{Path: ""},
},
},
expected: []string{
"/somewhere",
},
},
}
for _, test := range tests {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{}
i = test.metadata
actual := i.(fileOwner).ownedFiles()
for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d)
}
})
}
}
+7 -5
View File
@@ -12,11 +12,12 @@ import (
// Document represents the syft cataloging findings as a JSON document
type Document struct {
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
Source Source `json:"source"` // Source represents the original object that was cataloged
Distro Distribution `json:"distro"` // Distro represents the Linux distribution that was detected from the source
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
Source Source `json:"source"` // Source represents the original object that was cataloged
Distro Distribution `json:"distro"` // Distro represents the Linux distribution that was detected from the source
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
ArtifactRelationships []Relationship `json:"artifactRelationships"`
}
// NewDocument creates and populates a new JSON document struct from the given cataloging results.
@@ -38,6 +39,7 @@ func NewDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d *distro.Di
Version: internal.JSONSchemaVersion,
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
},
ArtifactRelationships: newRelationships(pkg.NewRelationships(catalog)),
}
for _, p := range catalog.Sorted() {
+3
View File
@@ -16,6 +16,7 @@ type Package struct {
// packageBasicMetadata contains non-ambiguous values (type-wise) from pkg.Package.
type packageBasicMetadata struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Type string `json:"type"`
@@ -60,6 +61,7 @@ func NewPackage(p *pkg.Package) (Package, error) {
return Package{
packageBasicMetadata: packageBasicMetadata{
ID: string(p.ID),
Name: p.Name,
Version: p.Version,
Type: string(p.Type),
@@ -89,6 +91,7 @@ func (a Package) ToPackage() (pkg.Package, error) {
}
return pkg.Package{
// does not include found-by and locations
ID: pkg.ID(a.ID),
Name: a.Name,
Version: a.Version,
FoundBy: a.FoundBy,
+4
View File
@@ -31,6 +31,7 @@ func TestJsonDirsPresenter(t *testing.T) {
// populate catalog with test data
catalog.Add(pkg.Package{
ID: "package-1-id",
Name: "package-1",
Version: "1.0.1",
Type: pkg.PythonPkg,
@@ -51,6 +52,7 @@ func TestJsonDirsPresenter(t *testing.T) {
},
})
catalog.Add(pkg.Package{
ID: "package-2-id",
Name: "package-2",
Version: "2.0.1",
Type: pkg.DebPkg,
@@ -113,6 +115,7 @@ func TestJsonImgsPresenter(t *testing.T) {
// populate catalog with test data
catalog.Add(pkg.Package{
ID: "package-1-id",
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
@@ -133,6 +136,7 @@ func TestJsonImgsPresenter(t *testing.T) {
},
})
catalog.Add(pkg.Package{
ID: "package-2-id",
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
+23
View File
@@ -0,0 +1,23 @@
package json
import "github.com/anchore/syft/syft/pkg"
type Relationship struct {
Parent string `json:"parent"`
Child string `json:"child"`
Type string `json:"type"`
Metadata interface{} `json:"metadata"`
}
func newRelationships(relationships []pkg.Relationship) []Relationship {
result := make([]Relationship, len(relationships))
for i, r := range relationships {
result[i] = Relationship{
Parent: string(r.Parent),
Child: string(r.Child),
Type: string(r.Type),
Metadata: r.Metadata,
}
}
return result
}
@@ -1,6 +1,7 @@
{
"artifacts": [
{
"id": "package-1-id",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@@ -30,6 +31,7 @@
}
},
{
"id": "package-2-id",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
@@ -72,7 +74,8 @@
"version": "[not provided]"
},
"schema": {
"version": "1.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.1.json"
}
"version": "1.0.2",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.2.json"
},
"artifactRelationships": []
}
@@ -1,6 +1,7 @@
{
"artifacts": [
{
"id": "package-1-id",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@@ -31,6 +32,7 @@
}
},
{
"id": "package-2-id",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
@@ -103,7 +105,8 @@
"version": "[not provided]"
},
"schema": {
"version": "1.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.1.json"
}
"version": "1.0.2",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.0.2.json"
},
"artifactRelationships": []
}
+2 -4
View File
@@ -2,6 +2,7 @@ package integration
import (
"bytes"
"strings"
"testing"
"github.com/anchore/stereoscope/pkg/imagetest"
@@ -57,8 +58,6 @@ func TestCatalogFromJSON(t *testing.T) {
var actualPackages, expectedPackages []*pkg.Package
// TODO: take out pkg.RpmdbMetadataType filter
for _, p := range expectedCatalog.Sorted() {
expectedPackages = append(expectedPackages, p)
}
@@ -89,8 +88,7 @@ func TestCatalogFromJSON(t *testing.T) {
for _, d := range deep.Equal(a, e) {
// ignore errors for empty collections vs nil for select fields
// TODO: this is brittle, but not dangerously so. We should still find a better way to do this.
if d == "Licenses: [] != <nil slice>" {
if strings.Contains(d, "[] != <nil slice>") {
continue
}
t.Errorf(" package %d (name=%s) diff: %+v", i, e.Name, d)
@@ -0,0 +1,20 @@
package integration
import (
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/source"
"testing"
)
func TestJavaNoMainPackage(t *testing.T) { // Regression: https://github.com/anchore/syft/issues/252
fixtureImageName := "image-java-no-main-package"
_, cleanup := imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
defer cleanup()
_, _, _, err := syft.Catalog("docker-archive:"+tarPath, source.SquashedScope)
if err != nil {
t.Fatalf("failed to catalog image: %+v", err)
}
}
@@ -0,0 +1,61 @@
package integration
import (
"bytes"
"encoding/json"
"testing"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/presenter"
jsonPresenter "github.com/anchore/syft/syft/presenter/json"
"github.com/anchore/syft/syft/source"
)
func TestPackageOwnershipRelationships(t *testing.T) {
// ensure that the json presenter is applying artifact ownership with an image that has expected ownership relationships
tests := []struct {
fixture string
}{
{
fixture: "image-owning-package",
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
_, cleanup := imagetest.GetFixtureImage(t, "docker-archive", test.fixture)
tarPath := imagetest.GetFixtureImageTarPath(t, test.fixture)
defer cleanup()
src, catalog, d, err := syft.Catalog("docker-archive:"+tarPath, source.SquashedScope)
if err != nil {
t.Fatalf("failed to catalog image: %+v", err)
}
p := presenter.GetPresenter(presenter.JSONPresenter, src.Metadata, catalog, d)
if p == nil {
t.Fatal("unable to get presenter")
}
output := bytes.NewBufferString("")
err = p.Present(output)
if err != nil {
t.Fatalf("unable to present: %+v", err)
}
var doc jsonPresenter.Document
decoder := json.NewDecoder(output)
if err := decoder.Decode(&doc); err != nil {
t.Fatalf("unable to decode json doc: %+v", err)
}
if len(doc.ArtifactRelationships) == 0 {
t.Errorf("expected to find relationships between packages but found none")
}
})
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ func TestRegression212ApkBufferSize(t *testing.T) {
t.Fatalf("failed to catalog image: %+v", err)
}
expectedPkgs := 57
expectedPkgs := 58
actualPkgs := 0
for range catalog.Enumerate(pkg.ApkPkg) {
actualPkgs += 1
@@ -0,0 +1,23 @@
FROM jenkins:2.60.3
USER root
WORKDIR /usr/share/jenkins
RUN mkdir tmp
WORKDIR /usr/share/jenkins/tmp
RUN apt-get update 2>&1 > /dev/null && apt-get install -y less zip 2>&1 > /dev/null
RUN unzip ../jenkins.war 2>&1 > /dev/null
RUN rm -f ./META-INF/MANIFEST.MF
WORKDIR /usr/share/jenkins
RUN rm -rf jenkins.war
RUN cd ./tmp && zip -r ../jenkins.war . && cd ..
RUN rm -rf ./tmp
@@ -1,2 +1,5 @@
FROM alpine:latest
RUN apk add tzdata vim alpine-sdk
FROM alpine@sha256:d9a7354e3845ea8466bb00b22224d9116b183e594527fb5b6c3d30bc01a20378
RUN apk add --no-cache \
tzdata=2021a-r0 \
vim=8.2.2320-r0 \
alpine-sdk=1.0-r0
@@ -0,0 +1,3 @@
FROM ubuntu:20.04
# this covers rpm-python
RUN apt-get update && apt-get install -y python-pil=6.2.1-3