Compare commits

...
29 Commits
Author SHA1 Message Date
Alex Goodman c43f4fb416 add signature verification to install.sh (#2941)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-10 11:29:53 -04:00
Alex Goodman db0c33481e pin golang image (#2944)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-10 09:59:33 -04:00
anchore-actions-token-generator[bot]andspiffcs a658cc7402 chore(deps): update tools to latest versions (#2943)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2024-06-10 09:20:22 -04:00
Alex Goodman 254a562b4e Add support for reading ELF package notes with section header (#2939)
* add support for reading ELF package notes with section header

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add systemd elf package fields to json schema

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-07 14:38:54 -04:00
anchore-actions-token-generator[bot]andspiffcs bc20e66d08 chore(deps): update tools to latest versions (#2940)
* chore(deps): update tools to latest versions

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: update header and deprecated fields

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2024-06-07 16:55:32 +00:00
Alex Goodman 6e16396d18 remove dco workflow (#2936)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-06 14:29:55 +00:00
Brian Ebarb 5cdc0845fb feat: add license field to ELF binary package metadata (#2890)
Signed-off-by: Brian Ebarb <ebarb.brian@gmail.com>

feat: add License component to elf binary packages

Signed-off-by: Brian Ebarb <ebarb.brian@gmail.com>

feat: fix elf_package_cataloger test

feat: elf package cataloger unit test updates
2024-06-06 14:10:53 +00:00
dependabot[bot] baca15f186 chore(deps): bump github.com/docker/docker (#2935)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.3+incompatible to 26.1.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.1.3...v26.1.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-06 09:56:47 -04:00
Laurent Goderre b608ab77e1 feat: classifier for util-linux (#2933)
Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
2024-06-05 17:32:52 -04:00
dependabot[bot] f4c941f08d chore(deps): bump golang.org/x/net from 0.25.0 to 0.26.0 (#2929)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/net/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 19:09:09 +00:00
dependabot[bot] 2d5760145c chore(deps): bump github/codeql-action from 3.25.7 to 3.25.8 (#2928)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.7 to 3.25.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/f079b8493333aace61c81488f8bd40919487bd9f...2e230e8fe0ad3a14a340ad0815ddb96d599d2aff)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 11:58:14 -07:00
dependabot[bot] f11bb95c1e chore(deps): bump golang.org/x/mod from 0.17.0 to 0.18.0 (#2930)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/mod/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 11:57:54 -07:00
dependabot[bot] d2fa5ed41c chore(deps): bump github.com/CycloneDX/cyclonedx-go from 0.8.0 to 0.9.0 (#2931)
Bumps [github.com/CycloneDX/cyclonedx-go](https://github.com/CycloneDX/cyclonedx-go) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/CycloneDX/cyclonedx-go/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-go/blob/master/.goreleaser.yml)
- [Commits](https://github.com/CycloneDX/cyclonedx-go/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: github.com/CycloneDX/cyclonedx-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 11:57:43 -07:00
Christopher Angelo Phillips 7508d75b2b fix: update unit tests to use pinned patch version (#2932)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2024-06-05 18:31:38 +00:00
Alex Goodman 3472b48177 Add relationships for python poetry packages (#2906)
* [wip] add initial poetry.lock relationship support

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* provide generic set for basic types

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* dependency resolver should allow for conditional deps

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add tests for poetry lock relationship additions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update schema with python poetry dependency refs

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* dep specification data structure should not be recursive in nature

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-04 20:00:05 +00:00
Alex Goodman 4bdbd1aa2a remove unused test fixtures and empty dirs (#2924)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-04 15:22:49 -04:00
William MurphyandAlex Goodman 557ad73ee6 fix: only skip tmpfs mounts for some paths (#2918)
* fix: only skip tmpfs mounts for some paths

Signed-off-by: Will Murphy <will.murphy@anchore.com>

* refactor and add tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add regression test for archive processing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump to golang 1.22

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove rule 1 and add more tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Will Murphy <will.murphy@anchore.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-04 15:21:45 -04:00
Alex Goodman cb09dd9e19 match existing DCO GH check name
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-04 13:34:29 -04:00
Alex Goodman 2f39322f81 use dco tool during gh app outage (#2926)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-06-04 17:31:29 +00:00
William Tang fe0b78b7fe fix: close sql driver after testing sqlite availability (#2922)
Signed-off-by: William Tang <ttc0419@outlook.com>
2024-06-03 14:42:13 +00:00
Ralph Bean 8a7f08e2c8 Use redhat as namespace for redhat rpms (#2914)
The namespace value of `redhat` signifies this as an RPM package
produced and distributed by Red Hat.

Using "rhel" in the namespace is not correct.

Signed-off-by: Ralph Bean <rbean@redhat.com>
2024-06-03 10:18:52 -04:00
dufucunanddufucun 29d601d1f1 chore: fix some comments (#2920)
Signed-off-by: dufucun <dufuchun@sohu.com>
Co-authored-by: dufucun <dufucunn@sohu.com>
2024-06-03 14:10:39 +00:00
anchore-actions-token-generator[bot]andwagoodman 3ac95bfbd0 chore(deps): update CPE dictionary index (#2919)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2024-06-03 10:00:30 -04:00
dependabot[bot] 24262b9e31 chore(deps): bump modernc.org/sqlite from 1.29.10 to 1.30.0 (#2921)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.29.10 to 1.30.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.29.10...v1.30.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 09:59:33 -04:00
dependabot[bot] b04bc0fbfe chore(deps): bump github/codeql-action from 3.25.6 to 3.25.7 (#2916)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.6 to 3.25.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/9fdb3e49720b44c48891d036bb502feb25684276...f079b8493333aace61c81488f8bd40919487bd9f)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-31 11:17:21 -04:00
dependabot[bot] aafa161ff6 chore(deps): bump github.com/charmbracelet/bubbletea (#2917)
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 0.26.3 to 0.26.4.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v0.26.3...v0.26.4)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-31 10:20:00 -04:00
Laurent Goderre eeb4193d4a Lua: Add support for more advanced syntax (#2908)
* Add lua/rocksepc support for variables substitution
* Lua: Skip expressions in rockspec packages
* Lua: Add support for concatenation of string and variables
* Lua: Skip expressions in local
* Lua: Skip build sections in Rockspec files
* Lua: skip function blocks in Rockspec
* Lua: Add support for multi variable per line
---------
Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
2024-05-30 11:38:45 -04:00
anchore-actions-token-generator[bot]andspiffcs 5cf8cc9bc3 chore(deps): update tools to latest versions (#2913)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2024-05-30 11:37:31 -04:00
Alex Goodman f4a69e6d35 add relationships for go binary packages (#2912)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-05-30 11:37:17 -04:00
104 changed files with 10975 additions and 738 deletions
+4 -4
View File
@@ -2,7 +2,7 @@ tools:
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
- name: binny
version:
want: v0.7.0
want: v0.8.0
method: github-release
with:
repo: anchore/binny
@@ -26,7 +26,7 @@ tools:
# used for linting
- name: golangci-lint
version:
want: v1.59.0
want: v1.59.1
method: github-release
with:
repo: golangci/golangci-lint
@@ -58,7 +58,7 @@ tools:
# used to release all artifacts
- name: goreleaser
version:
want: v1.26.2
want: v2.0.0
method: github-release
with:
repo: goreleaser/goreleaser
@@ -111,7 +111,7 @@ tools:
# used for triggering a release
- name: gh
version:
want: v2.49.2
want: v2.50.0
method: github-release
with:
repo: cli/cli
+1 -1
View File
@@ -5,7 +5,7 @@ inputs:
go-version:
description: "Go version to install"
required: true
default: "1.21.x"
default: "1.22.x"
go-dependencies:
description: "Download go dependencies"
required: true
+3 -3
View File
@@ -45,7 +45,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 #v3.25.6
uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff #v3.25.8
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@9fdb3e49720b44c48891d036bb502feb25684276 #v3.25.6
uses: github/codeql-action/autobuild@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff #v3.25.8
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,4 +70,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 #v3.25.6
uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff #v3.25.8
+8
View File
@@ -17,6 +17,14 @@ jobs:
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
- name: Check if running on main
if: github.ref != 'refs/heads/main'
# we are using the following flag when running `cosign blob-verify` for checksum signature verification:
# --certificate-identity-regexp "https://github.com/anchore/.github/workflows/release.yaml@refs/heads/main"
# if we are not on the main branch, the signature will not be verifiable since the suffix requires the main branch
# at the time of when the OIDC token was issued on the Github Actions runner.
run: echo "This can only be run on the main branch otherwise releases produced will not be verifiable with cosign" && exit 1
- name: Check if tag already exists
# note: this will fail if the tag already exists
run: |
+3
View File
@@ -187,6 +187,9 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@v3.5.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
- name: Bootstrap environment
+3 -1
View File
@@ -1,3 +1,5 @@
version: 2
release:
prerelease: auto
draft: false
@@ -80,7 +82,7 @@ nfpms:
- deb
brews:
- tap:
- repository:
owner: anchore
name: homebrew-syft
token: "{{.Env.GITHUB_BREW_TOKEN}}"
+4 -5
View File
@@ -40,11 +40,10 @@ Syft binaries are provided for Linux, macOS and Windows.
> curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
> ```
... or, you can specify a release version and destination directory for the installation:
> ```bash
> curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <DESTINATION_DIR> <RELEASE_VERSION>
> ```
Install script options:
- `-b`: Specify a custom installation directory (defaults to `./bin`)
- `-d`: More verbose logging levels (`-d` for debug, `-dd` for trace)
- `-v`: Verify the signature of the downloaded artifact before installation (requires [`cosign`](https://github.com/sigstore/cosign) to be installed)
### Homebrew
```bash
+14 -14
View File
@@ -1,9 +1,9 @@
module github.com/anchore/syft
go 1.21.0
go 1.22.0
require (
github.com/CycloneDX/cyclonedx-go v0.8.0
github.com/CycloneDX/cyclonedx-go v0.9.0
github.com/Masterminds/semver v1.5.0
github.com/Masterminds/sprig/v3 v3.2.3
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
@@ -24,12 +24,12 @@ require (
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.26.3
github.com/charmbracelet/bubbletea v0.26.4
github.com/charmbracelet/lipgloss v0.11.0
github.com/dave/jennifer v1.7.0
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
github.com/distribution/reference v0.6.0
github.com/docker/docker v26.1.3+incompatible
github.com/docker/docker v26.1.4+incompatible
github.com/dustin/go-humanize v1.0.1
github.com/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
@@ -78,10 +78,10 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
go.uber.org/goleak v1.3.0
golang.org/x/mod v0.17.0
golang.org/x/net v0.25.0
golang.org/x/mod v0.18.0
golang.org/x/net v0.26.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.29.10
modernc.org/sqlite v1.30.0
)
require google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
@@ -106,7 +106,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.1.1 // indirect
github.com/charmbracelet/x/ansi v0.1.2 // indirect
github.com/charmbracelet/x/input v0.1.0 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.0 // indirect
@@ -226,13 +226,13 @@ require (
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.19.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
@@ -240,7 +240,7 @@ require (
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.49.3 // indirect
modernc.org/libc v1.50.9 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
+30 -30
View File
@@ -58,8 +58,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M=
github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk=
github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8=
github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -155,14 +155,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.3 h1:iXyGvI+FfOWqkB2V07m1DF3xxQijxjY2j8PqiXYqasg=
github.com/charmbracelet/bubbletea v0.26.3/go.mod h1:bpZHfDHTYJC5g+FBK+ptJRCQotRC+Dhh3AoMxa/2+3Q=
github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40=
github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk=
github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28=
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
@@ -229,8 +229,8 @@ github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qe
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU=
github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -875,8 +875,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -917,8 +917,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -966,8 +966,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1079,15 +1079,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1100,8 +1100,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1163,8 +1163,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1357,18 +1357,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk=
modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@@ -1377,8 +1377,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg=
modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM=
modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+220 -53
View File
@@ -2,34 +2,26 @@
# note: we require errors to propagate (don't set -e)
set -u
PROJECT_NAME="syft"
PROJECT_NAME=syft
OWNER=anchore
REPO="${PROJECT_NAME}"
GITHUB_DOWNLOAD_PREFIX=https://github.com/${OWNER}/${REPO}/releases/download
INSTALL_SH_BASE_URL=https://raw.githubusercontent.com/${OWNER}/${PROJECT_NAME}
PROGRAM_ARGS=$@
# signature verification options
# the location to the cosign binary (allowed to be overridden by the user)
COSIGN_BINARY=${COSIGN_BINARY:-cosign}
VERIFY_SIGN=false
# this is the earliest tag in the repo where cosign sign-blob was introduced in the release process (see the goreleaser config)
VERIFY_SIGN_SUPPORTED_VERSION=v0.104.0
# this is the earliest tag in the repo where the -v flag was introduced to this install.sh script
VERIFY_SIGN_FLAG_VERSION=v1.6.0
# do not change the name of this parameter (this must always be backwards compatible)
DOWNLOAD_TAG_INSTALL_SCRIPT=${DOWNLOAD_TAG_INSTALL_SCRIPT:-true}
#
# usage [script-name]
#
usage() (
this=$1
cat <<EOF
$this: download go binaries for anchore/syft
Usage: $this [-b] dir [-d] [tag]
-b the installation directory (dDefaults to ./bin)
-d turns on debug logging
-dd turns on trace logging
[tag] the specific release to use (if missing, then the latest will be used)
EOF
exit 2
)
# ------------------------------------------------------------------------
# https://github.com/client9/shlib - portable posix shell functions
# Public domain - http://unlicense.org
@@ -270,21 +262,21 @@ hash_sha256() (
)
hash_sha256_verify() (
TARGET=$1
target=$1
checksums=$2
if [ -z "$checksums" ]; then
log_err "hash_sha256_verify checksum file not specified in arg2"
log_err "hash_sha256_verify checksum file not specified as argument"
return 1
fi
BASENAME=${TARGET##*/}
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
target_basename=${target##*/}
want=$(grep "${target_basename}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
if [ -z "$want" ]; then
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
log_err "hash_sha256_verify unable to find checksum for '${target}' in '${checksums}'"
return 1
fi
got=$(hash_sha256 "$TARGET")
got=$(hash_sha256 "$target")
if [ "$want" != "$got" ]; then
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
log_err "hash_sha256_verify checksum for '$target' did not verify ${want} vs $got"
return 1
fi
)
@@ -358,28 +350,69 @@ github_release_tag() (
echo "$tag"
)
# github_release_asset_url [release-url-prefix] [name] [version] [output-dir] [filename]
#
# outputs the url to the release asset
#
github_release_asset_url() (
download_url="$1"
name="$2"
version="$3"
filename="$4"
complete_filename="${name}_${version}_${filename}"
complete_url="${download_url}/${complete_filename}"
echo "${complete_url}"
)
# download_github_release_checksums_files [release-url-prefix] [name] [version] [output-dir] [filename]
#
# outputs path to the downloaded checksums related file
#
download_github_release_checksums_files() (
download_url="$1"
name="$2"
version="$3"
output_dir="$4"
filename="$5"
log_trace "download_github_release_checksums_files(url=${download_url}, name=${name}, version=${version}, output_dir=${output_dir}, filename=${filename})"
complete_filename="${name}_${version}_${filename}"
complete_url=$(github_release_asset_url "${download_url}" "${name}" "${version}" "${filename}")
output_path="${output_dir}/${complete_filename}"
http_download "${output_path}" "${complete_url}" ""
asset_file_exists "${output_path}"
log_trace "download_github_release_checksums_files() returned '${output_path}' for file '${complete_filename}'"
echo "${output_path}"
)
# download_github_release_checksums [release-url-prefix] [name] [version] [output-dir]
#
# outputs path to the downloaded checksums file
#
download_github_release_checksums() (
download_url="$1"
name="$2"
version="$3"
output_dir="$4"
download_github_release_checksums_files "$@" "checksums.txt"
)
log_trace "download_github_release_checksums(url=${download_url}, name=${name}, version=${version}, output_dir=${output_dir})"
# github_release_checksums_sig_url [release-url-prefix] [name] [version]
#
# outputs the url to the release checksums signature file
#
github_release_checksums_sig_url() (
github_release_asset_url "$@" "checksums.txt.sig"
)
checksum_filename=${name}_${version}_checksums.txt
checksum_url=${download_url}/${checksum_filename}
output_path="${output_dir}/${checksum_filename}"
http_download "${output_path}" "${checksum_url}" ""
asset_file_exists "${output_path}"
log_trace "download_github_release_checksums() returned '${output_path}'"
echo "${output_path}"
# github_release_checksums_cert_url [release-url-prefix] [name] [version]
#
# outputs the url to the release checksums certificate file
#
github_release_checksums_cert_url() (
github_release_asset_url "$@" "checksums.txt.pem"
)
# search_for_asset [checksums-file-path] [name] [os] [arch] [format]
@@ -535,7 +568,10 @@ download_and_install_asset() (
format="$8"
binary="$9"
asset_filepath=$(download_asset "${download_url}" "${download_path}" "${name}" "${os}" "${arch}" "${version}" "${format}")
if ! asset_filepath=$(download_asset "${download_url}" "${download_path}" "${name}" "${os}" "${arch}" "${version}" "${format}"); then
log_err "could not download asset for os='${os}' arch='${arch}' format='${format}'"
return 1
fi
# don't continue if we couldn't download an asset
if [ -z "${asset_filepath}" ]; then
@@ -546,6 +582,36 @@ download_and_install_asset() (
install_asset "${asset_filepath}" "${install_path}" "${binary}"
)
# verify_sign [checksums-file-path] [certificate-reference] [signature-reference] [version]
#
# attempts verify the signature of the checksums file from the release workflow in Github Actions run against the main branch.
#
verify_sign() {
checksums_file=$1
cert_reference=$2
sig_reference=$3
log_trace "verifying artifact $1"
log_file=$(mktemp)
${COSIGN_BINARY} \
verify-blob "$checksums_file" \
--certificate "$cert_reference" \
--signature "$sig_reference" \
--certificate-identity "https://github.com/${OWNER}/${REPO}/.github/workflows/release.yaml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" > "${log_file}" 2>&1
if [ $? -ne 0 ]; then
log_err "$(cat "${log_file}")"
rm -f "${log_file}"
return 1
fi
rm -f "${log_file}"
}
# download_asset [release-url-prefix] [download-path] [name] [os] [arch] [version] [format] [binary]
#
# outputs the path to the downloaded asset asset_filepath
@@ -572,6 +638,20 @@ download_asset() (
return 1
fi
if [ "$VERIFY_SIGN" = true ]; then
checksum_sig_file_url=$(github_release_checksums_sig_url "${download_url}" "${name}" "${version}")
log_trace "checksums signature url: ${checksum_sig_file_url}"
checksums_cert_file_url=$(github_release_checksums_cert_url "${download_url}" "${name}" "${version}")
log_trace "checksums certificate url: ${checksums_cert_file_url}"
if ! verify_sign "${checksums_filepath}" "${checksums_cert_file_url}" "${checksum_sig_file_url}"; then
log_err "signature verification failed"
return 1
fi
log_info "signature verification succeeded"
fi
asset_url="${download_url}/${asset_filename}"
asset_filepath="${destination}/${asset_filename}"
http_download "${asset_filepath}" "${asset_url}" ""
@@ -609,6 +689,79 @@ install_asset() (
install "${archive_dir}/${binary}" "${destination}/"
)
# compare two semver strings. Returns 0 if version1 >= version2, 1 otherwise.
# Note: pre-release (-) and metadata (+) are not supported.
compare_semver() {
# remove leading 'v' if present
version1=${1#v}
version2=${2#v}
IFS=. read -r major1 minor1 patch1 <<EOF
$version1
EOF
IFS=. read -r major2 minor2 patch2 <<EOF
$version2
EOF
if [ "$major1" -gt "$major2" ]; then
return 0
elif [ "$major1" -lt "$major2" ]; then
return 1
fi
if [ "$minor1" -gt "$minor2" ]; then
return 0
elif [ "$minor1" -lt "$minor2" ]; then
return 1
fi
if [ "$patch1" -gt "$patch2" ]; then
return 0
elif [ "$patch1" -lt "$patch2" ]; then
return 1
fi
# versions are equal
return 0
}
prep_signature_verification() {
version="$1"
if [ "$VERIFY_SIGN" != true ]; then
return 0
fi
# is there any cryptographic material produced at release that we can use for signature verification?
if ! compare_semver "$version" "$VERIFY_SIGN_SUPPORTED_VERSION"; then
log_err "${PROJECT_NAME} release '$version' does not support signature verification"
log_err "you can still install ${PROJECT_NAME} by removing the -v flag or using a release that supports signature verification (>= '$VERIFY_SIGN_SUPPORTED_VERSION')"
log_err "aborting installation"
return 1
else
log_trace "${PROJECT_NAME} release '$version' supports signature verification (>= '$VERIFY_SIGN_SUPPORTED_VERSION')"
fi
# will invoking an earlier version of this script work (considering the -v flag)?
if ! compare_semver "$version" "$VERIFY_SIGN_FLAG_VERSION"; then
# the -v argument did not always exist, so we cannot be guaranteed that invoking an earlier version of this script
# will work (error with "illegal option -v"). However, the user requested signature verification, so we will
# attempt to install the application with this version of the script (keeping signature verification).
DOWNLOAD_TAG_INSTALL_SCRIPT=false
log_debug "provided version install script does not support -v flag (>= '$VERIFY_SIGN_FLAG_VERSION'), using current script for installation"
else
log_trace "provided version install script supports -v flag (>= '$VERIFY_SIGN_FLAG_VERSION')"
fi
# check to see if the cosign binary is installed
if is_command "${COSIGN_BINARY}"; then
log_trace "${COSIGN_BINARY} binary is installed"
else
log_err "signature verification is requested but ${COSIGN_BINARY} binary is not installed (see https://docs.sigstore.dev/system_config/installation/ to install it)"
return 1
fi
}
main() (
# parse arguments
@@ -616,7 +769,7 @@ main() (
install_dir=${install_dir:-./bin}
# note: never change the program flags or arguments (this must always be backwards compatible)
while getopts "b:dh?x" arg; do
while getopts "b:dvh?x" arg; do
case "$arg" in
b) install_dir="$OPTARG" ;;
d)
@@ -628,25 +781,37 @@ main() (
log_set_priority $log_trace_priority
fi
;;
h | \?) usage "$0" ;;
v) VERIFY_SIGN=true;;
h | \?)
cat <<EOF
Download and install a released binary for ${OWNER}/${REPO} from the github releases page
Usage: $0 [-v] [-b DIR] [-d] [TAG]
-b DIR the installation directory (defaults to ./bin)
-d turns on debug logging
-dd turns on trace logging
-v verify checksum signature (requires cosign binary to be installed).
TAG the specific release to use (if missing, then the latest will be used)
EOF
exit 0
;;
x) set -x ;;
esac
done
shift $((OPTIND - 1))
set +u
tag=$1
if [ -z "${tag}" ]; then
log_debug "checking github for the current release tag"
log_info "checking github for the current release tag"
tag=""
else
log_debug "checking github for release tag='${tag}'"
log_info "checking github for release tag='${tag}'"
fi
set -u
tag=$(get_release_tag "${OWNER}" "${REPO}" "${tag}")
if [ "$?" != "0" ]; then
if ! tag=$(get_release_tag "${OWNER}" "${REPO}" "${tag}"); then
log_err "unable to find tag='${tag}'"
log_err "do not specify a version or select a valid version from https://github.com/${OWNER}/${REPO}/releases"
return 1
@@ -661,6 +826,10 @@ main() (
binary=$(get_binary_name "${os}" "${arch}" "${PROJECT_NAME}")
download_url="${GITHUB_DOWNLOAD_PREFIX}/${tag}"
if ! prep_signature_verification "$version"; then
return 1
fi
# we always use the install.sh script that is associated with the tagged release. Why? the latest install.sh is not
# guaranteed to be able to install every version of the application. We use the DOWNLOAD_TAG_INSTALL_SCRIPT env var
# to indicate if we should continue processing with the existing script or to download the script from the given tag.
@@ -678,10 +847,8 @@ main() (
log_debug "downloading files into ${download_dir}"
download_and_install_asset "${download_url}" "${download_dir}" "${install_dir}" "${PROJECT_NAME}" "${os}" "${arch}" "${version}" "${format}" "${binary}"
# don't continue if we couldn't install the asset
if [ "$?" != "0" ]; then
if ! download_and_install_asset "${download_url}" "${download_dir}" "${install_dir}" "${PROJECT_NAME}" "${os}" "${arch}" "${version}" "${format}" "${binary}"; then
log_err "failed to install ${PROJECT_NAME}"
return 1
fi
@@ -697,4 +864,4 @@ if [ -z "${TEST_INSTALL_SH}" ]; then
main "$@"
exit $?
fi
set -u
set -u
+1 -1
View File
@@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// 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 = "16.0.12"
JSONSchemaVersion = "16.0.14"
)
+68
View File
@@ -0,0 +1,68 @@
package internal
import (
"fmt"
"sort"
)
// Set represents a generic set type.
type Set[T comparable] map[T]struct{}
// NewSet creates a new empty Set.
func NewSet[T comparable](start ...T) Set[T] {
ret := make(Set[T])
for _, v := range start {
ret.Add(v)
}
return ret
}
// Add adds elements to the set.
func (s Set[T]) Add(elements ...T) {
for _, e := range elements {
s[e] = struct{}{}
}
}
// Remove removes an element from the set.
func (s Set[T]) Remove(element T) {
delete(s, element)
}
// Contains checks if an element is in the set.
func (s Set[T]) Contains(element T) bool {
_, ok := s[element]
return ok
}
// ToSlice returns a sorted slice of elements in the set.
func (s Set[T]) ToSlice() []T {
ret := make([]T, len(s))
idx := 0
for v := range s {
ret[idx] = v
idx++
}
sort.Slice(ret, func(i, j int) bool {
return fmt.Sprintf("%v", ret[i]) < fmt.Sprintf("%v", ret[j])
})
return ret
}
// Equals checks if two sets are equal.
func (s Set[T]) Equals(o Set[T]) bool {
if len(s) != len(o) {
return false
}
for k := range s {
if !o.Contains(k) {
return false
}
}
return true
}
// Empty checks if the set is empty.
func (s Set[T]) Empty() bool {
return len(s) == 0
}
+146
View File
@@ -0,0 +1,146 @@
package internal
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSet(t *testing.T) {
tests := []struct {
name string
start []int
result Set[int]
}{
{"empty set", []int{}, NewSet[int]()},
{"non-empty set", []int{1, 2, 3}, NewSet(1, 2, 3)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSet(tt.start...)
require.Equal(t, tt.result, s)
})
}
}
func TestAdd(t *testing.T) {
tests := []struct {
name string
input []int
add []int
result Set[int]
}{
{"add to empty set", []int{}, []int{1, 2, 3}, NewSet(1, 2, 3)},
{"add to non-empty set", []int{1}, []int{2, 3}, NewSet(1, 2, 3)},
{"add existing elements", []int{1, 2}, []int{2, 3}, NewSet(1, 2, 3)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSet(tt.input...)
s.Add(tt.add...)
assert.Equal(t, tt.result, s)
})
}
}
func TestRemove(t *testing.T) {
tests := []struct {
name string
input []int
remove int
result Set[int]
}{
{"remove from non-empty set", []int{1, 2, 3}, 2, NewSet(1, 3)},
{"remove non-existent element", []int{1, 2}, 3, NewSet(1, 2)},
{"remove from single-element set", []int{1}, 1, NewSet[int]()},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSet(tt.input...)
s.Remove(tt.remove)
assert.Equal(t, tt.result, s)
})
}
}
func TestContains(t *testing.T) {
tests := []struct {
name string
input []int
contains int
result bool
}{
{"element in set", []int{1, 2, 3}, 2, true},
{"element not in set", []int{1, 2}, 3, false},
{"empty set", []int{}, 1, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSet(tt.input...)
assert.Equal(t, tt.result, s.Contains(tt.contains))
})
}
}
func TestToSlice(t *testing.T) {
tests := []struct {
name string
input []int
result []int
}{
{"non-empty set", []int{3, 1, 2}, []int{1, 2, 3}},
{"empty set", []int{}, []int{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSet(tt.input...)
assert.Equal(t, tt.result, s.ToSlice())
})
}
}
func TestEquals(t *testing.T) {
tests := []struct {
name string
set1 []int
set2 []int
result bool
}{
{"equal sets", []int{1, 2, 3}, []int{3, 2, 1}, true},
{"different sets", []int{1, 2}, []int{2, 3}, false},
{"empty sets", []int{}, []int{}, true},
{"one empty set", []int{1, 2}, []int{}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s1 := NewSet(tt.set1...)
s2 := NewSet(tt.set2...)
assert.Equal(t, tt.result, s1.Equals(s2))
})
}
}
func TestEmpty(t *testing.T) {
tests := []struct {
name string
input []int
result bool
}{
{"non-empty set", []int{1}, false},
{"empty set", []int{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSet(tt.input...)
assert.Equal(t, tt.result, s.Empty())
})
}
}
-61
View File
@@ -1,61 +0,0 @@
package internal
import "sort"
// StringSet represents a set of string types.
type StringSet map[string]struct{}
// NewStringSet creates a new empty StringSet.
func NewStringSet(start ...string) StringSet {
ret := make(StringSet)
for _, s := range start {
ret.Add(s)
}
return ret
}
// Add a string to the set.
func (s StringSet) Add(i ...string) {
for _, str := range i {
s[str] = struct{}{}
}
}
// Remove a string from the set.
func (s StringSet) Remove(i string) {
delete(s, i)
}
// Contains indicates if the given string is contained within the set.
func (s StringSet) Contains(i string) bool {
_, ok := s[i]
return ok
}
// ToSlice returns a sorted slice of strings that are contained within the set.
func (s StringSet) ToSlice() []string {
ret := make([]string, len(s))
idx := 0
for v := range s {
ret[idx] = v
idx++
}
sort.Strings(ret)
return ret
}
func (s StringSet) Equals(o StringSet) bool {
if len(s) != len(o) {
return false
}
for k := range s {
if !o.Contains(k) {
return false
}
}
return true
}
func (s StringSet) Empty() bool {
return len(s) < 1
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+76 -5
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.12/document",
"$id": "anchore.io/schema/syft/json/16.0.14/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@@ -647,12 +647,24 @@
"type": {
"type": "string"
},
"vendor": {
"architecture": {
"type": "string"
},
"osCPE": {
"type": "string"
},
"os": {
"type": "string"
},
"osVersion": {
"type": "string"
},
"system": {
"type": "string"
},
"vendor": {
"type": "string"
},
"sourceRepo": {
"type": "string"
},
@@ -2051,15 +2063,74 @@
"index"
]
},
"PythonPoetryLockEntry": {
"PythonPoetryLockDependencyEntry": {
"properties": {
"index": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"optional": {
"type": "boolean"
},
"markers": {
"type": "string"
},
"extras": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"index"
"name",
"version",
"optional"
]
},
"PythonPoetryLockEntry": {
"properties": {
"index": {
"type": "string"
},
"dependencies": {
"items": {
"$ref": "#/$defs/PythonPoetryLockDependencyEntry"
},
"type": "array"
},
"extras": {
"items": {
"$ref": "#/$defs/PythonPoetryLockExtraEntry"
},
"type": "array"
}
},
"type": "object",
"required": [
"index",
"dependencies"
]
},
"PythonPoetryLockExtraEntry": {
"properties": {
"name": {
"type": "string"
},
"dependencies": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"name",
"dependencies"
]
},
"RDescription": {
@@ -9,13 +9,11 @@ import (
"path/filepath"
"strings"
"github.com/moby/sys/mountinfo"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event"
@@ -43,7 +41,8 @@ func newDirectoryIndexer(path, base string, visitors ...PathIndexVisitor) *direc
[]PathIndexVisitor{
requireFileInfo,
disallowByFileType,
newUnixSystemMountFinder().disallowUnixSystemRuntimePath},
skipPathsByMountTypeAndName(path),
},
visitors...,
),
errPaths: make(map[string]error),
@@ -450,57 +449,6 @@ func (r *directoryIndexer) disallowRevisitingVisitor(_, path string, _ os.FileIn
return nil
}
type unixSystemMountFinder struct {
disallowedMountPaths []string
}
func newUnixSystemMountFinder() unixSystemMountFinder {
infos, err := mountinfo.GetMounts(nil)
if err != nil {
log.WithFields("error", err).Warnf("unable to get system mounts")
return unixSystemMountFinder{}
}
return unixSystemMountFinder{
disallowedMountPaths: keepUnixSystemMountPaths(infos),
}
}
func keepUnixSystemMountPaths(infos []*mountinfo.Info) []string {
var mountPaths []string
for _, info := range infos {
if info == nil {
continue
}
// we're only interested in ignoring the logical filesystems typically found at these mount points:
// - /proc
// - procfs
// - proc
// - /sys
// - sysfs
// - /dev
// - devfs - BSD/darwin flavored systems and old linux systems
// - devtmpfs - driver core maintained /dev tmpfs
// - udev - userspace implementation that replaced devfs
// - tmpfs - used for /dev in special instances (within a container)
switch info.FSType {
case "proc", "procfs", "sysfs", "devfs", "devtmpfs", "udev", "tmpfs":
log.WithFields("mountpoint", info.Mountpoint).Debug("ignoring system mountpoint")
mountPaths = append(mountPaths, info.Mountpoint)
}
}
return mountPaths
}
func (f unixSystemMountFinder) disallowUnixSystemRuntimePath(_, path string, _ os.FileInfo, _ error) error {
if internal.HasAnyOfPrefixes(path, f.disallowedMountPaths...) {
return fs.SkipDir
}
return nil
}
func disallowByFileType(_, _ string, info os.FileInfo, _ error) error {
if info == nil {
// we can't filter out by filetype for non-existent files
@@ -10,7 +10,6 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/moby/sys/mountinfo"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -462,145 +461,3 @@ func relativePath(basePath, givenPath string) string {
return relPath
}
func Test_disallowUnixSystemRuntimePath(t *testing.T) {
unixSubject := unixSystemMountFinder{
// mock out detecting the mount points
disallowedMountPaths: []string{"/proc", "/sys", "/dev"},
}
tests := []struct {
name string
path string
base string
expected error
}{
{
name: "relative path to proc is allowed",
path: "proc/place",
},
{
name: "relative path within proc is not allowed",
path: "/proc/place",
expected: fs.SkipDir,
},
{
name: "path exactly to proc is not allowed",
path: "/proc",
expected: fs.SkipDir,
},
{
name: "similar to proc",
path: "/pro/c",
},
{
name: "similar to proc",
path: "/pro",
},
{
name: "dev is not allowed",
path: "/dev",
expected: fs.SkipDir,
},
{
name: "sys is not allowed",
path: "/sys",
expected: fs.SkipDir,
},
{
name: "unrelated allowed path",
path: "/something/sys",
},
{
name: "do not consider base when matching paths (non-matching)",
base: "/a/b/c",
path: "/a/b/c/dev",
},
{
name: "do not consider base when matching paths (matching)",
base: "/a/b/c",
path: "/dev",
expected: fs.SkipDir,
},
}
for _, test := range tests {
t.Run(test.path, func(t *testing.T) {
assert.Equal(t, test.expected, unixSubject.disallowUnixSystemRuntimePath(test.base, test.path, nil, nil))
})
}
}
func Test_keepUnixSystemMountPaths(t *testing.T) {
tests := []struct {
name string
infos []*mountinfo.Info
want []string
}{
{
name: "all valid filesystems",
infos: []*mountinfo.Info{
{
Mountpoint: "/etc/hostname",
FSType: "/dev/vda1",
},
{
Mountpoint: "/sys/fs/cgroup",
FSType: "cgroup",
},
{
Mountpoint: "/",
FSType: "overlay",
},
},
want: nil,
},
{
name: "no valid filesystems",
infos: []*mountinfo.Info{
{
Mountpoint: "/proc",
FSType: "proc",
},
{
Mountpoint: "/proc-2",
FSType: "procfs",
},
{
Mountpoint: "/sys",
FSType: "sysfs",
},
{
Mountpoint: "/dev",
FSType: "devfs",
},
{
Mountpoint: "/dev-u",
FSType: "udev",
},
{
Mountpoint: "/dev-tmp",
FSType: "devtmpfs",
},
{
Mountpoint: "/run",
FSType: "tmpfs",
},
},
want: []string{
"/proc",
"/proc-2",
"/sys",
"/dev",
"/dev-u",
"/dev-tmp",
"/run",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, keepUnixSystemMountPaths(tt.infos))
})
}
}
+157
View File
@@ -0,0 +1,157 @@
package fileresolver
import (
"io/fs"
"os"
"sort"
"strings"
"github.com/moby/sys/mountinfo"
"github.com/anchore/syft/internal/log"
)
type pathSkipper struct {
// scanTarget is the root path that is being scanned (without any base-path logic applied).
scanTarget string
// ignorableMountTypes is a set of mount types that should be ignored. Optionally a list of paths (the map values)
// can be provided that this mount type should be ignored at. For example in some containers /dev is mounted
// as a tmpfs and should be ignored, but /tmp should not be ignored. An empty list of paths means that paths
// within the mount type should always be ignored.
ignorableMountTypes map[string][]string
// current mount paths for the current system
mounts []*mountinfo.Info
mountsByType map[string][]*mountinfo.Info
}
// skipPathsByMountTypeAndName accepts the root path and returns a PathIndexVisitor that will skip paths based
// the filesystem type, the mountpoint, and configured blocklist paths for each filesystem type.
// This will help syft dodge filesystem topologies that have the potential to make the search space much bigger in
// areas known to not traditionally contain files of interest (installed software). It is meant to allow scanning
// "/" on a unix host to succeed, while also not causing any files in a narrow directory scan to be skipped unnecessarily.
func skipPathsByMountTypeAndName(root string) PathIndexVisitor {
infos, err := mountinfo.GetMounts(nil)
if err != nil {
log.WithFields("error", err).Warnf("unable to get system mounts")
return func(_ string, _ string, _ os.FileInfo, _ error) error {
return nil
}
}
return newPathSkipperFromMounts(root, infos).pathIndexVisitor
}
func newPathSkipperFromMounts(root string, infos []*mountinfo.Info) pathSkipper {
// we're only interested in ignoring the logical filesystems typically found at these mount points:
// - /proc
// - procfs
// - proc
// - /sys
// - sysfs
// - /dev
// - devfs - BSD/darwin flavored systems and old linux systems
// - devtmpfs - driver core maintained /dev tmpfs
// - udev - userspace implementation that replaced devfs
// - tmpfs - used for /dev in special instances (within a container)
ignorableMountTypes := map[string][]string{
"proc": nil,
"procfs": nil,
"sysfs": nil,
"devfs": nil,
"devtmpfs": nil,
"udev": nil,
// note: there should be no order required (e.g. search /sys/thing before /sys) since that would imply that
// we could not ignore a nested path within a path that would be ignored anyway.
"tmpfs": {"/run", "/dev", "/var/run", "/var/lock", "/sys"},
}
// The longest path is the most specific path, e.g.
// if / is mounted as tmpfs, but /home/syft/permanent is mounted as ext4,
// then the mount type for /home/syft/permanent/foo is ext4, and the mount info
// stating that /home/syft/permanent is ext4 has the longer mount point.
sort.Slice(infos, func(i, j int) bool {
return len(infos[i].Mountpoint) > len(infos[j].Mountpoint)
})
mountsByType := make(map[string][]*mountinfo.Info)
for _, mi := range infos {
mountsByType[mi.FSType] = append(mountsByType[mi.FSType], mi)
}
return pathSkipper{
scanTarget: root,
ignorableMountTypes: ignorableMountTypes,
mounts: infos,
mountsByType: mountsByType,
}
}
func (ps pathSkipper) pathIndexVisitor(_ string, givenPath string, _ os.FileInfo, _ error) error {
for _, mi := range ps.mounts {
conditionalPaths, ignorable := ps.ignorableMountTypes[mi.FSType]
if len(conditionalPaths) == 0 {
// Rule 1: ignore any path within a mount point that is of the given filesystem type unconditionally
if !containsPath(givenPath, mi.Mountpoint) {
continue
}
if !ignorable {
// we've matched on the most specific path at this point, which means we should stop searching
// mount points for this path
break
}
log.WithFields(
"path", givenPath,
"mountpoint", mi.Mountpoint,
"fs", mi.FSType,
).Debug("ignoring path based on mountpoint filesystem type")
return fs.SkipDir
}
// Rule 2: ignore any path within a mount point that is of the given filesystem type, only if
// the path is on a known blocklist of paths for that filesystem type.
// For example: /dev can be mounted as a tmpfs, which should always be skipped.
for _, conditionalPath := range conditionalPaths {
if !containsPath(givenPath, conditionalPath) {
continue
}
log.WithFields(
"path", givenPath,
"mountpoint", mi.Mountpoint,
"fs", mi.FSType,
"condition", conditionalPath,
).Debug("ignoring path based on mountpoint filesystem type")
return fs.SkipDir
}
}
return nil
}
func containsPath(p1, p2 string) bool {
p1Clean := simpleClean(p1)
p2Clean := simpleClean(p2)
if p1Clean == p2Clean {
return true
}
return strings.HasPrefix(p1Clean, p2Clean+"/")
}
func simpleClean(p string) string {
p = strings.TrimSpace(p)
if p == "" {
return "."
}
if p == "/" {
return "/"
}
return strings.TrimSuffix(p, "/")
}
@@ -0,0 +1,395 @@
package fileresolver
import (
"io/fs"
"testing"
"github.com/moby/sys/mountinfo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_newPathSkipper(t *testing.T) {
type expect struct {
path string
wantErr assert.ErrorAssertionFunc
}
unixSubject := []*mountinfo.Info{
{
Mountpoint: "/proc",
FSType: "procfs",
},
{
Mountpoint: "/sys",
FSType: "sysfs",
},
{
Mountpoint: "/dev",
FSType: "devfs",
},
{
Mountpoint: "/",
FSType: "/dev/disk3s1s1",
},
{
Mountpoint: "/dev/shm",
FSType: "shm",
},
{
Mountpoint: "/tmp",
FSType: "tmpfs",
},
}
tests := []struct {
name string
root string
base string
mounts []*mountinfo.Info
want []expect
}{
{
name: "happy path",
root: "/somewhere",
mounts: []*mountinfo.Info{
{
Mountpoint: "/home/somewhere/else",
FSType: "/dev/disk3s6",
},
{
Mountpoint: "/somewhere",
FSType: "/dev/disk3s7",
},
},
want: []expect{
{
// within a known mountpoint with valid type (1)
path: "/somewhere/dev",
},
{
// is a known mountpoint with valid type
path: "/somewhere",
},
{
// within a known mountpoint with valid type (2)
path: "/home/somewhere/else/too",
},
{
// outside of any known mountpoint should not be an error
path: "/bogus",
},
},
},
{
name: "ignore paths within a scan target",
root: "/somewhere",
mounts: []*mountinfo.Info{
{
Mountpoint: "/somewhere/doesnt/matter/proc",
FSType: "procfs",
},
{
Mountpoint: "/somewhere",
FSType: "/dev/disk3s7",
},
},
want: []expect{
{
// within a known mountpoint with valid type (1)
path: "/somewhere/dev",
},
{
// is a known mountpoint with valid type
path: "/somewhere",
},
{
// mountpoint that should be ignored
path: "/somewhere/doesnt/matter/proc",
wantErr: assertSkipErr(),
},
{
// within a mountpoint that should be ignored
path: "/somewhere/doesnt/matter/proc",
wantErr: assertSkipErr(),
},
},
},
{
name: "nested mountpoints behave correctly",
root: "/somewhere",
mounts: []*mountinfo.Info{
{
Mountpoint: "/somewhere/dev",
FSType: "devfs",
},
{
Mountpoint: "/somewhere/dev/includeme",
FSType: "/dev/disk3s7",
},
},
want: []expect{
{
// is a known mountpoint with valid type
path: "/somewhere/dev",
wantErr: assertSkipErr(),
},
{
// is a known mountpoint with valid type
path: "/somewhere/dev/includeme",
},
{
// within a known mountpoint with valid type
path: "/somewhere/dev/includeme/too!",
},
},
},
{
name: "keep some tmpfs mounts conditionally",
root: "/",
mounts: []*mountinfo.Info{
{
Mountpoint: "/run/somewhere",
FSType: "tmpfs",
},
{
Mountpoint: "/run/terrafirma",
FSType: "/dev/disk3s8",
},
{
Mountpoint: "/tmp",
FSType: "tmpfs",
},
{
Mountpoint: "/else/othertmp",
FSType: "tmpfs",
},
{
Mountpoint: "/else/othertmp/includeme",
FSType: "/dev/disk3s7",
},
},
want: []expect{
{
// since /run is explicitly ignored, this should be skipped
path: "/run/somewhere/else",
wantErr: assertSkipErr(),
},
{
path: "/run/terrafirma",
},
{
path: "/run/terrafirma/nested",
},
{
path: "/tmp",
},
{
path: "/else/othertmp/includeme",
},
{
path: "/else/othertmp/includeme/nested",
},
{
// no mount path, so we should include it
path: "/somewhere/dev/includeme",
},
{
// keep additional tmpfs mounts that are not explicitly ignored
path: "/else/othertmp",
},
},
},
{
name: "ignore known trixy tmpfs paths",
root: "/",
mounts: []*mountinfo.Info{
{
Mountpoint: "/",
FSType: "/dev/disk3s7",
},
{
Mountpoint: "/dev",
FSType: "tmpfs",
},
{
Mountpoint: "/run",
FSType: "tmpfs",
},
{
Mountpoint: "/var/run",
FSType: "tmpfs",
},
{
Mountpoint: "/var/lock",
FSType: "tmpfs",
},
{
Mountpoint: "/sys",
FSType: "tmpfs",
},
{
Mountpoint: "/tmp",
FSType: "tmpfs",
},
},
want: []expect{
{
path: "/dev",
wantErr: assertSkipErr(),
},
{
path: "/run",
wantErr: assertSkipErr(),
},
{
path: "/var/run",
wantErr: assertSkipErr(),
},
{
path: "/var/lock",
wantErr: assertSkipErr(),
},
{
path: "/sys",
wantErr: assertSkipErr(),
},
// show that we honor ignoring nested paths
{
path: "/sys/nested",
wantErr: assertSkipErr(),
},
// show that paths outside of the known mountpoints are not skipped
{
path: "/stuff",
},
// show that we allow other tmpfs paths that are not on the blocklist
{
path: "/tmp/allowed",
},
// show sibling paths with same prefix (e.g. /sys vs /system) to that of not allowed paths are not skipped
{
path: "/system",
},
},
},
{
name: "test unix paths",
mounts: unixSubject,
root: "/",
want: []expect{
{
// relative path to proc is allowed
path: "proc/place",
},
{
// relative path within proc is not allowed
path: "/proc/place",
wantErr: assertSkipErr(),
},
{
// path exactly to proc is not allowed
path: "/proc",
wantErr: assertSkipErr(),
},
{
// similar to proc
path: "/pro/c",
},
{
// similar to proc
path: "/pro",
},
{
// dev is not allowed
path: "/dev",
wantErr: assertSkipErr(),
},
{
// sys is not allowed
path: "/sys",
wantErr: assertSkipErr(),
},
},
},
{
name: "test unix paths with base",
mounts: unixSubject,
root: "/",
base: "/a/b/c",
want: []expect{
{
// do not consider base when matching paths (non-matching)
path: "/a/b/c/dev",
},
{
// do not consider base when matching paths (matching)
path: "/dev",
wantErr: assertSkipErr(),
},
},
},
{
name: "mimic nixos setup",
root: "/",
mounts: []*mountinfo.Info{
{
Mountpoint: "/",
FSType: "tmpfs", // this is an odd setup, but valid
},
{
Mountpoint: "/home",
FSType: "/dev/disk3s7",
},
},
want: []expect{
{
path: "/home/somewhere",
},
{
path: "/home",
},
{
path: "/somewhere",
},
{
// still not allowed...
path: "/run",
wantErr: assertSkipErr(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.base == "" {
tt.base = tt.root
}
require.NotEmpty(t, tt.want)
ps := newPathSkipperFromMounts(tt.root, tt.mounts)
for _, exp := range tt.want {
t.Run(exp.path, func(t *testing.T) {
got := ps.pathIndexVisitor(tt.base, exp.path, nil, nil)
if exp.wantErr == nil {
assert.NoError(t, got)
return
}
exp.wantErr(t, got)
})
}
})
}
}
func assertSkipErr() assert.ErrorAssertionFunc {
return assertErrorIs(fs.SkipDir)
}
func assertErrorIs(want error) assert.ErrorAssertionFunc {
return func(t assert.TestingT, got error, msgAndArgs ...interface{}) bool {
return assert.ErrorIs(t, got, want, msgAndArgs...)
}
}
+29 -4
View File
@@ -15,9 +15,34 @@ type ClassifierMatch struct {
// ELFBinaryPackageNoteJSONPayload Represents metadata captured from the .note.package section of the binary
type ELFBinaryPackageNoteJSONPayload struct {
Type string `json:"type,omitempty"`
Vendor string `json:"vendor,omitempty"`
System string `json:"system,omitempty"`
// these are well-known fields as defined by systemd ELF package metadata "spec" https://systemd.io/ELF_PACKAGE_METADATA/
// Type is the type of the package (e.g. "rpm", "deb", "apk", etc.)
Type string `json:"type,omitempty"`
// Architecture of the binary package (e.g. "amd64", "arm", etc.)
Architecture string `json:"architecture,omitempty"`
// OS CPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)
OSCPE string `json:"osCPE,omitempty"`
// OS is the OS name, typically corresponding to ID in os-release (e.g. "fedora")
OS string `json:"os,omitempty"`
// osVersion is the version of the OS, typically corresponding to VERSION_ID in os-release (e.g. "33")
OSVersion string `json:"osVersion,omitempty"`
// these are additional fields that are not part of the systemd spec
// System is a context-specific name for the system that the binary package is intended to run on or a part of
System string `json:"system,omitempty"`
// Vendor is the individual or organization that produced the source code for the binary
Vendor string `json:"vendor,omitempty"`
// SourceRepo is the URL to the source repository for which the binary was built from
SourceRepo string `json:"sourceRepo,omitempty"`
Commit string `json:"commit,omitempty"`
// Commit is the commit hash of the source repository for which the binary was built from
Commit string `json:"commit,omitempty"`
}
+4 -2
View File
@@ -22,8 +22,10 @@ func dbEntryDependencySpecifier(p pkg.Package) dependency.Specification {
provides = append(provides, stripVersionSpecifiers(meta.Provides)...)
return dependency.Specification{
Provides: provides,
Requires: stripVersionSpecifiers(meta.Dependencies),
ProvidesRequires: dependency.ProvidesRequires{
Provides: provides,
Requires: stripVersionSpecifiers(meta.Dependencies),
},
}
}
+12 -6
View File
@@ -25,8 +25,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
},
},
},
{
@@ -39,8 +41,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a", "so:libc.musl-x86_64.so.1"},
Requires: []string{"so:libc.musl-x86_64.so.2"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a", "so:libc.musl-x86_64.so.1"},
Requires: []string{"so:libc.musl-x86_64.so.2"},
},
},
},
{
@@ -53,8 +57,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a"},
Requires: nil,
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a"},
Requires: nil,
},
},
},
}
+4 -2
View File
@@ -34,8 +34,10 @@ func dbEntryDependencySpecifier(p pkg.Package) dependency.Specification {
}
return dependency.Specification{
Provides: provides,
Requires: requires,
ProvidesRequires: dependency.ProvidesRequires{
Provides: provides,
Requires: requires,
},
}
}
+12 -6
View File
@@ -25,8 +25,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-c", "a-thing", "a-thing"}, // note: gets deduplicated downstream
Requires: []string{"b-thing"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-c", "a-thing", "a-thing"}, // note: gets deduplicated downstream
Requires: []string{"b-thing"},
},
},
},
{
@@ -39,8 +41,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a", "libtree-sitter.so.me=1-64", "libtree-sitter.so.me"},
Requires: []string{"libtree-sitter.so.thing=2-64"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a", "libtree-sitter.so.me=1-64", "libtree-sitter.so.me"},
Requires: []string{"libtree-sitter.so.thing=2-64"},
},
},
},
{
@@ -53,8 +57,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a"},
Requires: nil,
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a"},
Requires: nil,
},
},
},
}
@@ -616,6 +616,16 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("busybox-binary", "[", "busybox"),
},
},
{
logicalFixture: `util-linux/2.37.4/linux-amd64`,
expected: pkg.Package{
Name: "util-linux",
Version: "2.37.4",
PURL: "pkg:generic/util-linux@2.37.4",
Locations: locations("getopt"),
Metadata: metadata("util-linux-binary"),
},
},
{
logicalFixture: "java-jre-openjdk/1.8.0_352-b08/linux-amd64",
expected: pkg.Package{
+9
View File
@@ -173,6 +173,15 @@ func DefaultClassifiers() []Classifier {
PURL: mustPURL("pkg:generic/busybox@version"),
CPEs: singleCPE("cpe:2.3:a:busybox:busybox:*:*:*:*:*:*:*:*"),
},
{
Class: "util-linux-binary",
FileGlob: "**/getopt",
EvidenceMatcher: FileContentsVersionMatcher(
`\x00util-linux\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "util-linux",
PURL: mustPURL("pkg:generic/util-linux@version"),
CPEs: singleCPE("cpe:2.3:a:kernel:util-linux:*:*:*:*:*:*:*:*"),
},
{
Class: "haproxy-binary",
FileGlob: "**/haproxy",
+53 -6
View File
@@ -2,15 +2,16 @@ package binary
import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet, licenses []pkg.License) pkg.Package {
func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) pkg.Package {
p := pkg.Package{
Name: metadata.Name,
Version: metadata.Version,
Licenses: pkg.NewLicenseSet(licenses...),
Licenses: pkg.NewLicenseSet(pkg.NewLicense(metadata.License)),
PURL: packageURL(metadata),
Type: pkg.BinaryPkg,
Locations: locations,
@@ -23,13 +24,59 @@ func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet, l
}
func packageURL(metadata elfBinaryPackageNotes) string {
// TODO: what if the System value is not set?
var qualifiers []packageurl.Qualifier
os := metadata.OS
osVersion := metadata.OSVersion
atts, err := cpe.NewAttributes(metadata.OSCPE)
if err == nil {
// only "upgrade" the OS information if there is something more specific to use in it's place
if os == "" && osVersion == "" || os == "" && atts.Version != "" || atts.Product != "" && osVersion == "" {
os = atts.Product
osVersion = atts.Version
}
}
if os != "" {
osQualifier := os
if osVersion != "" {
osQualifier += "-" + osVersion
}
qualifiers = append(qualifiers, packageurl.Qualifier{
Key: "distro",
Value: osQualifier,
})
}
ty := purlDistroType(metadata.Type)
namespace := os
if ty == packageurl.TypeGeneric || os == "" {
namespace = metadata.System
}
return packageurl.NewPackageURL(
packageurl.TypeGeneric,
metadata.System,
ty,
namespace,
metadata.Name,
metadata.Version,
nil,
qualifiers,
"",
).ToString()
}
func purlDistroType(ty string) string {
switch ty {
case "rpm":
return packageurl.TypeRPM
case "deb":
return packageurl.TypeDebian
case "apk":
return packageurl.TypeAlpine
case "alpm":
return "alpm"
}
return packageurl.TypeGeneric
}
@@ -1,8 +1,10 @@
package binary
import (
"bytes"
"context"
"debug/elf"
"encoding/binary"
"encoding/json"
"fmt"
@@ -79,7 +81,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
}
// create a package for each unique name/version pair (based on the first note found)
pkgs = append(pkgs, newELFPackage(notes[0], noteLocations, nil))
pkgs = append(pkgs, newELFPackage(notes[0], noteLocations))
}
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
@@ -154,11 +156,42 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
return nil, err
}
var metadata elfBinaryPackageNotes
if err := json.Unmarshal(notes, &metadata); err != nil {
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
if len(notes) == 0 {
return nil, nil
}
return &metadata, err
{
var metadata elfBinaryPackageNotes
if err := json.Unmarshal(notes, &metadata); err == nil {
return &metadata, nil
}
}
{
var header elf64SectionHeader
headerSize := binary.Size(header) / 4
if len(notes) > headerSize {
var metadata elfBinaryPackageNotes
newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
if err := json.Unmarshal(newPayload, &metadata); err == nil {
return &metadata, nil
}
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
}
}
return nil, err
}
type elf64SectionHeader struct {
ShName uint32
ShType uint32
ShFlags uint64
ShAddr uint64
ShOffset uint64
ShSize uint64
ShLink uint32
ShInfo uint32
ShAddralign uint64
ShEntsize uint64
}
@@ -9,53 +9,115 @@ import (
)
func Test_ELF_Package_Cataloger(t *testing.T) {
expectedPkgs := []pkg.Package{
cases := []struct {
name string
fixture string
expected []pkg.Package
}{
{
Name: "libhello_world.so",
Version: "0.01",
PURL: "pkg:generic/syftsys/libhello_world.so@0.01",
FoundBy: "",
Locations: file.NewLocationSet(
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so"),
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so"),
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so"),
),
Language: "",
Type: pkg.BinaryPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "testfixture",
Vendor: "syft",
System: "syftsys",
SourceRepo: "https://github.com/someone/somewhere.git",
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
name: "go case",
fixture: "elf-test-fixtures",
expected: []pkg.Package{
{
Name: "libhello_world.so",
Version: "0.01",
PURL: "pkg:generic/syftsys/libhello_world.so@0.01",
Locations: file.NewLocationSet(
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so"),
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so"),
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so"),
),
Licenses: pkg.NewLicenseSet(
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
),
Type: pkg.BinaryPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "testfixture",
Vendor: "syft",
System: "syftsys",
SourceRepo: "https://github.com/someone/somewhere.git",
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
},
},
{
Name: "syfttestfixture",
Version: "0.01",
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
Locations: file.NewLocationSet(
file.NewLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/elfbinwithnestedlib").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin1").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin2").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Licenses: pkg.NewLicenseSet(
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
),
Type: pkg.BinaryPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "testfixture",
Vendor: "syft",
System: "syftsys",
SourceRepo: "https://github.com/someone/somewhere.git",
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
},
},
},
},
{
Name: "syfttestfixture",
Version: "0.01",
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
FoundBy: "",
Locations: file.NewLocationSet(
file.NewLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/elfbinwithnestedlib").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin1").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin2").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Language: "",
Type: pkg.BinaryPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "testfixture",
Vendor: "syft",
System: "syftsys",
SourceRepo: "https://github.com/someone/somewhere.git",
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
name: "fedora 64 bit binaries",
fixture: "image-fedora-64bit",
expected: []pkg.Package{
{
Name: "coreutils",
Version: "9.5-1.fc41",
PURL: "pkg:rpm/fedora/coreutils@9.5-1.fc41?distro=fedora-40",
Locations: file.NewLocationSet(
file.NewLocation("/sha256sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
Architecture: "x86_64",
OSCPE: "cpe:/o:fedoraproject:fedora:40",
},
},
},
},
{
name: "fedora 32 bit binaries",
fixture: "image-fedora-32bit",
expected: []pkg.Package{
{
Name: "coreutils",
Version: "9.0-5.fc36",
PURL: "pkg:rpm/fedora/coreutils@9.0-5.fc36?distro=fedora-36",
Locations: file.NewLocationSet(
file.NewLocation("/sha256sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
Architecture: "arm",
OSCPE: "cpe:/o:fedoraproject:fedora:36",
},
},
},
},
}
pkgtest.NewCatalogTester().
WithImageResolver(t, "elf-test-fixtures").
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
Expects(expectedPkgs, nil).
TestCataloger(t, NewELFPackageCataloger())
for _, v := range cases {
t.Run(v.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
WithImageResolver(t, v.fixture).
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
Expects(v.expected, nil).
TestCataloger(t, NewELFPackageCataloger())
})
}
}
+82 -8
View File
@@ -14,36 +14,110 @@ import (
func Test_packageURL(t *testing.T) {
tests := []struct {
name string
notes elfBinaryPackageNotes
expected string
metadata elfBinaryPackageNotes
want string
}{
{
name: "elf-binary-package-cataloger",
notes: elfBinaryPackageNotes{
metadata: elfBinaryPackageNotes{
Name: "github.com/anchore/syft",
Version: "v0.1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
System: "syftsys",
},
},
expected: "pkg:generic/syftsys/github.com/anchore/syft@v0.1.0",
want: "pkg:generic/syftsys/github.com/anchore/syft@v0.1.0",
},
{
name: "elf binary package short name",
notes: elfBinaryPackageNotes{
metadata: elfBinaryPackageNotes{
Name: "go.opencensus.io",
Version: "v0.23.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
System: "syftsys",
},
},
expected: "pkg:generic/syftsys/go.opencensus.io@v0.23.0",
want: "pkg:generic/syftsys/go.opencensus.io@v0.23.0",
},
{
name: "no info",
metadata: elfBinaryPackageNotes{
Name: "test",
Version: "1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
},
},
want: "pkg:rpm/test@1.0",
},
{
name: "with system",
metadata: elfBinaryPackageNotes{
Name: "test",
Version: "1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
System: "system",
},
},
want: "pkg:rpm/system/test@1.0",
},
{
name: "with os info preferred",
metadata: elfBinaryPackageNotes{
Name: "test",
Version: "1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
OS: "fedora",
OSVersion: "2.0",
OSCPE: "cpe:/o:someone:redhat:3.0",
},
},
want: "pkg:rpm/fedora/test@1.0?distro=fedora-2.0",
},
{
name: "with os info fallback to CPE parsing (missing version)",
metadata: elfBinaryPackageNotes{
Name: "test",
Version: "1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
OS: "fedora",
OSCPE: "cpe:/o:someone:redhat:3.0",
},
},
want: "pkg:rpm/redhat/test@1.0?distro=redhat-3.0",
},
{
name: "with os info preferred (missing OS)",
metadata: elfBinaryPackageNotes{
Name: "test",
Version: "1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
Type: "rpm",
OSVersion: "2.0",
OSCPE: "cpe:/o:someone:redhat:3.0",
},
},
want: "pkg:rpm/redhat/test@1.0?distro=redhat-3.0",
},
{
name: "missing type",
metadata: elfBinaryPackageNotes{
Name: "test",
Version: "1.0",
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
System: "system",
},
},
want: "pkg:generic/system/test@1.0",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, packageURL(test.notes))
assert.Equal(t, test.want, packageURL(test.metadata))
})
}
}
@@ -82,7 +156,7 @@ func Test_newELFPackage(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := newELFPackage(test.metadata, file.NewLocationSet(), nil)
actual := newELFPackage(test.metadata, file.NewLocationSet())
if diff := cmp.Diff(test.expected, actual, cmpopts.IgnoreFields(pkg.Package{}, "id"), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})); diff != "" {
t.Errorf("newELFPackage() mismatch (-want +got):\n%s", diff)
}
@@ -17,6 +17,14 @@ from-images:
paths:
- /bin/[
- name: util-linux
version: 2.37.4
images:
- ref: cirros:0.6.2@sha256:7ba1f5bc57923f65c99677e53758d386320ed699b621d9630b1f2bd7ceb3a878
platform: linux/amd64
paths:
- /usr/bin/getopt
- version: 5.1.16
images:
- ref: bash:5.1.16@sha256:c7a903a541d8f5fe693cbe7f5ece18a1b6a03734c76092d2b153d7e98a964927
@@ -0,0 +1,5 @@
FROM --platform=linux/arm arm32v7/fedora:36 as build
FROM scratch
COPY --from=build /bin/sha256sum /sha256sum
COPY --from=build /bin/sha1sum /sha1sum
@@ -0,0 +1,5 @@
FROM --platform=linux/amd64 fedora:41 as build
FROM scratch
COPY --from=build /bin/sha256sum /sha256sum
COPY --from=build /bin/sha1sum /sha1sum
+4 -2
View File
@@ -38,8 +38,10 @@ func dbEntryDependencySpecifier(p pkg.Package) dependency.Specification {
}
return dependency.Specification{
Provides: provides,
Requires: requires,
ProvidesRequires: dependency.ProvidesRequires{
Provides: provides,
Requires: requires,
},
}
}
+12 -6
View File
@@ -25,8 +25,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
},
},
},
{
@@ -39,8 +41,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a", "foo"},
Requires: []string{"libgmp10", "default-mta", "mail-transport-agent"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a", "foo"},
Requires: []string{"libgmp10", "default-mta", "mail-transport-agent"},
},
},
},
{
@@ -53,8 +57,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a"},
Requires: nil,
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a"},
Requires: nil,
},
},
},
}
+9 -101
View File
@@ -4,16 +4,9 @@ Package golang provides a concrete Cataloger implementation relating to packages
package golang
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
@@ -30,102 +23,17 @@ func NewGoModuleFileCataloger(opts CatalogerConfig) pkg.Cataloger {
c := goModCataloger{
licenses: newGoLicenses(modFileCatalogerName, opts),
}
return &progressingCataloger{
cataloger: generic.NewCataloger(modFileCatalogerName).
WithParserByGlobs(c.parseGoModFile, "**/go.mod"),
}
return generic.NewCataloger(modFileCatalogerName).
WithParserByGlobs(c.parseGoModFile, "**/go.mod")
}
// NewGoModuleBinaryCataloger returns a new cataloger object that searches within binaries built by the go compiler.
func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger {
return &progressingCataloger{
cataloger: generic.NewCataloger(binaryCatalogerName).
WithParserByMimeTypes(
newGoBinaryCataloger(opts).parseGoBinary,
mimetype.ExecutableMIMETypeSet.List()...,
),
}
}
type progressingCataloger struct {
cataloger *generic.Cataloger
}
func (p *progressingCataloger) Name() string {
return p.cataloger.Name()
}
func (p *progressingCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
pkgs, relationships, err := p.cataloger.Catalog(ctx, resolver)
goCompilerPkgs := []pkg.Package{}
totalLocations := file.NewLocationSet()
for _, goPkg := range pkgs {
mValue, ok := goPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry)
if !ok {
continue
}
// go binary packages should only contain a single location
for _, location := range goPkg.Locations.ToSlice() {
if !totalLocations.Contains(location) {
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
if stdLibPkg != nil {
goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg)
totalLocations.Add(location)
}
}
}
}
pkgs = append(pkgs, goCompilerPkgs...)
return pkgs, relationships, err
}
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
stdlibCpe, err := generateStdlibCpe(version)
if err != nil {
return nil
}
goCompilerPkg := &pkg.Package{
Name: "stdlib",
Version: version,
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
CPEs: []cpe.CPE{stdlibCpe},
Locations: location,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
Language: pkg.Go,
Type: pkg.GoModulePkg,
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: version,
},
}
goCompilerPkg.SetID()
return goCompilerPkg
}
func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) {
// GoCompiledVersion when pulled from a binary is prefixed by go
version = strings.TrimPrefix(version, "go")
// we also need to trim starting from the first +<metadata> to
// correctly extract potential rc candidate information for cpe generation
// ex: 2.0.0-rc.1+build.123 -> 2.0.0-rc.1; if no + is found then + is returned
after, _, found := strings.Cut("+", version)
if found {
version = after
}
// extracting <version> and <candidate>
// https://regex101.com/r/985GsI/1
captureGroups := internal.MatchNamedCaptureGroups(versionCandidateGroups, version)
vr, ok := captureGroups["version"]
if !ok || vr == "" {
return stdlibCpe, fmt.Errorf("could not match candidate version for: %s", version)
}
cpeString := fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", captureGroups["version"])
if candidate, ok := captureGroups["candidate"]; ok && candidate != "" {
cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate)
}
return cpe.New(cpeString, cpe.GeneratedSource)
return generic.NewCataloger(binaryCatalogerName).
WithParserByMimeTypes(
newGoBinaryCataloger(opts).parseGoBinary,
mimetype.ExecutableMIMETypeSet.List()...,
).
WithProcessors(stdlibProcessor)
}
@@ -8,6 +8,92 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_PackageCataloger_Binary(t *testing.T) {
tests := []struct {
name string
fixture string
expectedPkgs []string
expectedRels []string
}{
{
name: "simple module with dependencies",
fixture: "image-small",
expectedPkgs: []string{
"anchore.io/not/real @ (devel) (/run-me)",
"github.com/andybalholm/brotli @ v1.0.1 (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me)",
"github.com/klauspost/compress @ v1.11.4 (/run-me)",
"github.com/klauspost/pgzip @ v1.2.5 (/run-me)",
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me)",
"github.com/nwaples/rardecode @ v1.1.0 (/run-me)",
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me)",
"github.com/ulikunitz/xz @ v0.5.9 (/run-me)",
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me)",
"stdlib @ go1.22.4 (/run-me)",
},
expectedRels: []string{
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"stdlib @ go1.22.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
},
},
{
name: "partially built binary",
// the difference is the build flags used to build the binary... they will not reference the module directly
// see the dockerfile for details
fixture: "image-not-a-module",
expectedPkgs: []string{
"command-line-arguments @ (devel) (/run-me)", // this is the difference!
"github.com/andybalholm/brotli @ v1.0.1 (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me)",
"github.com/klauspost/compress @ v1.11.4 (/run-me)",
"github.com/klauspost/pgzip @ v1.2.5 (/run-me)",
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me)",
"github.com/nwaples/rardecode @ v1.1.0 (/run-me)",
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me)",
"github.com/ulikunitz/xz @ v0.5.9 (/run-me)",
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me)",
"stdlib @ go1.22.4 (/run-me)",
},
expectedRels: []string{
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
"stdlib @ go1.22.4 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
WithImageResolver(t, test.fixture).
ExpectsPackageStrings(test.expectedPkgs).
ExpectsRelationshipStrings(test.expectedRels).
TestCataloger(t, NewGoModuleBinaryCataloger(DefaultCatalogerConfig()))
})
}
}
func Test_Mod_Cataloger_Globs(t *testing.T) {
tests := []struct {
name string
+27 -7
View File
@@ -69,17 +69,38 @@ func (c *goBinaryCataloger) parseGoBinary(_ context.Context, resolver file.Resol
mods := scanFile(unionReader, reader.RealPath)
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
var rels []artifact.Relationship
for _, mod := range mods {
pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, mod.arch, unionReader)...)
var depPkgs []pkg.Package
mainPkg, depPkgs := c.buildGoPkgInfo(resolver, reader.Location, mod, mod.arch, unionReader)
if mainPkg != nil {
rels = createModuleRelationships(*mainPkg, depPkgs)
pkgs = append(pkgs, *mainPkg)
}
pkgs = append(pkgs, depPkgs...)
}
return pkgs, nil, nil
return pkgs, rels, nil
}
func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) []pkg.Package {
func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.Relationship {
var relationships []artifact.Relationship
for _, dep := range deps {
relationships = append(relationships, artifact.Relationship{
From: dep,
To: main,
Type: artifact.DependencyOfRelationship,
})
}
return relationships
}
func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) (*pkg.Package, []pkg.Package) {
var pkgs []pkg.Package
if mod == nil {
return pkgs
return nil, pkgs
}
var empty debug.Module
@@ -110,13 +131,12 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
}
if mod.Main == empty {
return pkgs
return nil, pkgs
}
main := c.makeGoMainPackage(resolver, mod, arch, location, reader)
pkgs = append(pkgs, main)
return pkgs
return &main, pkgs
}
func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
@@ -964,7 +964,10 @@ func TestBuildGoPkgInfo(t *testing.T) {
c := newGoBinaryCataloger(DefaultCatalogerConfig())
reader, err := unionreader.GetUnionReader(io.NopCloser(strings.NewReader(test.binaryContent)))
require.NoError(t, err)
pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.mod.arch, reader)
mainPkg, pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.mod.arch, reader)
if mainPkg != nil {
pkgs = append(pkgs, *mainPkg)
}
require.Len(t, pkgs, len(test.expected))
for i, p := range pkgs {
pkgtest.AssertPackagesEqual(t, test.expected[i], p)
+100
View File
@@ -0,0 +1,100 @@
package golang
import (
"fmt"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func stdlibProcessor(pkgs []pkg.Package, relationships []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
compilerPkgs, newRelationships := stdlibPackageAndRelationships(pkgs)
return append(pkgs, compilerPkgs...), append(relationships, newRelationships...), err
}
func stdlibPackageAndRelationships(pkgs []pkg.Package) ([]pkg.Package, []artifact.Relationship) {
var goCompilerPkgs []pkg.Package
var relationships []artifact.Relationship
totalLocations := file.NewLocationSet()
for _, goPkg := range pkgs {
mValue, ok := goPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry)
if !ok {
continue
}
// go binary packages should only contain a single location
for _, location := range goPkg.Locations.ToSlice() {
if totalLocations.Contains(location) {
continue
}
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
if stdLibPkg != nil {
goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg)
totalLocations.Add(location)
}
relationships = append(relationships, artifact.Relationship{
From: *stdLibPkg,
To: goPkg,
Type: artifact.DependencyOfRelationship,
})
}
}
return goCompilerPkgs, relationships
}
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
stdlibCpe, err := generateStdlibCpe(version)
if err != nil {
return nil
}
goCompilerPkg := &pkg.Package{
Name: "stdlib",
Version: version,
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
CPEs: []cpe.CPE{stdlibCpe},
Locations: location,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
Language: pkg.Go,
Type: pkg.GoModulePkg,
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: version,
},
}
goCompilerPkg.SetID()
return goCompilerPkg
}
func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) {
// GoCompiledVersion when pulled from a binary is prefixed by go
version = strings.TrimPrefix(version, "go")
// we also need to trim starting from the first +<metadata> to
// correctly extract potential rc candidate information for cpe generation
// ex: 2.0.0-rc.1+build.123 -> 2.0.0-rc.1; if no + is found then + is returned
after, _, found := strings.Cut("+", version)
if found {
version = after
}
// extracting <version> and <candidate>
// https://regex101.com/r/985GsI/1
captureGroups := internal.MatchNamedCaptureGroups(versionCandidateGroups, version)
vr, ok := captureGroups["version"]
if !ok || vr == "" {
return stdlibCpe, fmt.Errorf("could not match candidate version for: %s", version)
}
cpeString := fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", captureGroups["version"])
if candidate, ok := captureGroups["candidate"]; ok && candidate != "" {
cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate)
}
return cpe.New(cpeString, cpe.GeneratedSource)
}
@@ -0,0 +1,137 @@
package golang
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/cmptest"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func Test_stdlibPackageAndRelationships(t *testing.T) {
tests := []struct {
name string
pkgs []pkg.Package
wantPkgs int
wantRels int
}{
{
name: "no packages",
},
{
name: "ignore non-go-binary packages",
pkgs: []pkg.Package{
{
Name: "not-go",
Version: "1.0.0",
Metadata: pkg.GolangModuleEntry{},
},
},
wantPkgs: 0,
wantRels: 0,
},
{
name: "with go-binary packages -- missing location",
pkgs: []pkg.Package{
{
Name: "github.com/something/go",
Version: "1.0.0",
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: "go1.22.2",
MainModule: "github.com/something/go",
},
},
},
wantPkgs: 0,
wantRels: 0,
},
{
name: "with go-binary packages",
pkgs: []pkg.Package{
{
Name: "github.com/something/go",
Version: "1.0.0",
Locations: file.NewLocationSet(file.NewLocation("/bin/my-app")),
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: "go1.22.2",
MainModule: "github.com/something/go",
},
},
},
wantPkgs: 1,
wantRels: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPkgs, gotRels := stdlibPackageAndRelationships(tt.pkgs)
assert.Len(t, gotPkgs, tt.wantPkgs)
assert.Len(t, gotRels, tt.wantRels)
})
}
}
func Test_stdlibPackageAndRelationships_values(t *testing.T) {
loc := file.NewLocation("/bin/my-app")
locSet := file.NewLocationSet(loc)
p := pkg.Package{
Name: "github.com/something/go",
Version: "1.0.0",
Locations: locSet,
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: "go1.22.2",
MainModule: "github.com/something/go",
},
}
p.SetID()
expectedPkg := pkg.Package{
Name: "stdlib",
Version: "go1.22.2",
PURL: packageURL("stdlib", "1.22.2"),
Language: pkg.Go,
Type: pkg.GoModulePkg,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
CPEs: []cpe.CPE{
{
Attributes: cpe.MustAttributes("cpe:2.3:a:golang:go:1.22.2:-:*:*:*:*:*:*"),
Source: "syft-generated",
},
},
Locations: locSet,
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: "go1.22.2",
},
}
expectedPkg.SetID()
expectedRel := artifact.Relationship{
From: expectedPkg,
To: p,
Type: artifact.DependencyOfRelationship,
}
gotPkgs, gotRels := stdlibPackageAndRelationships([]pkg.Package{p})
require.Len(t, gotPkgs, 1)
gotPkg := gotPkgs[0]
if d := cmp.Diff(expectedPkg, gotPkg, cmptest.DefaultCommonOptions()...); d != "" {
t.Errorf("unexpected package (-want +got): %s", d)
}
require.Len(t, gotRels, 1)
gotRel := gotRels[0]
if d := cmp.Diff(expectedRel, gotRel, cmptest.DefaultCommonOptions()...); d != "" {
t.Errorf("unexpected relationship (-want +got): %s", d)
}
}
@@ -0,0 +1 @@
/run-me
@@ -0,0 +1,25 @@
FROM --platform=linux/amd64 golang:1.22.4 AS builder
RUN mkdir /app
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go main.go
# building with "." vs "main.go" is a difference in the buildinfo section
# specifically with main.go the buildinfo section will contain the following:
#
# path command-line-arguments
#
# instead of
#
# mod anchore.io/not/real
#
RUN CGO_ENABLED=0 GOOS=linux go build -o run-me main.go
FROM scratch
COPY --from=builder /app/run-me /run-me
ENTRYPOINT ["/run-me"]
@@ -0,0 +1,17 @@
module anchore.io/not/real
go 1.22.1
require github.com/mholt/archiver/v3 v3.5.1
require (
github.com/andybalholm/brotli v1.0.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/klauspost/compress v1.11.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
)
@@ -0,0 +1,26 @@
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -0,0 +1,19 @@
package main
import "github.com/mholt/archiver/v3"
func main() {
z := archiver.Zip{
MkdirAll: true,
SelectiveCompression: true,
ContinueOnError: false,
OverwriteExisting: false,
ImplicitTopLevelFolder: false,
}
err := z.Archive([]string{"main.go"}, "test.zip")
if err != nil {
panic(err)
}
}
@@ -0,0 +1 @@
/run-me
@@ -0,0 +1,16 @@
FROM --platform=linux/amd64 golang:1.22.4 AS builder
RUN mkdir /app
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go main.go
RUN CGO_ENABLED=0 GOOS=linux go build -o run-me .
FROM scratch
COPY --from=builder /app/run-me /run-me
ENTRYPOINT ["/run-me"]
@@ -0,0 +1,17 @@
module anchore.io/not/real
go 1.22.1
require github.com/mholt/archiver/v3 v3.5.1
require (
github.com/andybalholm/brotli v1.0.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/klauspost/compress v1.11.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
)
@@ -0,0 +1,26 @@
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -0,0 +1,19 @@
package main
import "github.com/mholt/archiver/v3"
func main() {
z := archiver.Zip{
MkdirAll: true,
SelectiveCompression: true,
ContinueOnError: false,
OverwriteExisting: false,
ImplicitTopLevelFolder: false,
}
err := z.Archive([]string{"main.go"}, "test.zip")
if err != nil {
panic(err)
}
}
@@ -10,6 +10,9 @@
"github.com/SimonWaldherr/zplgfa": [
"cpe:2.3:a:simonwaldherr:zplgfa:*:*:*:*:*:go:*:*"
],
"github.com/anchore/stereoscope": [
"cpe:2.3:a:anchore:stereoscope:*:*:*:*:*:go:*:*"
],
"github.com/apptainer/apptainer": [
"cpe:2.3:a:lfprojects:apptainer:*:*:*:*:*:go:*:*"
],
@@ -5,6 +5,7 @@ import (
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
@@ -14,6 +15,14 @@ import (
// requires for itself. These strings can represent anything from file paths, package names, or any other concept
// that is useful for dependency resolution within that packing ecosystem.
type Specification struct {
ProvidesRequires
// Variants allows for specifying multiple sets of provides/requires for a single package. This is useful
// in cases when you have conditional optional dependencies for a package.
Variants []ProvidesRequires
}
type ProvidesRequires struct {
// Provides holds a list of abstract resources that this package provides for other packages.
Provides []string
@@ -37,55 +46,70 @@ func Processor(s Specifier) generic.Processor {
}
}
rels = append(rels, resolve(s, pkgs)...)
rels = append(rels, Resolve(s, pkgs)...)
return pkgs, rels, err
}
}
// resolve will create relationships between packages based on the dependency claims of each package.
func resolve(specifier Specifier, pkgs []pkg.Package) (relationships []artifact.Relationship) {
pkgsProvidingResource := make(map[string][]artifact.ID)
// Resolve will create relationships between packages based on the dependency claims of each package.
func Resolve(specifier Specifier, pkgs []pkg.Package) (relationships []artifact.Relationship) {
pkgsProvidingResource := make(map[string]internal.Set[artifact.ID])
pkgsByID := make(map[artifact.ID]pkg.Package)
specsByPkg := make(map[artifact.ID]Specification)
specsByPkg := make(map[artifact.ID][]ProvidesRequires)
for _, p := range pkgs {
id := p.ID()
pkgsByID[id] = p
specsByPkg[id] = specifier(p)
for _, resource := range deduplicate(specifier(p).Provides) {
pkgsProvidingResource[resource] = append(pkgsProvidingResource[resource], id)
}
specsByPkg[id] = allProvides(pkgsProvidingResource, id, specifier(p))
}
seen := strset.New()
for _, dependantPkg := range pkgs {
spec := specsByPkg[dependantPkg.ID()]
for _, resource := range deduplicate(spec.Requires) {
for _, providingPkgID := range pkgsProvidingResource[resource] {
// prevent creating duplicate relationships
pairKey := string(providingPkgID) + "-" + string(dependantPkg.ID())
if seen.Has(pairKey) {
continue
specs := specsByPkg[dependantPkg.ID()]
for _, spec := range specs {
for _, resource := range deduplicate(spec.Requires) {
for providingPkgID := range pkgsProvidingResource[resource] {
// prevent creating duplicate relationships
pairKey := string(providingPkgID) + "-" + string(dependantPkg.ID())
if seen.Has(pairKey) {
continue
}
providingPkg := pkgsByID[providingPkgID]
relationships = append(relationships,
artifact.Relationship{
From: providingPkg,
To: dependantPkg,
Type: artifact.DependencyOfRelationship,
},
)
seen.Add(pairKey)
}
providingPkg := pkgsByID[providingPkgID]
relationships = append(relationships,
artifact.Relationship{
From: providingPkg,
To: dependantPkg,
Type: artifact.DependencyOfRelationship,
},
)
seen.Add(pairKey)
}
}
}
return relationships
}
func allProvides(pkgsProvidingResource map[string]internal.Set[artifact.ID], id artifact.ID, spec Specification) []ProvidesRequires {
prs := []ProvidesRequires{spec.ProvidesRequires}
prs = append(prs, spec.Variants...)
for _, pr := range prs {
for _, resource := range deduplicate(pr.Provides) {
if pkgsProvidingResource[resource] == nil {
pkgsProvidingResource[resource] = internal.NewSet[artifact.ID]()
}
pkgsProvidingResource[resource].Add(id)
}
}
return prs
}
func deduplicate(ss []string) []string {
// note: we sort the set such that multiple invocations of this function will be deterministic
set := strset.New(ss...)
@@ -44,6 +44,19 @@ func Test_resolve(t *testing.T) {
"b": /* depends on */ {"a"},
},
},
{
name: "find relationships between packages with variants",
s: newSpecifierBuilder().
WithProvides(a /* provides */, "a-resource").
WithRequires(b /* requires */, "a[variant]").
WithProvides(c /* provides */, "c-resource").
WithVariant(a /* provides */, "variant" /* which requires */, "c-resource").
Specifier(),
want: map[string][]string{
"b":/* depends on */ {"a"},
"a":/* depends on */ {"c"},
},
},
{
name: "deduplicates provider keys",
s: newSpecifierBuilder().
@@ -71,7 +84,7 @@ func Test_resolve(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
relationships := resolve(tt.s, subjects)
relationships := Resolve(tt.s, subjects)
if d := cmp.Diff(tt.want, abstractRelationships(t, relationships)); d != "" {
t.Errorf("unexpected relationships (-want +got):\n%s", d)
}
@@ -82,12 +95,14 @@ func Test_resolve(t *testing.T) {
type specifierBuilder struct {
provides map[string][]string
requires map[string][]string
variants map[string]map[string][]string
}
func newSpecifierBuilder() *specifierBuilder {
return &specifierBuilder{
provides: make(map[string][]string),
requires: make(map[string][]string),
variants: make(map[string]map[string][]string),
}
}
@@ -101,11 +116,30 @@ func (m *specifierBuilder) WithRequires(p pkg.Package, requires ...string) *spec
return m
}
func (m *specifierBuilder) WithVariant(p pkg.Package, variantName string, requires ...string) *specifierBuilder {
if _, ok := m.variants[p.Name]; !ok {
m.variants[p.Name] = make(map[string][]string)
}
m.variants[p.Name][variantName] = append(m.variants[p.Name][variantName], requires...)
return m
}
func (m specifierBuilder) Specifier() Specifier {
return func(p pkg.Package) Specification {
var prs []ProvidesRequires
for variantName, requires := range m.variants[p.Name] {
prs = append(prs, ProvidesRequires{
Provides: []string{p.Name + "[" + variantName + "]"},
Requires: requires,
})
}
return Specification{
Provides: m.provides[p.Name],
Requires: m.requires[p.Name],
ProvidesRequires: ProvidesRequires{
Provides: m.provides[p.Name],
Requires: m.requires[p.Name],
},
Variants: prs,
}
}
}
@@ -2,8 +2,10 @@ package pkgtest
import (
"context"
"fmt"
"io"
"os"
"sort"
"strings"
"testing"
@@ -40,6 +42,7 @@ type CatalogTester struct {
compareOptions []cmp.Option
locationComparer cmptest.LocationComparer
licenseComparer cmptest.LicenseComparer
packageStringer func(pkg.Package) string
customAssertions []func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship)
}
@@ -48,6 +51,7 @@ func NewCatalogTester() *CatalogTester {
wantErr: require.NoError,
locationComparer: cmptest.DefaultLocationComparer,
licenseComparer: cmptest.DefaultLicenseComparer,
packageStringer: stringPackage,
ignoreUnfulfilledPathResponses: map[string][]string{
"FilesByPath": {
// most catalogers search for a linux release, which will not be fulfilled in testing
@@ -187,6 +191,23 @@ func (p *CatalogTester) Expects(pkgs []pkg.Package, relationships []artifact.Rel
return p
}
func (p *CatalogTester) WithPackageStringer(fn func(pkg.Package) string) *CatalogTester {
p.packageStringer = fn
return p
}
func (p *CatalogTester) ExpectsPackageStrings(expected []string) *CatalogTester {
return p.ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, _ []artifact.Relationship) {
diffPackages(t, expected, pkgs, p.packageStringer)
})
}
func (p *CatalogTester) ExpectsRelationshipStrings(expected []string) *CatalogTester {
return p.ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
diffRelationships(t, expected, relationships, pkgs, p.packageStringer)
})
}
func (p *CatalogTester) ExpectsResolverPathResponses(locations []string) *CatalogTester {
p.expectedPathResponses = locations
return p
@@ -347,3 +368,70 @@ func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff)
}
}
func diffPackages(t *testing.T, expected []string, actual []pkg.Package, pkgStringer func(pkg.Package) string) {
t.Helper()
sort.Strings(expected)
if d := cmp.Diff(expected, stringPackages(actual, pkgStringer)); d != "" {
t.Errorf("unexpected package strings (-want, +got): %s", d)
}
}
func diffRelationships(t *testing.T, expected []string, actual []artifact.Relationship, pkgs []pkg.Package, pkgStringer func(pkg.Package) string) {
t.Helper()
pkgsByID := make(map[artifact.ID]pkg.Package)
for _, p := range pkgs {
pkgsByID[p.ID()] = p
}
sort.Strings(expected)
if d := cmp.Diff(expected, stringRelationships(actual, pkgsByID, pkgStringer)); d != "" {
t.Errorf("unexpected relationship strings (-want, +got): %s", d)
}
}
func stringRelationships(relationships []artifact.Relationship, nameLookup map[artifact.ID]pkg.Package, pkgStringer func(pkg.Package) string) []string {
var result []string
for _, r := range relationships {
var fromName, toName string
{
fromPkg, ok := nameLookup[r.From.ID()]
if !ok {
fromName = string(r.From.ID())
} else {
fromName = pkgStringer(fromPkg)
}
}
{
toPkg, ok := nameLookup[r.To.ID()]
if !ok {
toName = string(r.To.ID())
} else {
toName = pkgStringer(toPkg)
}
}
result = append(result, fromName+" ["+string(r.Type)+"] "+toName)
}
sort.Strings(result)
return result
}
func stringPackages(pkgs []pkg.Package, pkgStringer func(pkg.Package) string) []string {
var result []string
for _, p := range pkgs {
result = append(result, pkgStringer(p))
}
sort.Strings(result)
return result
}
func stringPackage(p pkg.Package) string {
locs := p.Locations.ToSlice()
var loc string
if len(locs) > 0 {
loc = p.Locations.ToSlice()[0].RealPath
}
return fmt.Sprintf("%s @ %s (%s)", p.Name, p.Version, loc)
}
@@ -76,6 +76,27 @@ func TestParseRockspec(t *testing.T) {
},
},
},
{
Fixture: "test-fixtures/rockspec/luasyslog-2.0.1-1.rockspec",
ExpectedPkg: pkg.Package{
Name: "luasyslog",
Version: "2.0.1-1",
PURL: "pkg:luarocks/luasyslog@2.0.1-1",
Type: pkg.LuaRocksPkg,
Language: pkg.Lua,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT/X11", file.NewLocation("test-fixtures/rockspec/luasyslog-2.0.1-1.rockspec")),
),
Metadata: pkg.LuaRocksPackage{
Name: "luasyslog",
Version: "2.0.1-1",
License: "MIT/X11",
Homepage: "https://github.com/lunarmodules/luasyslog",
Description: "Syslog logging for Lua",
URL: "git://github.com/lunarmodules/luasyslog.git",
},
},
},
}
for _, test := range tests {
+270 -23
View File
@@ -45,7 +45,8 @@ func parseRockspecData(reader io.Reader) (rockspec, error) {
}
i := 0
blocks, err := parseRockspecBlock(data, &i)
locals := make(map[string]string)
blocks, err := parseRockspecBlock(data, &i, locals)
if err != nil {
return noReturn, err
@@ -56,9 +57,9 @@ func parseRockspecData(reader io.Reader) (rockspec, error) {
}, nil
}
func parseRockspecBlock(data []byte, i *int) ([]rockspecNode, error) {
func parseRockspecBlock(data []byte, i *int, locals map[string]string) ([]rockspecNode, error) {
var out []rockspecNode
var iterator func(data []byte, i *int) (*rockspecNode, error)
var iterator func(data []byte, i *int, locals map[string]string) (*rockspecNode, error)
parsing.SkipWhitespace(data, i)
@@ -67,6 +68,14 @@ func parseRockspecBlock(data []byte, i *int) ([]rockspecNode, error) {
}
c := data[*i]
// Block starting with a comment
if c == '-' {
parseComment(data, i)
parsing.SkipWhitespace(data, i)
c = data[*i]
}
switch {
case c == '"' || c == '\'':
iterator = parseRockspecListItem
@@ -77,7 +86,7 @@ func parseRockspecBlock(data []byte, i *int) ([]rockspecNode, error) {
}
for *i < len(data) {
item, err := iterator(data, i)
item, err := iterator(data, i, locals)
if err != nil {
return nil, fmt.Errorf("%w\n%s", err, parsing.PrintError(data, *i))
}
@@ -99,7 +108,7 @@ func parseRockspecBlock(data []byte, i *int) ([]rockspecNode, error) {
}
//nolint:funlen, gocognit
func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
func parseRockspecNode(data []byte, i *int, locals map[string]string) (*rockspecNode, error) {
parsing.SkipWhitespace(data, i)
if *i >= len(data) {
@@ -136,7 +145,7 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
return nil, fmt.Errorf("invalid literal character: %s", string(c))
}
key, err := parseRockspecLiteral(data, i)
key, err := parseRockspecLiteral(data, i, locals)
if err != nil {
return nil, err
}
@@ -147,6 +156,16 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
return nil, fmt.Errorf("unexpected end of node at %d", *i)
}
if key == "local" {
err := parseLocal(data, i, locals)
if err != nil {
return nil, err
}
return &rockspecNode{
key: ",",
}, nil
}
c = data[*i]
if c != '=' {
return nil, fmt.Errorf("unexpected character: %s", string(c))
@@ -159,6 +178,14 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
return nil, fmt.Errorf("unexpected end of node at %d", *i)
}
if key == "build" {
skipBuildNode(data, i)
return &rockspecNode{
key: ",",
}, nil
}
c = data[*i]
switch c {
@@ -180,7 +207,7 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
parsing.SkipWhitespace(data, i)
obj, err := parseRockspecBlock(data, i)
obj, err := parseRockspecBlock(data, i, locals)
if err != nil {
return nil, err
@@ -190,16 +217,10 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
return &rockspecNode{
key, value,
}, nil
case '"', '\'':
str, err := parseRockspecString(data, i)
if err != nil {
return nil, err
}
value := str.value
case '(':
skipExpression(data, i)
return &rockspecNode{
key, value,
key: ",",
}, nil
case '[':
offset := *i + 1
@@ -214,7 +235,7 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
*i++
str, err := parseRockspecString(data, i)
str, err := parseRockspecString(data, i, locals)
if err != nil {
return nil, err
@@ -234,10 +255,18 @@ func parseRockspecNode(data []byte, i *int) (*rockspecNode, error) {
}, nil
}
return nil, nil
value, err := parseRockspecValue(data, i, locals, "")
if err != nil {
return nil, err
}
return &rockspecNode{
key, value,
}, nil
}
func parseRockspecListItem(data []byte, i *int) (*rockspecNode, error) {
func parseRockspecListItem(data []byte, i *int, locals map[string]string) (*rockspecNode, error) {
parsing.SkipWhitespace(data, i)
if *i >= len(data) {
@@ -269,14 +298,70 @@ func parseRockspecListItem(data []byte, i *int) (*rockspecNode, error) {
}, nil
}
str, err := parseRockspecString(data, i)
str, err := parseRockspecString(data, i, locals)
if err != nil {
return nil, err
}
return str, nil
}
func parseRockspecLiteral(data []byte, i *int) (string, error) {
func parseRockspecValue(data []byte, i *int, locals map[string]string, initialValue string) (string, error) {
c := data[*i]
var value string
switch c {
case '"', '\'':
str, err := parseRockspecString(data, i, locals)
if err != nil {
return "", err
}
value = str.value.(string)
default:
local, err := parseRockspecLiteral(data, i, locals)
if err != nil {
return "", err
}
l, ok := locals[local]
if !ok {
return "", fmt.Errorf("unknown local: %s", local)
}
value = l
}
value = fmt.Sprintf("%s%s", initialValue, value)
skipWhitespaceNoNewLine(data, i)
if len(data) > *i+2 {
if data[*i] == '.' && data[*i+1] == '.' {
*i += 2
skipWhitespaceNoNewLine(data, i)
if *i >= len(data) {
return "", fmt.Errorf("unexpected end of expression at %d", *i)
}
v, err := parseRockspecValue(data, i, locals, value)
if err != nil {
return "", err
}
value = v
}
}
return value, nil
}
func parseRockspecLiteral(data []byte, i *int, locals map[string]string) (string, error) {
var buf bytes.Buffer
out:
for *i < len(data) {
@@ -284,7 +369,7 @@ out:
switch {
case c == '[':
*i++
nested, err := parseRockspecString(data, i)
nested, err := parseRockspecString(data, i, locals)
if err != nil {
return "", err
}
@@ -303,7 +388,7 @@ out:
return buf.String(), nil
}
func parseRockspecString(data []byte, i *int) (*rockspecNode, error) {
func parseRockspecString(data []byte, i *int, _ map[string]string) (*rockspecNode, error) {
delim := data[*i]
var endDelim byte
switch delim {
@@ -344,9 +429,171 @@ func parseComment(data []byte, i *int) {
}
}
//nolint:funlen
func parseLocal(data []byte, i *int, locals map[string]string) error {
keys := []string{}
values := []string{}
keys:
for {
parsing.SkipWhitespace(data, i)
key, err := parseRockspecLiteral(data, i, locals)
if err != nil {
return err
}
if key == "function" {
err := skipFunction(data, i)
if err != nil {
return err
}
return nil
}
keys = append(keys, key)
parsing.SkipWhitespace(data, i)
c := data[*i]
switch c {
case ',':
*i++
continue
case '=':
*i++
break keys
default:
return fmt.Errorf("unexpected character: %s", string(c))
}
}
values:
for {
skipWhitespaceNoNewLine(data, i)
c := data[*i]
switch c {
case '"', '\'':
value, err := parseRockspecString(data, i, locals)
if err != nil {
return err
}
values = append(values, value.value.(string))
default:
ref, err := parseRockspecLiteral(data, i, locals)
if err != nil {
return err
}
// Skip if it's an expression
skipWhitespaceNoNewLine(data, i)
c := data[*i]
var value string
if c != '\n' && c != '\r' {
skipExpression(data, i)
value = ""
} else {
value = locals[ref]
}
values = append(values, value)
}
skipWhitespaceNoNewLine(data, i)
c = data[*i]
switch c {
case ',':
*i++
continue
case '\n', '\r':
parsing.SkipWhitespace(data, i)
break values
}
}
if len(keys) != len(values) {
return fmt.Errorf("expected %d values got %d", len(keys), len(values))
}
for i := 0; i < len(keys); i++ {
locals[keys[i]] = values[i]
}
return nil
}
func skipBuildNode(data []byte, i *int) {
bracesCount := 0
for *i < len(data) {
c := data[*i]
switch c {
case '{':
bracesCount++
case '}':
bracesCount--
}
if bracesCount == 0 {
return
}
*i++
}
}
func skipFunction(data []byte, i *int) error {
blocks := 1
for *i < len(data)-5 {
if parsing.IsWhitespace(data[*i]) {
switch {
case string(data[*i+1:*i+3]) == "if" && parsing.IsWhitespace(data[*i+3]):
blocks++
*i += 3
case string(data[*i+1:*i+4]) == "end" && parsing.IsWhitespace(data[*i+4]):
blocks--
*i += 4
if blocks == 0 {
return nil
}
default:
*i++
}
} else {
*i++
}
}
return fmt.Errorf("unterminated function at %d", *i)
}
func skipExpression(data []byte, i *int) {
parseComment(data, i)
}
func skipWhitespaceNoNewLine(data []byte, i *int) {
for *i < len(data) && (data[*i] == ' ' || data[*i] == '\t') {
*i++
}
}
func isLiteral(c byte) bool {
if c == '[' || c == ']' {
return true
}
if c == '.' {
return false
}
return parsing.IsLiteral(c)
}
@@ -60,6 +60,49 @@ multiline = [[
a multiline
string
]]
`,
},
{
name: "variables",
content: `
local foo = "bar"
local baz = foo
hello = baz
`,
},
{
name: "multiple variables in one line",
content: `
local foo, bar = "hello", "world"
baz = foo
test = bar
`,
},
{
name: "skip expressions",
content: `
test = (hello == "world") and "foo" or "bar"
baz = "123"
`,
},
{
name: "skip expressions in locals",
content: `
local var1 = "foo"
local var2 = var1 == "foo" and "true" or ("false")
foo = "bar"
`,
},
{
name: "concatenation",
content: `
local foo = "bar"
local baz = "123"
hello = "world"..baz
baz = foo.." "..baz
test = foo .. baz
`,
},
{
@@ -80,6 +123,17 @@ object = {
hello = "world"
-- this is another comment
}
`,
},
{
name: "content start with comment",
content: `
foo = "bar"
-- this is a comment
object = {
-- this is another comment
hello = "world"
}
`,
},
{
@@ -91,6 +145,33 @@ list = {
-- "baz"
"hello"
}
`,
},
{
name: "skip build section",
content: `
foo = "bar"
build = {
a = {
{
content
}
}
}
bar = "baz"
`,
},
{
name: "skip functions",
content: `
local function test
if foo == bar then
if hello = world then
blah
end
end
end
test = "blah"
`,
},
{
@@ -153,6 +234,20 @@ list = {
"bar",
-`,
},
{
name: "undefined local",
wantErr: require.Error,
content: `
test = hello
`,
},
{
name: "unterminated concatenation",
wantErr: require.Error,
content: `
local foo = "123"
hello = foo.. `,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -0,0 +1,36 @@
local package_name = "luasyslog"
local package_version = "2.0.1"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "dev") and "main" or nil,
tag = (package_version ~= "dev") and package_version or nil,
}
description = {
summary = "Syslog logging for Lua",
detailed = [[
Addon for LuaLogging to log to the system log on unix systems.
Can also be used without LuaLogging to directly write to syslog.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1",
"lualogging >= 1.4.0, < 2.0.0",
}
build = {
type = "builtin",
modules = {
lsyslog = {
sources = "lsyslog.c",
},
["logging.syslog"] = "syslog.lua",
}
}
+204 -44
View File
@@ -4,13 +4,10 @@ import (
"context"
"fmt"
"path"
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@@ -385,6 +382,201 @@ func Test_PackageCataloger_Globs(t *testing.T) {
}
}
func Test_PoetryLockCataloger_Relationships(t *testing.T) {
tests := []struct {
name string
fixture string
expectedRelationships []string
}{
{
name: "no dependencies",
fixture: "test-fixtures/poetry/dev-deps",
expectedRelationships: nil,
},
{
name: "simple dependencies",
fixture: "test-fixtures/poetry/simple-deps",
expectedRelationships: []string{
"certifi @ 2024.2.2 (.) [dependency-of] requests @ 2.32.2 (.)",
"charset-normalizer @ 3.3.2 (.) [dependency-of] requests @ 2.32.2 (.)",
"idna @ 3.7 (.) [dependency-of] requests @ 2.32.2 (.)",
"urllib3 @ 2.2.1 (.) [dependency-of] requests @ 2.32.2 (.)",
},
},
{
name: "multiple extras",
fixture: "test-fixtures/poetry/multiple-extras",
expectedRelationships: []string{
"anyio @ 4.3.0 (.) [dependency-of] anyio @ 4.3.0 (.)",
"anyio @ 4.3.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"anyio @ 4.3.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"brotli @ 1.1.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"brotlicffi @ 1.1.0.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"certifi @ 2024.2.2 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"certifi @ 2024.2.2 (.) [dependency-of] httpx @ 0.27.0 (.)",
"cffi @ 1.16.0 (.) [dependency-of] brotlicffi @ 1.1.0.0 (.)",
"h11 @ 0.14.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"h2 @ 4.1.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"h2 @ 4.1.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"hpack @ 4.0.0 (.) [dependency-of] h2 @ 4.1.0 (.)",
"httpcore @ 1.0.5 (.) [dependency-of] httpx @ 0.27.0 (.)",
"hyperframe @ 6.0.1 (.) [dependency-of] h2 @ 4.1.0 (.)",
"idna @ 3.7 (.) [dependency-of] anyio @ 4.3.0 (.)",
"idna @ 3.7 (.) [dependency-of] httpx @ 0.27.0 (.)",
"pycparser @ 2.22 (.) [dependency-of] cffi @ 1.16.0 (.)",
"sniffio @ 1.3.1 (.) [dependency-of] anyio @ 4.3.0 (.)",
"sniffio @ 1.3.1 (.) [dependency-of] httpx @ 0.27.0 (.)",
"socksio @ 1.0.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"socksio @ 1.0.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
},
},
{
name: "nested extras",
fixture: "test-fixtures/poetry/nested-extras",
expectedRelationships: []string{
"annotated-types @ 0.7.0 (.) [dependency-of] pydantic @ 2.7.1 (.)",
"anyio @ 4.3.0 (.) [dependency-of] anyio @ 4.3.0 (.)",
"anyio @ 4.3.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"anyio @ 4.3.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"anyio @ 4.3.0 (.) [dependency-of] starlette @ 0.37.2 (.)",
"anyio @ 4.3.0 (.) [dependency-of] watchfiles @ 0.21.0 (.)",
"certifi @ 2024.2.2 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"certifi @ 2024.2.2 (.) [dependency-of] httpx @ 0.27.0 (.)",
"click @ 8.1.7 (.) [dependency-of] httpx @ 0.27.0 (.)",
"click @ 8.1.7 (.) [dependency-of] python-dotenv @ 1.0.1 (.)",
"click @ 8.1.7 (.) [dependency-of] typer @ 0.12.3 (.)",
"click @ 8.1.7 (.) [dependency-of] uvicorn @ 0.29.0 (.)",
"colorama @ 0.4.6 (.) [dependency-of] click @ 8.1.7 (.)",
"colorama @ 0.4.6 (.) [dependency-of] pygments @ 2.18.0 (.)",
"colorama @ 0.4.6 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
"dnspython @ 2.6.1 (.) [dependency-of] email-validator @ 2.1.1 (.)",
"email-validator @ 2.1.1 (.) [dependency-of] pydantic @ 2.7.1 (.)",
"fastapi @ 0.111.0 (.) [dependency-of] fastapi-cli @ 0.0.4 (.)",
"fastapi-cli @ 0.0.4 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"h11 @ 0.14.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"h11 @ 0.14.0 (.) [dependency-of] uvicorn @ 0.29.0 (.)",
"httpcore @ 1.0.5 (.) [dependency-of] dnspython @ 2.6.1 (.)",
"httpcore @ 1.0.5 (.) [dependency-of] httpx @ 0.27.0 (.)",
"httptools @ 0.6.1 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
"httpx @ 0.27.0 (.) [dependency-of] dnspython @ 2.6.1 (.)",
"httpx @ 0.27.0 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"httpx @ 0.27.0 (.) [dependency-of] starlette @ 0.37.2 (.)",
"idna @ 3.7 (.) [dependency-of] anyio @ 4.3.0 (.)",
"idna @ 3.7 (.) [dependency-of] dnspython @ 2.6.1 (.)",
"idna @ 3.7 (.) [dependency-of] email-validator @ 2.1.1 (.)",
"idna @ 3.7 (.) [dependency-of] httpx @ 0.27.0 (.)",
"itsdangerous @ 2.2.0 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"itsdangerous @ 2.2.0 (.) [dependency-of] starlette @ 0.37.2 (.)",
"jinja2 @ 3.1.4 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"jinja2 @ 3.1.4 (.) [dependency-of] starlette @ 0.37.2 (.)",
"markdown-it-py @ 3.0.0 (.) [dependency-of] rich @ 13.7.1 (.)",
"mdurl @ 0.1.2 (.) [dependency-of] markdown-it-py @ 3.0.0 (.)",
"orjson @ 3.10.3 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"pydantic @ 2.7.1 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"pydantic @ 2.7.1 (.) [dependency-of] pydantic-extra-types @ 2.7.0 (.)",
"pydantic @ 2.7.1 (.) [dependency-of] pydantic-settings @ 2.2.1 (.)",
"pydantic-core @ 2.18.2 (.) [dependency-of] pydantic @ 2.7.1 (.)",
"pydantic-extra-types @ 2.7.0 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"pydantic-settings @ 2.2.1 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"pygments @ 2.18.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"pygments @ 2.18.0 (.) [dependency-of] rich @ 13.7.1 (.)",
"python-dotenv @ 1.0.1 (.) [dependency-of] pydantic-settings @ 2.2.1 (.)",
"python-dotenv @ 1.0.1 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
"python-multipart @ 0.0.9 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"python-multipart @ 0.0.9 (.) [dependency-of] starlette @ 0.37.2 (.)",
"pyyaml @ 6.0.1 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"pyyaml @ 6.0.1 (.) [dependency-of] markdown-it-py @ 3.0.0 (.)",
"pyyaml @ 6.0.1 (.) [dependency-of] pydantic-settings @ 2.2.1 (.)",
"pyyaml @ 6.0.1 (.) [dependency-of] python-multipart @ 0.0.9 (.)",
"pyyaml @ 6.0.1 (.) [dependency-of] starlette @ 0.37.2 (.)",
"pyyaml @ 6.0.1 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
"rich @ 13.7.1 (.) [dependency-of] httpx @ 0.27.0 (.)",
"rich @ 13.7.1 (.) [dependency-of] typer @ 0.12.3 (.)",
"shellingham @ 1.5.4 (.) [dependency-of] typer @ 0.12.3 (.)",
"sniffio @ 1.3.1 (.) [dependency-of] anyio @ 4.3.0 (.)",
"sniffio @ 1.3.1 (.) [dependency-of] httpx @ 0.27.0 (.)",
"starlette @ 0.37.2 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"typer @ 0.12.3 (.) [dependency-of] fastapi-cli @ 0.0.4 (.)",
"typing-extensions @ 4.12.0 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"typing-extensions @ 4.12.0 (.) [dependency-of] pydantic @ 2.7.1 (.)",
"typing-extensions @ 4.12.0 (.) [dependency-of] pydantic-core @ 2.18.2 (.)",
"typing-extensions @ 4.12.0 (.) [dependency-of] typer @ 0.12.3 (.)",
"ujson @ 5.10.0 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"uvicorn @ 0.29.0 (.) [dependency-of] fastapi @ 0.111.0 (.)",
"uvicorn @ 0.29.0 (.) [dependency-of] fastapi-cli @ 0.0.4 (.)",
"uvloop @ 0.19.0 (.) [dependency-of] anyio @ 4.3.0 (.)",
"uvloop @ 0.19.0 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
"watchfiles @ 0.21.0 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
"websockets @ 12.0 (.) [dependency-of] uvicorn @ 0.29.0 (.)", // proof of uvicorn[standard]
},
},
{
name: "conflicting extras",
fixture: "test-fixtures/poetry/conflicting-with-extras",
expectedRelationships: []string{
"anyio @ 4.3.0 (.) [dependency-of] anyio @ 4.3.0 (.)",
"anyio @ 4.3.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"anyio @ 4.3.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"brotli @ 1.1.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"brotlicffi @ 1.1.0.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"certifi @ 2024.2.2 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"certifi @ 2024.2.2 (.) [dependency-of] httpx @ 0.27.0 (.)",
"cffi @ 1.16.0 (.) [dependency-of] brotlicffi @ 1.1.0.0 (.)",
"colorama @ 0.4.6 (.) [dependency-of] rich @ 0.3.3 (.)",
"h11 @ 0.14.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"h2 @ 4.1.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"h2 @ 4.1.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"hpack @ 4.0.0 (.) [dependency-of] h2 @ 4.1.0 (.)",
"httpcore @ 1.0.5 (.) [dependency-of] httpx @ 0.27.0 (.)",
"hyperframe @ 6.0.1 (.) [dependency-of] h2 @ 4.1.0 (.)",
"idna @ 3.7 (.) [dependency-of] anyio @ 4.3.0 (.)",
"idna @ 3.7 (.) [dependency-of] httpx @ 0.27.0 (.)",
"pprintpp @ 0.4.0 (.) [dependency-of] rich @ 0.3.3 (.)",
"pycparser @ 2.22 (.) [dependency-of] cffi @ 1.16.0 (.)",
"sniffio @ 1.3.1 (.) [dependency-of] anyio @ 4.3.0 (.)",
"sniffio @ 1.3.1 (.) [dependency-of] httpx @ 0.27.0 (.)",
"socksio @ 1.0.0 (.) [dependency-of] httpcore @ 1.0.5 (.)",
"socksio @ 1.0.0 (.) [dependency-of] httpx @ 0.27.0 (.)",
"typing-extensions @ 3.10.0.2 (.) [dependency-of] rich @ 0.3.3 (.)",
// ideally we should NOT see these dependencies. However, they are technically installed in the environment
// and an import is present in httpx for each of these, so in theory they are actually dependencies even
// though our pyproject.toml looks like this:
//
// [tool.poetry.dependencies]
// python = "^3.11"
// httpx = {extras = ["brotli", "http2", "socks"], version = "^0.27.0"}
// pygments = "1.6"
// click = "<8"
// rich = "<10"
//
// note that pygments, click, and rich are installed outside of the allowable ranges for the given
// httpx package version constraints, per the poetry.lock:
//
// # for package httpx
// [package.extras]
// cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
//
// note: the pyproject.toml and poetry.lock state are consistent with each other (just with
// "poetry install" and "poetry lock", and nothing was forced!)
"click @ 7.1.2 (.) [dependency-of] httpx @ 0.27.0 (.)",
"pygments @ 1.6 (.) [dependency-of] httpx @ 0.27.0 (.)",
"rich @ 0.3.3 (.) [dependency-of] httpx @ 0.27.0 (.)",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
WithPackageStringer(stringPackage).
ExpectsRelationshipStrings(test.expectedRelationships).
TestCataloger(t, NewPackageCataloger(DefaultCatalogerConfig()))
})
}
}
func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
tests := []struct {
name string
@@ -482,52 +674,20 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
WithImageResolver(t, test.fixture).
ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
diffRelationships(t, test.expectedRelationships, relationships, pkgs)
}).
WithPackageStringer(stringPackage).
ExpectsRelationshipStrings(test.expectedRelationships).
TestCataloger(t, NewInstalledPackageCataloger())
})
}
}
func diffRelationships(t *testing.T, expected []string, actual []artifact.Relationship, pkgs []pkg.Package) {
pkgsByID := make(map[artifact.ID]pkg.Package)
for _, p := range pkgs {
pkgsByID[p.ID()] = p
}
sort.Strings(expected)
if d := cmp.Diff(expected, stringRelationships(actual, pkgsByID)); d != "" {
t.Errorf("unexpected relationships (-want, +got): %s", d)
func stringPackage(p pkg.Package) string {
locs := p.Locations.ToSlice()
var loc string
if len(locs) > 0 {
// we want the location of the site-packages, not the metadata file
loc = path.Dir(path.Dir(p.Locations.ToSlice()[0].RealPath))
}
}
func stringRelationships(relationships []artifact.Relationship, nameLookup map[artifact.ID]pkg.Package) []string {
var result []string
for _, r := range relationships {
var fromName, toName string
{
fromPkg, ok := nameLookup[r.From.ID()]
if !ok {
fromName = string(r.From.ID())
} else {
loc := path.Dir(path.Dir(fromPkg.Locations.ToSlice()[0].RealPath))
fromName = fmt.Sprintf("%s @ %s (%s)", fromPkg.Name, fromPkg.Version, loc)
}
}
{
toPkg, ok := nameLookup[r.To.ID()]
if !ok {
toName = string(r.To.ID())
} else {
loc := path.Dir(path.Dir(toPkg.Locations.ToSlice()[0].RealPath))
toName = fmt.Sprintf("%s @ %s (%s)", toPkg.Name, toPkg.Version, loc)
}
}
result = append(result, fromName+" ["+string(r.Type)+"] "+toName)
}
sort.Strings(result)
return result
return fmt.Sprintf("%s @ %s (%s)", p.Name, p.Version, loc)
}
+81 -7
View File
@@ -15,7 +15,72 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
)
var _ dependency.Specifier = wheelEggDependencySpecifier
func poetryLockDependencySpecifier(p pkg.Package) dependency.Specification {
meta, ok := p.Metadata.(pkg.PythonPoetryLockEntry)
if !ok {
log.Tracef("cataloger failed to extract poetry lock metadata for package %+v", p.Name)
return dependency.Specification{}
}
// this package reference always includes the package name and no extras
provides := []string{packageRef(p.Name, "")}
var requires []string
// add required dependencies (those which a marker is not present indicating it is explicitly optional or needs an extra marker)
for _, dep := range meta.Dependencies {
if isDependencyForExtra(dep) {
continue
}
// we always have the base package requirement without any extras to get base dependencies
requires = append(requires, packageRef(dep.Name, ""))
// if there are extras, we need to add a requirement for each extra individually
// for example:
// uvicorn = {version = ">=0.12.0", extras = ["standard", "else"]}
// then we must install uvicorn with the extras "standard" and "else" to satisfy the requirement
for _, extra := range dep.Extras {
// always refer to extras with the package name (e.g. name[extra])
// note: this must always be done independent of other extras (e.g. name[extra1] and name[extra2] separately
// is correct and name[extra1,extra2] will result in dependency resolution failure)
requires = append(requires, packageRef(dep.Name, extra))
}
}
var variants []dependency.ProvidesRequires
for _, extra := range meta.Extras {
variants = append(variants,
dependency.ProvidesRequires{
// always refer to extras with the package name (e.g. name[extra])
// note: this must always be done independent of other extras (e.g. name[extra1] and name[extra2] separately
// is correct and name[extra1,extra2] will result in dependency resolution failure)
Provides: []string{packageRef(p.Name, extra.Name)},
Requires: extractPackageNames(extra.Dependencies),
},
)
}
return dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: provides,
Requires: requires,
},
Variants: variants,
}
}
func isDependencyForExtra(dep pkg.PythonPoetryLockDependencyEntry) bool {
return strings.Contains(dep.Markers, "extra ==")
}
func packageRef(name, extra string) string {
cleanExtra := strings.TrimSpace(extra)
cleanName := strings.TrimSpace(name)
if cleanExtra == "" {
return cleanName
}
return cleanName + "[" + cleanExtra + "]"
}
func wheelEggDependencySpecifier(p pkg.Package) dependency.Specification {
meta, ok := p.Metadata.(pkg.PythonPackage)
@@ -35,7 +100,7 @@ func wheelEggDependencySpecifier(p pkg.Package) dependency.Specification {
// how extras function, where there tends to be a try/except around imports as an indication if that extra
// functionality should be executed or not (there isn't a package declaration to reference at runtime).
for _, depSpecifier := range meta.RequiresDist {
depSpecifier = extractPackageNameFromRequiresDest(depSpecifier)
depSpecifier = extractPackageName(depSpecifier)
if depSpecifier == "" {
continue
}
@@ -43,20 +108,29 @@ func wheelEggDependencySpecifier(p pkg.Package) dependency.Specification {
}
return dependency.Specification{
Provides: provides,
Requires: requires,
ProvidesRequires: dependency.ProvidesRequires{
Provides: provides,
Requires: requires,
},
}
}
// extractPackageNameFromRequiresDest removes any extras or version constraints from a given Requires-Dist field value,
// leaving only the package name.
func extractPackageNameFromRequiresDest(s string) string {
// extractPackageName removes any extras or version constraints from a given Requires-Dist field value (and
// semantically similar fields), leaving only the package name.
func extractPackageName(s string) string {
// examples:
// html5lib ; extra == 'html5lib' --> html5lib
// soupsieve (>1.2) --> soupsieve
return strings.TrimSpace(internal.SplitAny(s, "(;")[0])
}
func extractPackageNames(ss []string) []string {
var names []string
for _, s := range ss {
names = append(names, extractPackageName(s))
}
return names
}
func wheelEggRelationships(ctx context.Context, resolver file.Resolver, pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
if err != nil {
@@ -1 +1,275 @@
package python
import (
"os"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
)
func Test_poetryLockDependencySpecifier(t *testing.T) {
tests := []struct {
name string
p pkg.Package
want dependency.Specification
}{
{
name: "no dependencies",
p: pkg.Package{
Name: "foo",
Metadata: pkg.PythonPoetryLockEntry{
Dependencies: []pkg.PythonPoetryLockDependencyEntry{},
},
},
want: dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"foo"},
},
},
},
{
name: "with required dependencies",
p: pkg.Package{
Name: "foo",
Metadata: pkg.PythonPoetryLockEntry{
Dependencies: []pkg.PythonPoetryLockDependencyEntry{
{
Name: "bar",
Version: "1.2.3",
},
},
},
},
want: dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"foo"},
Requires: []string{"bar"},
},
},
},
{
name: "with optional dependencies (explicit)",
p: pkg.Package{
Name: "foo",
Metadata: pkg.PythonPoetryLockEntry{
Dependencies: []pkg.PythonPoetryLockDependencyEntry{
{
Name: "bar",
Version: "1.2.3",
Optional: true,
},
},
},
},
want: dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"foo"},
Requires: []string{"bar"},
},
},
},
{
name: "without dependencies for non-required extra",
p: pkg.Package{
Name: "foo",
Metadata: pkg.PythonPoetryLockEntry{
Dependencies: []pkg.PythonPoetryLockDependencyEntry{
{
Name: "bar",
Version: "1.2.3",
Optional: true,
Markers: "extra == 'baz'",
},
},
// note: there is no "baz" extra defined
},
},
want: dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"foo"},
Requires: nil, // no requirements for non-required extra
},
},
},
{
name: "package with extra",
p: pkg.Package{
Name: "foo",
Metadata: pkg.PythonPoetryLockEntry{
Dependencies: []pkg.PythonPoetryLockDependencyEntry{
{
Name: "bar", // note: we NEVER reference this, the extras section is the source of truth here
Version: "1.2.3",
Optional: true,
Markers: "extra == 'baz'",
},
},
Extras: []pkg.PythonPoetryLockExtraEntry{
{
Name: "baz",
Dependencies: []string{
"qux",
},
},
},
},
},
want: dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"foo"},
Requires: nil, // no requirements for non-required extra
},
Variants: []dependency.ProvidesRequires{
{
Provides: []string{"foo[baz]"},
Requires: []string{"qux"},
},
},
},
},
{
name: "package using extra",
p: pkg.Package{
Name: "foo",
Metadata: pkg.PythonPoetryLockEntry{
Dependencies: []pkg.PythonPoetryLockDependencyEntry{
{
Name: "starlette",
Version: ">=0.37.2,<0.38.0",
},
{
Name: "bar",
Version: "1.2.3",
Extras: []string{"standard", "things"}, // note multiple extras needed when installing
},
},
Extras: []pkg.PythonPoetryLockExtraEntry{
{
Name: "baz",
Dependencies: []string{
"qux (>=2.0.0)", // should strip version constraint
},
},
},
},
},
want: dependency.Specification{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"foo"},
Requires: []string{
"starlette",
// note: we break out the package and extra requirements separately
// and extras are never combined
"bar",
"bar[standard]",
"bar[things]",
},
},
Variants: []dependency.ProvidesRequires{
{
Provides: []string{"foo[baz]"},
Requires: []string{"qux"},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, poetryLockDependencySpecifier(tt.p))
})
}
}
func Test_poetryLockDependencySpecifier_againstPoetryLock(t *testing.T) {
tests := []struct {
name string
fixture string
want []dependency.Specification
}{
{
name: "simple dependencies with extras",
fixture: "test-fixtures/poetry/simple-deps/poetry.lock",
want: []dependency.Specification{
{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"certifi"},
},
},
{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"charset-normalizer"},
},
},
{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"idna"},
},
},
{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"requests"},
Requires: []string{"certifi", "charset-normalizer", "idna", "urllib3"},
},
Variants: []dependency.ProvidesRequires{
{
Provides: []string{"requests[socks]"},
Requires: []string{"PySocks"},
},
{
Provides: []string{"requests[use-chardet-on-py3]"},
Requires: []string{"chardet"},
},
},
},
{
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"urllib3"},
},
Variants: []dependency.ProvidesRequires{
{
Provides: []string{"urllib3[brotli]"},
Requires: []string{"brotli", "brotlicffi"},
},
{
Provides: []string{"urllib3[h2]"},
Requires: []string{"h2"}},
{
Provides: []string{"urllib3[socks]"},
Requires: []string{"pysocks"},
},
{
Provides: []string{"urllib3[zstd]"},
Requires: []string{"zstandard"},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fh, err := os.Open(tt.fixture)
require.NoError(t, err)
pkgs, err := poetryLockPackages(file.NewLocationReadCloser(file.NewLocation(tt.fixture), fh))
require.NoError(t, err)
var got []dependency.Specification
for _, p := range pkgs {
got = append(got, poetryLockDependencySpecifier(p))
}
if d := cmp.Diff(tt.want, got); d != "" {
t.Errorf("wrong result (-want +got):\n%s", d)
}
})
}
}
+118 -19
View File
@@ -3,6 +3,8 @@ package python
import (
"context"
"fmt"
"sort"
"strings"
"github.com/pelletier/go-toml"
@@ -10,6 +12,7 @@ import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
)
// integrity check
@@ -20,48 +23,144 @@ type poetryPackageSource struct {
}
type poetryPackages struct {
Packages []struct {
Name string `toml:"name"`
Version string `toml:"version"`
Category string `toml:"category"`
Description string `toml:"description"`
Optional bool `toml:"optional"`
Source poetryPackageSource `toml:"source"`
} `toml:"package"`
Packages []poetryPackage `toml:"package"`
}
type poetryPackage struct {
Name string `toml:"name"`
Version string `toml:"version"`
Category string `toml:"category"`
Description string `toml:"description"`
Optional bool `toml:"optional"`
Source poetryPackageSource `toml:"source"`
Dependencies map[string]poetryPackageDependency `toml:"dependencies"`
Extras map[string][]string `toml:"extras"`
}
type poetryPackageDependency struct {
Version string `toml:"version"`
Markers string `toml:"markers"`
Optional bool `toml:"optional"`
Extras []string `toml:"extras"`
}
func (d *poetryPackageDependency) UnmarshalText(data []byte) error {
// attempt to parse as a map first
var dep map[string]interface{}
if err := toml.Unmarshal(data, &dep); err == nil {
if extras, ok := dep["extras"]; ok {
if extrasList, ok := extras.([]string); ok {
d.Extras = extrasList
}
}
if markers, ok := dep["markers"]; ok {
if markersString, ok := markers.(string); ok {
d.Markers = markersString
}
}
if version, ok := dep["version"]; ok {
if versionString, ok := version.(string); ok {
d.Version = versionString
}
}
return nil
}
if strings.ContainsAny(string(data), "[]{}") {
// odds are this is really a malformed toml array or object
return fmt.Errorf("unable to parse poetry dependency: version is malformed array/object: %q", string(data))
}
// assume this is a simple version string
d.Version = string(data)
return nil
}
// parsePoetryLock is a parser function for poetry.lock contents, returning all python packages discovered.
func parsePoetryLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
pkgs, err := poetryLockPackages(reader)
if err != nil {
return nil, nil, err
}
// since we would never expect to create relationships for packages across multiple poetry.lock files
// we should do this on a file parser level (each poetry.lock) instead of a cataloger level (across all
// poetry.lock files)
return pkgs, dependency.Resolve(poetryLockDependencySpecifier, pkgs), nil
}
func poetryLockPackages(reader file.LocationReadCloser) ([]pkg.Package, error) {
tree, err := toml.LoadReader(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to load poetry.lock for parsing: %w", err)
return nil, fmt.Errorf("unable to load poetry.lock for parsing: %w", err)
}
metadata := poetryPackages{}
err = tree.Unmarshal(&metadata)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse poetry.lock: %w", err)
return nil, fmt.Errorf("unable to parse poetry.lock: %w", err)
}
var pkgs []pkg.Package
for _, p := range metadata.Packages {
var index string
if p.Source.URL != "" {
index = p.Source.URL
} else {
// https://python-poetry.org/docs/repositories/
index = "https://pypi.org/simple"
}
pkgs = append(
pkgs,
newPackageForIndexWithMetadata(
p.Name,
p.Version,
pkg.PythonPoetryLockEntry{Index: index},
newPythonPoetryLockEntry(p),
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
)
}
return pkgs, nil
}
return pkgs, nil, nil
func newPythonPoetryLockEntry(p poetryPackage) pkg.PythonPoetryLockEntry {
return pkg.PythonPoetryLockEntry{
Index: extractIndex(p),
Dependencies: extractPoetryDependencies(p),
Extras: extractPoetryExtras(p),
}
}
func extractIndex(p poetryPackage) string {
if p.Source.URL != "" {
return p.Source.URL
}
// https://python-poetry.org/docs/repositories/
return "https://pypi.org/simple"
}
func extractPoetryDependencies(p poetryPackage) []pkg.PythonPoetryLockDependencyEntry {
var deps []pkg.PythonPoetryLockDependencyEntry
for name, dep := range p.Dependencies {
deps = append(deps, pkg.PythonPoetryLockDependencyEntry{
Name: name,
Version: dep.Version,
Extras: dep.Extras,
Markers: dep.Markers,
})
}
sort.Slice(deps, func(i, j int) bool {
return deps[i].Name < deps[j].Name
})
return deps
}
func extractPoetryExtras(p poetryPackage) []pkg.PythonPoetryLockExtraEntry {
var extras []pkg.PythonPoetryLockExtraEntry
for name, deps := range p.Extras {
extras = append(extras, pkg.PythonPoetryLockExtraEntry{
Name: name,
Dependencies: deps,
})
}
sort.Slice(extras, func(i, j int) bool {
return extras[i].Name < extras[j].Name
})
return extras
}
@@ -10,7 +10,7 @@ import (
)
func TestParsePoetryLock(t *testing.T) {
fixture := "test-fixtures/poetry/poetry.lock"
fixture := "test-fixtures/poetry/dev-deps/poetry.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
expectedPkgs := []pkg.Package{
{
@@ -20,7 +20,29 @@ func TestParsePoetryLock(t *testing.T) {
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
Metadata: pkg.PythonPoetryLockEntry{Index: "https://test.pypi.org/simple"},
Metadata: pkg.PythonPoetryLockEntry{
Index: "https://test.pypi.org/simple",
Dependencies: []pkg.PythonPoetryLockDependencyEntry{
{Name: "docutils", Version: "*"},
{Name: "natsort", Version: "*"},
{Name: "six", Version: "*"},
{Name: "sphinx", Version: "*"},
},
Extras: []pkg.PythonPoetryLockExtraEntry{
{
Name: "deploy",
Dependencies: []string{"bumpversion", "twine", "wheel"},
},
{
Name: "docs",
Dependencies: []string{"sphinx", "sphinx-rtd-theme"},
},
{
Name: "test",
Dependencies: []string{"pytest", "pytest-cov", "coveralls", "beautifulsoup4", "hypothesis"},
},
},
},
},
{
Name: "alabaster",
@@ -51,7 +73,6 @@ func TestParsePoetryLock(t *testing.T) {
},
}
// TODO: relationships are not under test
var expectedRelationships []artifact.Relationship
pkgtest.TestFileParser(t, fixture, parsePoetryLock, expectedPkgs, expectedRelationships)
@@ -0,0 +1,444 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.3.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
]
[package.dependencies]
idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"]
[[package]]
name = "brotli"
version = "1.1.0"
description = "Python bindings for the Brotli compression library"
optional = false
python-versions = "*"
files = [
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"},
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
{file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
{file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
{file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
{file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
{file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
{file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
{file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
{file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
{file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
{file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
]
[[package]]
name = "brotlicffi"
version = "1.1.0.0"
description = "Python CFFI bindings to the Brotli library"
optional = false
python-versions = ">=3.7"
files = [
{file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"},
{file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"},
]
[package.dependencies]
cffi = ">=1.0.0"
[[package]]
name = "certifi"
version = "2024.2.2"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
]
[[package]]
name = "cffi"
version = "1.16.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
files = [
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
{file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
{file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
{file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
{file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
{file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
{file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
{file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
{file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
{file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
{file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
{file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
{file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
{file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
{file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
{file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
{file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
{file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
{file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
]
[package.dependencies]
pycparser = "*"
[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "h2"
version = "4.1.0"
description = "HTTP/2 State-Machine based protocol implementation"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
{file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
]
[package.dependencies]
hpack = ">=4.0,<5"
hyperframe = ">=6.0,<7"
[[package]]
name = "hpack"
version = "4.0.0"
description = "Pure-Python HPACK header compression"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
]
[[package]]
name = "httpcore"
version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httpx"
version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
anyio = "*"
brotli = {version = "*", optional = true, markers = "platform_python_implementation == \"CPython\" and extra == \"brotli\""}
brotlicffi = {version = "*", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"brotli\""}
certifi = "*"
h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""}
httpcore = "==1.*"
idna = "*"
sniffio = "*"
socksio = {version = "==1.*", optional = true, markers = "extra == \"socks\""}
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]]
name = "hyperframe"
version = "6.0.1"
description = "HTTP/2 framing layer for Python"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
]
[[package]]
name = "idna"
version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "pprintpp"
version = "0.4.0"
description = "A drop-in replacement for pprint that's actually pretty"
optional = false
python-versions = "*"
files = [
{file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"},
{file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"},
]
[[package]]
name = "pycparser"
version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
name = "pygments"
version = "1.6"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = "*"
files = [
{file = "Pygments-1.6.tar.gz", hash = "sha256:799ed4caf77516e54440806d8d9cd82a7607dfdf4e4fb643815171a4b5c921c0"},
]
[[package]]
name = "rich"
version = "0.3.3"
description = "Render rich text, tables, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.6,<4.0"
files = [
{file = "rich-0.3.3-py3-none-any.whl", hash = "sha256:04f1016b1aba52499104b594c17b1481504bbc7314f8b9ba1a7a9dbf3e8869c4"},
{file = "rich-0.3.3.tar.gz", hash = "sha256:604edf76a2a7d40c7ecc9af5ae41cee3da3e99532c8d0aa54cc42e017ee7f63a"},
]
[package.dependencies]
colorama = ">=0.4.3,<0.5.0"
pprintpp = ">=0.4.0,<0.5.0"
typing-extensions = ">=3.7.4,<4.0.0"
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "socksio"
version = "1.0.0"
description = "Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5."
optional = false
python-versions = ">=3.6"
files = [
{file = "socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3"},
{file = "socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac"},
]
[[package]]
name = "typing-extensions"
version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
optional = false
python-versions = "*"
files = [
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "a08ffc499b736c19f960d9ba247325d94852c779f10b31ce49976d263e87b24e"
@@ -0,0 +1,17 @@
[tool.poetry]
name = "testpkg"
version = "0.1.0"
description = ""
authors = ["Alex Goodman <wagoodman@users.noreply.github.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
httpx = {extras = ["brotli", "http2", "socks"], version = "^0.27.0"}
pygments = "1.6"
click = "<8"
rich = "<10"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,373 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.3.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
]
[package.dependencies]
idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"]
[[package]]
name = "brotli"
version = "1.1.0"
description = "Python bindings for the Brotli compression library"
optional = false
python-versions = "*"
files = [
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"},
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"},
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"},
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"},
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
{file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"},
{file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"},
{file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"},
{file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"},
{file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"},
{file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"},
{file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"},
{file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"},
{file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"},
{file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"},
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"},
{file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"},
{file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"},
{file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"},
{file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"},
{file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"},
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"},
{file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"},
{file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"},
{file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"},
{file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"},
{file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"},
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
]
[[package]]
name = "brotlicffi"
version = "1.1.0.0"
description = "Python CFFI bindings to the Brotli library"
optional = false
python-versions = ">=3.7"
files = [
{file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"},
{file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"},
{file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"},
{file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"},
{file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"},
{file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"},
{file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"},
]
[package.dependencies]
cffi = ">=1.0.0"
[[package]]
name = "certifi"
version = "2024.2.2"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
]
[[package]]
name = "cffi"
version = "1.16.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
files = [
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
{file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
{file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
{file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
{file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
{file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
{file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
{file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
{file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
{file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
{file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
{file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
{file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
{file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
{file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
{file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
{file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
{file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
{file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
]
[package.dependencies]
pycparser = "*"
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "h2"
version = "4.1.0"
description = "HTTP/2 State-Machine based protocol implementation"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
{file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
]
[package.dependencies]
hpack = ">=4.0,<5"
hyperframe = ">=6.0,<7"
[[package]]
name = "hpack"
version = "4.0.0"
description = "Pure-Python HPACK header compression"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
]
[[package]]
name = "httpcore"
version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httpx"
version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
anyio = "*"
brotli = {version = "*", optional = true, markers = "platform_python_implementation == \"CPython\" and extra == \"brotli\""}
brotlicffi = {version = "*", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"brotli\""}
certifi = "*"
h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""}
httpcore = "==1.*"
idna = "*"
sniffio = "*"
socksio = {version = "==1.*", optional = true, markers = "extra == \"socks\""}
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]]
name = "hyperframe"
version = "6.0.1"
description = "HTTP/2 framing layer for Python"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
]
[[package]]
name = "idna"
version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "pycparser"
version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "socksio"
version = "1.0.0"
description = "Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5."
optional = false
python-versions = ">=3.6"
files = [
{file = "socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3"},
{file = "socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "1aa23bf12b27ddd43daba759482256c2f8a0f62d3788f6930123ef5008847f60"
@@ -0,0 +1,14 @@
[tool.poetry]
name = "testpkg"
version = "0.1.0"
description = ""
authors = ["Alex Goodman <wagoodman@users.noreply.github.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
httpx = {extras = ["brotli", "http2", "socks"], version = "^0.27.0"}
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
[tool.poetry]
name = "testpkg"
version = "0.1.0"
description = ""
authors = ["Alex Goodman <wagoodman@users.noreply.github.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
fastapi = {extras = ["all"], version = "^0.111.0"}
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,166 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "certifi"
version = "2024.2.2"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
name = "idna"
version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "requests"
version = "2.32.2"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
files = [
{file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
{file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "urllib3"
version = "2.2.1"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
files = [
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "bbde8c07caea5e9a5695c84bc6ec39ded1436ea0dcbfd59942a4afdff86126ca"
@@ -0,0 +1,14 @@
[tool.poetry]
name = "testpkg"
version = "0.1.0"
description = ""
authors = ["Alex Goodman <wagoodman@users.noreply.github.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.32.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
+6 -2
View File
@@ -48,6 +48,10 @@ func NewArchiveCataloger() pkg.Cataloger {
}
func isSqliteDriverAvailable() bool {
_, err := sql.Open("sqlite", ":memory:")
return err == nil
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
return false
}
_ = db.Close()
return true
}
+4 -2
View File
@@ -45,8 +45,10 @@ func dbEntryDependencySpecifier(p pkg.Package) dependency.Specification {
}
return dependency.Specification{
Provides: provides,
Requires: requires,
ProvidesRequires: dependency.ProvidesRequires{
Provides: provides,
Requires: requires,
},
}
}
+12 -6
View File
@@ -25,8 +25,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
},
},
},
{
@@ -39,8 +41,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a", "libc.so.6(GLIBC_2.11)(64bit)"},
Requires: []string{"config(bash)"},
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a", "libc.so.6(GLIBC_2.11)(64bit)"},
Requires: []string{"config(bash)"},
},
},
},
{
@@ -53,8 +57,10 @@ func Test_dbEntryDependencySpecifier(t *testing.T) {
},
},
want: dependency.Specification{
Provides: []string{"package-a"},
Requires: nil,
ProvidesRequires: dependency.ProvidesRequires{
Provides: []string{"package-a"},
Requires: nil,
},
},
},
}
+3
View File
@@ -89,6 +89,9 @@ func packageURL(name, arch string, epoch *int, srpm string, version, release str
if distro != nil {
namespace = distro.ID
}
if namespace == "rhel" {
namespace = "redhat"
}
qualifiers := map[string]string{}
+2 -2
View File
@@ -28,7 +28,7 @@ func Test_packageURL(t *testing.T) {
Release: "r",
Epoch: nil,
},
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
expected: "pkg:rpm/redhat/p@v-r?distro=rhel-8.4",
},
{
name: "with arch and epoch",
@@ -67,7 +67,7 @@ func Test_packageURL(t *testing.T) {
Release: "r",
SourceRpm: "sourcerpm",
},
expected: "pkg:rpm/rhel/p@v-r?upstream=sourcerpm&distro=rhel-8.4",
expected: "pkg:rpm/redhat/p@v-r?upstream=sourcerpm&distro=rhel-8.4",
},
}
+16 -1
View File
@@ -66,7 +66,22 @@ type PythonPipfileLockEntry struct {
// PythonPoetryLockEntry represents a single package entry within a Pipfile.lock file.
type PythonPoetryLockEntry struct {
Index string `mapstructure:"index" json:"index"`
Index string `mapstructure:"index" json:"index"`
Dependencies []PythonPoetryLockDependencyEntry `json:"dependencies"`
Extras []PythonPoetryLockExtraEntry `json:"extras,omitempty"`
}
type PythonPoetryLockDependencyEntry struct {
Name string `json:"name"`
Version string `json:"version"`
Optional bool `json:"optional"`
Markers string `json:"markers,omitempty"`
Extras []string `json:"extras,omitempty"`
}
type PythonPoetryLockExtraEntry struct {
Name string `json:"name"`
Dependencies []string `json:"dependencies"`
}
// PythonRequirementsEntry represents a single entry within a [*-]requirements.txt file.
+60
View File
@@ -0,0 +1,60 @@
package cli
import (
"archive/tar"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestArchiveScan(t *testing.T) {
tests := []struct {
name string
args []string
archiveFixture string
env map[string]string
assertions []traitAssertion
}{
{
name: "scan an archive within the temp dir",
args: []string{
"scan",
"-o",
"json",
"file:" + createArchive(t, "test-fixtures/archive", t.TempDir()),
},
assertions: []traitAssertion{
assertSuccessfulReturnCode,
assertJsonReport,
assertPackageCount(1),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cmd, stdout, stderr := runSyft(t, test.env, test.args...)
for _, traitAssertionFn := range test.assertions {
traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
}
logOutputOnFailure(t, cmd, stdout, stderr)
})
}
}
func createArchive(t *testing.T, path string, destDir string) string {
// create a tarball of the test fixtures (not by shelling out)
archivePath := filepath.Join(destDir, "test.tar")
fh, err := os.Create(archivePath)
require.NoError(t, err)
defer fh.Close()
writer := tar.NewWriter(fh)
require.NoError(t, writer.AddFS(os.DirFS(path)))
require.NoError(t, writer.Close())
return archivePath
}
@@ -0,0 +1,47 @@
Metadata-Version: 2.1
Name: Pygments
Version: 2.6.1
Summary: Pygments is a syntax highlighting package written in Python.
Home-page: https://pygments.org/
Author: Georg Brandl
Author-email: georg@python.org
License: BSD License
Keywords: syntax highlighting
Platform: any
Classifier: License :: OSI Approved :: BSD License
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: System Administrators
Classifier: Development Status :: 6 - Mature
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Operating System :: OS Independent
Classifier: Topic :: Text Processing :: Filters
Classifier: Topic :: Utilities
Requires-Python: >=3.5
Pygments
~~~~~~~~
Pygments is a syntax highlighting package written in Python.
It is a generic syntax highlighter suitable for use in code hosting, forums,
wikis or other applications that need to prettify source code. Highlights
are:
* a wide range of over 500 languages and other text formats is supported
* special attention is paid to details, increasing quality by a fair amount
* support for new languages and formats are added easily
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences
* it is usable as a command-line tool and as a library
:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
@@ -0,0 +1,5 @@
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
Pygments-2.6.1.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
Pygments-2.6.1.dist-info/RECORD,,
pygments/__pycache__/__init__.cpython-38.pyc,,
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
@@ -0,0 +1 @@
top-level-pkg
@@ -1,2 +0,0 @@
FROM scratch
ADD api-key.txt .
@@ -1 +0,0 @@
some_ApI-kEy = "12345A7a901b345678901234567890123456789012345678901234567890"
@@ -1,2 +0,0 @@
FROM scratch
ADD api-key.txt .
@@ -1 +0,0 @@
some_ApI-kEy = "12345A7a901b345678901234567890123456789012345678901234567890"
@@ -38,3 +38,49 @@ test_search_for_asset_snapshot() {
}
run_test_case test_search_for_asset_snapshot
# verify 256 digest of a file
test_hash_sha256() {
target=./test-fixtures/assets/valid/syft_1.5.0_linux_arm64.tar.gz
# hash_sha256 [target]
# positive case
actual=$(hash_sha256 "${target}")
assertEquals "8d57abb57a0dae3ff23c8f0df1f51951b7772822e0d560e860d6f68c24ef6d3d" "${actual}" "mismatched checksum"
}
run_test_case test_hash_sha256
# verify 256 digest of a file relative to the checksums file
test_hash_sha256_verify() {
# hash_sha256_verify [target] [checksums]
# positive case
checksums=./test-fixtures/assets/valid/checksums.txt
target=./test-fixtures/assets/valid/syft_1.5.0_linux_arm64.tar.gz
hash_sha256_verify "${target}" "${checksums}"
assertEquals "0" "$?" "mismatched checksum"
# negative case
# we are expecting error messages, which is confusing to look at in passing tests... disable logging for now
log_set_priority -1
checksums=./test-fixtures/assets/invalid/checksums.txt
target=./test-fixtures/assets/invalid/syft_1.5.0_linux_arm64.tar.gz
hash_sha256_verify "${target}" "${checksums}"
assertEquals "1" "$?" "verification did not catch mismatched checksum"
# restore logging...
log_set_priority 0
}
run_test_case test_hash_sha256_verify
@@ -63,7 +63,7 @@ test_negative_snapshot_download_asset() {
test_sboms_have_packages() {
find "$(snapshot_dir)/" -name "*.sbom" -print0 | while IFS= read -r -d '' file; do
count=$(cat "$file" | jq ".artifacts | length")
if [ "$count" -gt 80 ]; then
if [ "$count" -lt 80 ]; then
echo "not enough packages found for file: $file"
exit 1
fi
@@ -31,6 +31,10 @@ test_download_release_asset() {
release=$(get_release_tag "${OWNER}" "${REPO}" "latest" )
# exercise all possible assets against a real github release (based on asset listing from https://github.com/anchore/syft/releases/tag/v0.36.0)
# verify all downloads against the checksums file + checksums file signature
VERIFY_SIGN=true
run_test_case test_download_release_asset "${release}" "darwin" "amd64" "tar.gz" "application/gzip"
run_test_case test_download_release_asset "${release}" "darwin" "arm64" "tar.gz" "application/gzip"
run_test_case test_download_release_asset "${release}" "linux" "amd64" "tar.gz" "application/gzip"
+89
View File
@@ -0,0 +1,89 @@
. test_harness.sh
test_compare_semver() {
# compare_semver [version1] [version2]
# positive cases (version1 >= version2)
compare_semver "0.32.0" "0.32.0"
assertEquals "0" "$?" "+ versions should equal"
compare_semver "0.32.1" "0.32.0"
assertEquals "0" "$?" "+ patch version should be greater"
compare_semver "0.33.0" "0.32.0"
assertEquals "0" "$?" "+ minor version should be greater"
compare_semver "0.333.0" "0.32.0"
assertEquals "0" "$?" "+ minor version should be greater (different length)"
compare_semver "00.33.00" "0.032.0"
assertEquals "0" "$?" "+ minor version should be greater (different length reversed)"
compare_semver "1.0.0" "0.9.9"
assertEquals "0" "$?" "+ major version should be greater"
compare_semver "v1.0.0" "1.0.0"
assertEquals "0" "$?" "+ can remove leading 'v' from version"
# negative cases (version1 < version2)
compare_semver "0.32.0" "0.32.1"
assertEquals "1" "$?" "- patch version should be less"
compare_semver "0.32.7" "0.33.0"
assertEquals "1" "$?" "- minor version should be less"
compare_semver "00.00032.070" "0.33.0"
assertEquals "1" "$?" "- minor version should be less (different length)"
compare_semver "0.32.7" "00.0033.000"
assertEquals "1" "$?" "- minor version should be less (different length reversed)"
compare_semver "1.9.9" "2.0.1"
assertEquals "1" "$?" "- major version should be less"
compare_semver "1.0.0" "v2.0.0"
assertEquals "1" "$?" "- can remove leading 'v' from version"
}
run_test_case test_compare_semver
# ensure that various signature verification pre-requisites are correctly checked for
test_prep_signature_verification() {
# prep_sign_verification [version]
# we are expecting error messages, which is confusing to look at in passing tests... disable logging for now
log_set_priority -1
# backup original values...
OG_COSIGN_BINARY=${COSIGN_BINARY}
# check the verification path...
VERIFY_SIGN=true
# release does not support signature verification
prep_signature_verification "0.103.0"
assertEquals "1" "$?" "release does not support signature verification"
# check that the COSIGN binary exists
COSIGN_BINARY=fake-cosign-that-doesnt-exist
prep_signature_verification "0.105.0"
assertEquals "1" "$?" "cosign binary verification failed"
# restore original values...
COSIGN_BINARY=${OG_COSIGN_BINARY}
# ignore any failing conditions since we are not verifying the signature
VERIFY_SIGN=false
prep_signature_verification "0.103.0"
assertEquals "0" "$?" "release support verification should not have been triggered"
COSIGN_BINARY=fake-cosign-that-doesnt-exist
prep_signature_verification "0.105.0"
assertEquals "0" "$?" "cosign binary verification should not have been triggered"
# restore original values...
COSIGN_BINARY=${OG_COSIGN_BINARY}
# restore logging...
log_set_priority 0
}
run_test_case test_prep_signature_verification
+25 -16
View File
@@ -1,18 +1,22 @@
NAME=syft
# for local testing (not testing within containers) use the binny-managed version of cosign.
# this also means that the user does not need to install cosign on their system to run tests.
COSIGN_BINARY=../../.tool/cosign
IMAGE_NAME=$(NAME)-install.sh-env
UBUNTU_IMAGE=$(IMAGE_NAME):ubuntu-20.04
ALPINE_IMAGE=$(IMAGE_NAME):alpine-3.6
BUSYBOX_IMAGE=busybox:1.36.1-musl
BUSYBOX_IMAGE=$(IMAGE_NAME):busybox-1.36
ENVS=./environments
DOCKER_RUN=docker run --rm -t -w /project/test/install -v $(shell pwd)/../../:/project
UNIT=make unit-local
UNIT=make unit-run
# acceptance testing is running the current install.sh against the latest release. Note: this could be a problem down
# the line if there are breaking changes made that don't align with the latest release (but will be OK with the next
# release)
ACCEPTANCE_CMD=sh -c '../../install.sh -b /usr/local/bin && syft version'
# release). This tests both installing with signature verification and without.
ACCEPTANCE_CMD=sh -c '../../install.sh -v -b /usr/local/bin && syft version && rm /usr/local/bin/syft && ../../install.sh -b /usr/local/bin && syft version'
# we also want to test against a previous release to ensure that install.sh defers execution to a former install.sh
PREVIOUS_RELEASE=v0.33.0
ACCEPTANCE_PREVIOUS_RELEASE_CMD=sh -c "../../install.sh -b /usr/local/bin $(PREVIOUS_RELEASE) && syft version"
@@ -28,17 +32,22 @@ endef
test: unit acceptance
.PHONY: ci-test-mac
ci-test-mac: unit-local acceptance-local
ci-test-mac: unit-run acceptance-local
# note: do not add acceptance-local to this list
.PHONY: acceptance
acceptance: acceptance-ubuntu-20.04 acceptance-alpine-3.6 acceptance-busybox
acceptance: acceptance-ubuntu-20.04 acceptance-alpine-3.6 acceptance-busybox-1.36
.PHONY: unit
unit: unit-ubuntu-20.04
.PHONY: unit-local
unit-local:
$(call title,unit tests)
@for f in $(shell ls *_test.sh); do echo "Running unit test suite '$${f}'"; bash -c "COSIGN_BINARY=$(COSIGN_BINARY) ./$${f}" || exit 1; done
.PHONY: unit-run
unit-run:
$(call title,unit tests)
@for f in $(shell ls *_test.sh); do echo "Running unit test suite '$${f}'"; bash $${f} || exit 1; done
@@ -55,7 +64,7 @@ acceptance-previous-release-local:
syft version | grep $(shell echo $(PREVIOUS_RELEASE)| tr -d "v")
.PHONY: save
save: ubuntu-20.04 alpine-3.6 pull-busybox
save: ubuntu-20.04 alpine-3.6 busybox-1.36
@mkdir cache || true
docker image save -o cache/ubuntu-env.tar $(UBUNTU_IMAGE)
docker image save -o cache/alpine-env.tar $(ALPINE_IMAGE)
@@ -88,7 +97,7 @@ ubuntu-20.04:
## ALPINE #######################################################
# note: unit tests cannot be run with sh (alpine dosn't have bash by default)
# note: unit tests cannot be run with sh (alpine doesn't have bash by default)
.PHONY: acceptance-alpine-3.6
acceptance-alpine-3.6: alpine-3.6
@@ -103,21 +112,21 @@ alpine-3.6:
## BUSYBOX #######################################################
# note: unit tests cannot be run with sh (busybox dosn't have bash by default)
# note: unit tests cannot be run with sh (busybox doesn't have bash by default)
# note: busybox by default will not have cacerts, so you will get TLS warnings (we want to test under these conditions)
.PHONY: acceptance-busybox
acceptance-busybox: pull-busybox
$(call title,busybox - acceptance)
.PHONY: acceptance-busybox-1.36
acceptance-busybox-1.36: busybox-1.36
$(call title,busybox-1.36 - acceptance)
$(DOCKER_RUN) $(BUSYBOX_IMAGE) \
$(ACCEPTANCE_CMD)
@echo "\n*** test note: you should see syft spit out a 'x509: certificate signed by unknown authority' error --this is expected ***"
.PHONY: pull-busybox
pull-busybox:
$(call title,busybox - build environment)
docker pull $(BUSYBOX_IMAGE)
.PHONY: busybox-1.36
busybox-1.36:
$(call title,busybox-1.36 - build environment)
docker build -t $(BUSYBOX_IMAGE) -f $(ENVS)/Dockerfile-busybox-1.36 .
## For CI ########################################################
@@ -1,2 +1,5 @@
FROM alpine:3.6@sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475
RUN apk update && apk add python3 wget unzip make ca-certificates jq
FROM alpine:3.6
RUN apk update && apk add python3 wget curl unzip make ca-certificates
RUN curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" && \
mv cosign-linux-amd64 /usr/local/bin/cosign && \
chmod +x /usr/local/bin/cosign
@@ -0,0 +1,21 @@
FROM alpine as certs
RUN apk update && apk add ca-certificates
# note: using qemu with a multi-arch image results in redirects not working with wget
# so let docker pull the image that matches the hosts architecture first and then pull the correct asset
FROM busybox:1.36.1-musl
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
COSIGN_ARCH="amd64"; \
elif [ "$ARCH" = "aarch64" ]; then \
COSIGN_ARCH="arm64"; \
else \
echo "Unsupported architecture: $ARCH" && exit 1; \
fi && \
echo "Downloading cosign for $COSIGN_ARCH" && \
wget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-${COSIGN_ARCH} && \
mv cosign-linux-${COSIGN_ARCH} /bin/cosign && \
chmod +x /bin/cosign
COPY --from=certs /etc/ssl/certs /etc/ssl/certs
@@ -1,2 +1,5 @@
FROM ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e3b2026b4fc2faba
RUN apt update -y && apt install make python3 curl unzip jq -y
FROM --platform=linux/amd64 ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e3b2026b4fc2faba
RUN apt update -y && apt install make python3 curl unzip -y
RUN LATEST_VERSION=$(curl https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d : -f2 | tr -d "v\", ") && \
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign_${LATEST_VERSION}_amd64.deb" && \
dpkg -i cosign_${LATEST_VERSION}_amd64.deb
@@ -0,0 +1 @@
!syft_1.5.0_linux_arm64.tar.gz
@@ -0,0 +1,22 @@
f459ecbed6621933b4dee4a613a6bdca915631742083f77ab88752f3ae0c6a9d syft_1.5.0_darwin_amd64.sbom
605322e3e7043a4f2f3d6e37f75a71389d38f6f290bff2e54bb2aaebbbf4829b syft_1.5.0_darwin_amd64.tar.gz
2feb92f6a4d117a3eb0cc5d887ac4b8da9a959ca404986dbe76e81ac83569d76 syft_1.5.0_darwin_arm64.sbom
fe02d072e7ec9a8eb4ac866ba973396a8beae79829ee870acaadd4d862e5e65a syft_1.5.0_darwin_arm64.tar.gz
40562766f99db7221dac4c82c44ccae4d9983a6795a96127f4da955d6797c090 syft_1.5.0_linux_amd64.deb
9adc0c0a3bbbdb8ce66914ffde5e176373cf5ec70a2a88cc886dc6ec54db0cd2 syft_1.5.0_linux_amd64.rpm
9de7f1c549020a25df12f07dd58330e6f6dd0c5363cf0de1157eb4f01fc41020 syft_1.5.0_linux_amd64.sbom
3d10023d46dfaf0fe75288df207b478b43597f7d2fff553f58430817166bd478 syft_1.5.0_linux_amd64.tar.gz
b661dd1be48b75b735717f9bedc4beea587968af8bf2d8c2752a302ae0a34f89 syft_1.5.0_linux_arm64.deb
5c311f44cc73fcae58884f8ea3328fc120bdaf3d21f4fe40270a22c6bb981f6d syft_1.5.0_linux_arm64.rpm
a74549794fa5dac79ab9f6401ee7000db3bd9589435b302583c3ee4ca13fb8e3 syft_1.5.0_linux_arm64.sbom
ee2b1289a1e4b0de9409c3a78867949ca42788a5f50072b8a6e6e04e6a269f9c syft_1.5.0_linux_arm64.tar.gz
599e66f0a00ba8b5cc7817f774c66c5f23de32e6b935ca9c03a185e4d2554d80 syft_1.5.0_linux_ppc64le.deb
dc4b8c4a02ffd8dd394ab94695ed44ed2bfceb06bc239835c51e84dd9b84a68c syft_1.5.0_linux_ppc64le.rpm
392044f2951ca6522d0bc71de43763202a548d40bf836098caf286041a7c8fa4 syft_1.5.0_linux_ppc64le.sbom
551d98b67f7476bc2e38453a588177b0b038933850a351a94c4bf360813f01d0 syft_1.5.0_linux_ppc64le.tar.gz
98cd582d9484f428cccd7351031543038f26d4e3f1481c5916e08e65983f1e21 syft_1.5.0_linux_s390x.deb
3a5197d43a469feaa87c723448a47a2312cf26456eef4580b59eac447baef9d4 syft_1.5.0_linux_s390x.rpm
5805bfb3e30452c8860665ea88bfdf356e9536a35c1b8ba28ac5b4717f2e6388 syft_1.5.0_linux_s390x.sbom
ff15f556660cc4c4279ce41e2475bffe2a0f72eb4423c21edf7380b2484fbf68 syft_1.5.0_linux_s390x.tar.gz
6b22df07e992d8f4881901535c095d268f79924e10be34fab03dec2b4f9f9ca0 syft_1.5.0_windows_amd64.sbom
5079c6a88e130f8677d0701cb2689f9eae2088022ecf5fa2b9f341b96d9983d2 syft_1.5.0_windows_amd64.zip

Some files were not shown because too many files have changed in this diff Show More